diff --git a/app/api/listings/translate/route.ts b/app/api/listings/translate/route.ts index f85ee4c..4d7558e 100644 --- a/app/api/listings/translate/route.ts +++ b/app/api/listings/translate/route.ts @@ -65,7 +65,7 @@ export async function POST(req: Request) { { role: 'system', content: - 'You are translating holiday rental listing copy between Finnish, Swedish, and English. Fill in missing locales, keep existing text unchanged, preserve meaning and tone, and respond with JSON only.', + 'You are translating holiday rental listing copy between Finnish, Swedish, and English. Fill in missing locales, keep existing text unchanged, preserve meaning and tone, and respond with JSON only. Suggest localized slugs if missing.', }, { role: 'user', @@ -74,6 +74,7 @@ export async function POST(req: Request) { sourceLocale: currentLocale, targetLocales: SUPPORTED_LOCALES.filter((l) => l !== currentLocale), locales: payload, + askForSlugs: true, }, null, 2, @@ -82,7 +83,7 @@ export async function POST(req: Request) { { role: 'system', content: - 'Return JSON with top-level "locales" containing keys en, fi, sv. Each locale has title, teaser, description. Do not include explanations.', + 'Return JSON with top-level "locales" containing keys en, fi, sv. Each locale has title, teaser, description, and slug. Do not include explanations.', }, ]; diff --git a/app/listings/new/page.tsx b/app/listings/new/page.tsx index 9300e0b..0130cc1 100644 --- a/app/listings/new/page.tsx +++ b/app/listings/new/page.tsx @@ -62,6 +62,7 @@ export default function NewListingPage() { const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'error'>('idle'); const [aiLoading, setAiLoading] = useState(false); const [slugStatus, setSlugStatus] = useState<'idle' | 'checking' | 'available' | 'taken' | 'error'>('idle'); + const [suggestedSlugs, setSuggestedSlugs] = useState>({ en: '', fi: '', sv: '' }); useEffect(() => { setCurrentLocale(uiLocale as Locale); @@ -151,6 +152,8 @@ export default function NewListingPage() { 'Preserve meaning, tone, numbers, and any markup.', 'Return valid JSON only with the same keys.', 'Fill missing translations; keep existing text unchanged.', + 'Suggest localized slugs based on the title/description; keep them URL-friendly (kebab-case).', + 'If teaser or slug is empty, propose one; otherwise keep the existing value.', ], sourceLocale: currentLocale, targetLocales: SUPPORTED_LOCALES.filter((loc) => loc !== currentLocale), @@ -161,13 +164,14 @@ export default function NewListingPage() { title: translations[loc].title, teaser: translations[loc].teaser, description: translations[loc].description, + slug: suggestedSlugs[loc] || slug, }, }), - {} as Record + {} as Record ), }; return JSON.stringify(payload, null, 2); - }, [translations, currentLocale]); + }, [translations, currentLocale, suggestedSlugs, slug]); async function autoTranslate() { if (aiLoading) return; @@ -184,7 +188,7 @@ export default function NewListingPage() { if (!res.ok || !data.translations) { throw new Error('bad response'); } - const incoming = data.translations as Record; + const incoming = data.translations as Record; setTranslations((prev) => { const next = { ...prev }; SUPPORTED_LOCALES.forEach((loc) => { @@ -197,6 +201,15 @@ export default function NewListingPage() { }); return next; }); + setSuggestedSlugs((prev) => { + const next = { ...prev }; + SUPPORTED_LOCALES.forEach((loc) => { + if (incoming?.[loc]?.slug) { + next[loc] = incoming[loc].slug as string; + } + }); + return next; + }); setMessage(t('aiAutoSuccess')); } catch (err) { setError(t('aiAutoError')); @@ -225,6 +238,16 @@ export default function NewListingPage() { }; } }); + setSuggestedSlugs((prev) => { + const next = { ...prev }; + SUPPORTED_LOCALES.forEach((loc) => { + const incoming = locales[loc]; + if (incoming && typeof incoming.slug === 'string' && incoming.slug) { + next[loc] = incoming.slug; + } + }); + return next; + }); setTranslations(next); setMessage(t('aiApplySuccess')); } catch (err) { @@ -379,17 +402,6 @@ export default function NewListingPage() {

{t('createListingTitle')}

-
{t('languageTabsLabel')} @@ -417,22 +429,48 @@ export default function NewListingPage() { style={{ display: 'grid', gap: 12, - gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', + gridTemplateColumns: 'repeat(auto-fit, minmax(340px, 1fr))', alignItems: 'start', }} > -
+
+

{t('localeSectionTitle')}