Improve localization AI helper layout
This commit is contained in:
parent
c128609c64
commit
82a4738d95
2 changed files with 84 additions and 35 deletions
|
|
@ -59,6 +59,7 @@ export default function NewListingPage() {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isAuthed, setIsAuthed] = useState(false);
|
const [isAuthed, setIsAuthed] = useState(false);
|
||||||
const [aiResponse, setAiResponse] = useState('');
|
const [aiResponse, setAiResponse] = useState('');
|
||||||
|
const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'error'>('idle');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentLocale(uiLocale as Locale);
|
setCurrentLocale(uiLocale as Locale);
|
||||||
|
|
@ -192,6 +193,18 @@ export default function NewListingPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyAiPrompt() {
|
||||||
|
try {
|
||||||
|
if (!navigator?.clipboard) throw new Error('clipboard unavailable');
|
||||||
|
await navigator.clipboard.writeText(aiPrompt);
|
||||||
|
setCopyStatus('copied');
|
||||||
|
setTimeout(() => setCopyStatus('idle'), 1500);
|
||||||
|
} catch (err) {
|
||||||
|
setCopyStatus('error');
|
||||||
|
setTimeout(() => setCopyStatus('idle'), 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function parseImages(): ImageInput[] {
|
function parseImages(): ImageInput[] {
|
||||||
return selectedImages.map((img) => ({
|
return selectedImages.map((img) => ({
|
||||||
data: img.dataUrl,
|
data: img.dataUrl,
|
||||||
|
|
@ -306,7 +319,7 @@ export default function NewListingPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="panel" style={{ maxWidth: 720, margin: '40px auto' }}>
|
<main className="panel" style={{ maxWidth: 1100, margin: '40px auto' }}>
|
||||||
<h1>{t('createListingTitle')}</h1>
|
<h1>{t('createListingTitle')}</h1>
|
||||||
<form onSubmit={onSubmit} style={{ display: 'grid', gap: 10 }}>
|
<form onSubmit={onSubmit} style={{ display: 'grid', gap: 10 }}>
|
||||||
<label>
|
<label>
|
||||||
|
|
@ -337,40 +350,67 @@ export default function NewListingPage() {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label>
|
<div
|
||||||
{t('titleLabel')}
|
style={{
|
||||||
<input value={translations[currentLocale].title} onChange={(e) => updateTranslation(currentLocale, 'title', e.target.value)} required />
|
display: 'grid',
|
||||||
</label>
|
gap: 12,
|
||||||
<label>
|
gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))',
|
||||||
{t('descriptionLabel')}
|
alignItems: 'start',
|
||||||
<textarea value={translations[currentLocale].description} onChange={(e) => updateTranslation(currentLocale, 'description', e.target.value)} required rows={4} />
|
}}
|
||||||
</label>
|
>
|
||||||
<label>
|
<div style={{ display: 'grid', gap: 8 }}>
|
||||||
{t('teaserLabel')}
|
<label>
|
||||||
<input value={translations[currentLocale].teaser} onChange={(e) => updateTranslation(currentLocale, 'teaser', e.target.value)} />
|
{t('titleLabel')}
|
||||||
</label>
|
<input value={translations[currentLocale].title} onChange={(e) => updateTranslation(currentLocale, 'title', e.target.value)} required />
|
||||||
<div className="panel" style={{ border: '1px solid rgba(148,163,184,0.3)', background: 'rgba(255,255,255,0.02)' }}>
|
</label>
|
||||||
<h3 style={{ marginTop: 0 }}>{t('aiHelperTitle')}</h3>
|
<label>
|
||||||
<p style={{ color: '#cbd5e1', marginTop: 4 }}>{t('aiHelperLead')}</p>
|
{t('descriptionLabel')}
|
||||||
<label style={{ display: 'grid', gap: 6, marginTop: 8 }}>
|
<textarea value={translations[currentLocale].description} onChange={(e) => updateTranslation(currentLocale, 'description', e.target.value)} required rows={4} />
|
||||||
<span>{t('aiPromptLabel')}</span>
|
</label>
|
||||||
<textarea value={aiPrompt} readOnly rows={10} style={{ fontFamily: 'monospace' }} />
|
<label>
|
||||||
</label>
|
{t('teaserLabel')}
|
||||||
<label style={{ display: 'grid', gap: 6, marginTop: 8 }}>
|
<input value={translations[currentLocale].teaser} onChange={(e) => updateTranslation(currentLocale, 'teaser', e.target.value)} />
|
||||||
<span>{t('aiResponseLabel')}</span>
|
</label>
|
||||||
<textarea
|
</div>
|
||||||
value={aiResponse}
|
<div className="panel" style={{ border: '1px solid rgba(148,163,184,0.3)', background: 'rgba(255,255,255,0.02)' }}>
|
||||||
onChange={(e) => setAiResponse(e.target.value)}
|
<h3 style={{ marginTop: 0 }}>{t('aiHelperTitle')}</h3>
|
||||||
rows={6}
|
<p style={{ color: '#cbd5e1', marginTop: 4 }}>{t('aiHelperLead')}</p>
|
||||||
placeholder='{"locales":{"fi":{"title":"..."}}}'
|
<div style={{ display: 'grid', gap: 6, marginTop: 8 }}>
|
||||||
style={{ fontFamily: 'monospace' }}
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap' }}>
|
||||||
/>
|
<span>{t('aiPromptLabel')}</span>
|
||||||
</label>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||||
<div style={{ display: 'flex', gap: 10, marginTop: 8, flexWrap: 'wrap' }}>
|
{copyStatus === 'copied' ? (
|
||||||
<button type="button" className="button secondary" onClick={() => applyAiResponse()}>
|
<span style={{ color: '#34d399', fontSize: 13 }}>{t('aiPromptCopied')}</span>
|
||||||
{t('aiApply')}
|
) : null}
|
||||||
</button>
|
{copyStatus === 'error' ? <span style={{ color: '#f87171', fontSize: 13 }}>{t('aiCopyError')}</span> : null}
|
||||||
<span style={{ color: '#cbd5e1', fontSize: 13 }}>{t('aiHelperNote')}</span>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="button secondary"
|
||||||
|
onClick={copyAiPrompt}
|
||||||
|
style={{ minHeight: 0, padding: '8px 12px' }}
|
||||||
|
>
|
||||||
|
{t('aiCopyPrompt')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea value={aiPrompt} readOnly rows={10} style={{ fontFamily: 'monospace' }} />
|
||||||
|
</div>
|
||||||
|
<label style={{ display: 'grid', gap: 6, marginTop: 8 }}>
|
||||||
|
<span>{t('aiResponseLabel')}</span>
|
||||||
|
<textarea
|
||||||
|
value={aiResponse}
|
||||||
|
onChange={(e) => setAiResponse(e.target.value)}
|
||||||
|
rows={6}
|
||||||
|
placeholder='{"locales":{"fi":{"title":"..."}}}'
|
||||||
|
style={{ fontFamily: 'monospace' }}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div style={{ display: 'flex', gap: 10, marginTop: 8, flexWrap: 'wrap' }}>
|
||||||
|
<button type="button" className="button secondary" onClick={() => applyAiResponse()}>
|
||||||
|
{t('aiApply')}
|
||||||
|
</button>
|
||||||
|
<span style={{ color: '#cbd5e1', fontSize: 13 }}>{t('aiHelperNote')}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))' }}>
|
<div style={{ display: 'grid', gap: 8, gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))' }}>
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,9 @@ const baseMessages = {
|
||||||
aiHelperTitle: 'AI translation helper',
|
aiHelperTitle: 'AI translation helper',
|
||||||
aiHelperLead: 'Copy the prompt to your AI assistant, let it translate missing locales, and paste the JSON reply back.',
|
aiHelperLead: 'Copy the prompt to your AI assistant, let it translate missing locales, and paste the JSON reply back.',
|
||||||
aiPromptLabel: 'Prompt to send to AI',
|
aiPromptLabel: 'Prompt to send to AI',
|
||||||
|
aiCopyPrompt: 'Copy prompt',
|
||||||
|
aiPromptCopied: 'Copied',
|
||||||
|
aiCopyError: 'Copy failed',
|
||||||
aiResponseLabel: 'Paste AI response (JSON)',
|
aiResponseLabel: 'Paste AI response (JSON)',
|
||||||
aiApply: 'Apply AI response',
|
aiApply: 'Apply AI response',
|
||||||
aiApplyError: 'Could not read AI response. Please ensure it is valid JSON with a locales object.',
|
aiApplyError: 'Could not read AI response. Please ensure it is valid JSON with a locales object.',
|
||||||
|
|
@ -287,6 +290,9 @@ const baseMessages = {
|
||||||
aiHelperTitle: 'AI-käännösapu',
|
aiHelperTitle: 'AI-käännösapu',
|
||||||
aiHelperLead: 'Kopioi prompti tekoälylle, käännä puuttuvat kielet ja liitä JSON-vastaus takaisin.',
|
aiHelperLead: 'Kopioi prompti tekoälylle, käännä puuttuvat kielet ja liitä JSON-vastaus takaisin.',
|
||||||
aiPromptLabel: 'Prompti tekoälylle',
|
aiPromptLabel: 'Prompti tekoälylle',
|
||||||
|
aiCopyPrompt: 'Kopioi prompti',
|
||||||
|
aiPromptCopied: 'Kopioitu',
|
||||||
|
aiCopyError: 'Kopiointi epäonnistui',
|
||||||
aiResponseLabel: 'Liitä tekoälyn vastaus (JSON)',
|
aiResponseLabel: 'Liitä tekoälyn vastaus (JSON)',
|
||||||
aiApply: 'Käytä AI-vastausta',
|
aiApply: 'Käytä AI-vastausta',
|
||||||
aiApplyError: 'Vastausta ei voitu lukea. Varmista, että se on kelvollista JSONia ja sisältää locales-avaimen.',
|
aiApplyError: 'Vastausta ei voitu lukea. Varmista, että se on kelvollista JSONia ja sisältää locales-avaimen.',
|
||||||
|
|
@ -552,6 +558,9 @@ const svMessages: Record<keyof typeof baseMessages.en, string> = {
|
||||||
aiHelperTitle: 'AI-översättningshjälp',
|
aiHelperTitle: 'AI-översättningshjälp',
|
||||||
aiHelperLead: 'Kopiera prompten till din AI-assistent, låt den översätta saknade språk och klistra in JSON-svaret här.',
|
aiHelperLead: 'Kopiera prompten till din AI-assistent, låt den översätta saknade språk och klistra in JSON-svaret här.',
|
||||||
aiPromptLabel: 'Prompt till AI',
|
aiPromptLabel: 'Prompt till AI',
|
||||||
|
aiCopyPrompt: 'Kopiera prompt',
|
||||||
|
aiPromptCopied: 'Kopierad',
|
||||||
|
aiCopyError: 'Kopiering misslyckades',
|
||||||
aiResponseLabel: 'Klistra in AI-svar (JSON)',
|
aiResponseLabel: 'Klistra in AI-svar (JSON)',
|
||||||
aiApply: 'Använd AI-svar',
|
aiApply: 'Använd AI-svar',
|
||||||
aiApplyError: 'Kunde inte läsa AI-svaret. Kontrollera att det är giltig JSON med locales-nyckeln.',
|
aiApplyError: 'Kunde inte läsa AI-svaret. Kontrollera att det är giltig JSON med locales-nyckeln.',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue