locale upgrade

This commit is contained in:
2026-01-22 20:56:35 +01:00
parent 377631ee50
commit 37a1bc4e18
28 changed files with 2117 additions and 71 deletions

View File

@@ -60,7 +60,7 @@ function EditorPageContent() {
const [isSaving, setIsSaving] = useState(false);
const [isCreating, setIsCreating] = useState(!projectId);
const [editLocale, setEditLocale] = useState(initialLocale);
const [baseTexts, setBaseTexts] = useState<{ title: string; description: string } | null>(null);
const [baseTexts, setBaseTexts] = useState<{ title: string; description: string; content: string } | null>(null);
const [showPreview, setShowPreview] = useState(false);
const [_isTyping, setIsTyping] = useState(false);
const [history, setHistory] = useState<typeof formData[]>([]);
@@ -96,6 +96,7 @@ function EditorPageContent() {
setBaseTexts({
title: foundProject.title || "",
description: foundProject.description || "",
content: foundProject.content || "",
});
const initialData = {
title: foundProject.title || "",
@@ -145,19 +146,64 @@ function EditorPageContent() {
});
if (!response.ok) return;
const data = await response.json();
const tr = data.translation as { title?: string; description?: string } | null;
if (tr?.title && tr?.description) {
setFormData((prev) => ({
...prev,
title: tr.title || prev.title,
description: tr.description || prev.description,
}));
const tr = data.translation as { title?: string; description?: string; content?: unknown } | null;
const translatedContent = (() => {
if (typeof tr?.content === "string") return tr.content;
if (tr?.content && typeof tr.content === "object" && "markdown" in tr.content) {
const markdown = (tr.content as Record<string, unknown>).markdown;
if (typeof markdown === "string") return markdown;
}
return null;
})();
if (tr?.title || tr?.description || translatedContent !== null) {
setFormData((prev) => {
const next = {
...prev,
title: tr?.title || prev.title,
description: tr?.description || prev.description,
content: translatedContent ?? prev.content,
};
return next;
});
if (translatedContent !== null) {
shouldUpdateContentRef.current = true;
}
}
} catch {
// ignore translation load failures
}
}, []);
const switchLocale = useCallback(
(next: string) => {
setEditLocale(next);
if (projectId) {
const newUrl = `/editor?id=${projectId}&locale=${encodeURIComponent(next)}`;
window.history.replaceState({}, "", newUrl);
}
if (next === "en" && baseTexts) {
setFormData((prev) => {
const nextData = {
...prev,
title: baseTexts.title,
description: baseTexts.description,
content: baseTexts.content,
};
return nextData;
});
shouldUpdateContentRef.current = true;
return;
}
if (projectId) {
loadTranslation(projectId, next);
}
},
[projectId, baseTexts, loadTranslation],
);
// Check authentication and load project
useEffect(() => {
const init = async () => {
@@ -188,6 +234,7 @@ function EditorPageContent() {
live: "",
image: "",
};
setBaseTexts({ title: "", description: "", content: "" });
setFormData(initialData);
setOriginalFormData(initialData);
setHistory([initialData]);
@@ -240,11 +287,12 @@ function EditorPageContent() {
const saveTitle = editLocale === "en" ? formData.title.trim() : (baseTexts?.title || formData.title.trim());
const saveDescription =
editLocale === "en" ? formData.description.trim() : (baseTexts?.description || formData.description.trim());
const saveContent = editLocale === "en" ? formData.content.trim() : (baseTexts?.content || formData.content.trim());
const saveData = {
title: saveTitle,
description: saveDescription,
content: formData.content.trim(),
content: saveContent,
category: formData.category,
tags: formData.tags,
github: formData.github.trim() || null,
@@ -302,12 +350,21 @@ function EditorPageContent() {
locale: editLocale,
title: formData.title.trim(),
description: formData.description.trim(),
content: formData.content.trim(),
}),
});
} catch {
// ignore translation save failures
}
}
if (editLocale === "en") {
setBaseTexts({
title: savedProject.title || "",
description: savedProject.description || "",
content: savedProject.content || "",
});
}
// Update project ID if it was a new project
if (!projectId && savedProject.id) {
@@ -706,27 +763,40 @@ function EditorPageContent() {
<label className="block text-sm font-medium text-stone-300 mb-2">
Language
</label>
<div className="custom-select">
<select
value={editLocale}
onChange={(e) => {
const next = e.target.value;
setEditLocale(next);
if (projectId) {
// Update URL for deep-linking and reload translation
const newUrl = `/editor?id=${projectId}&locale=${encodeURIComponent(next)}`;
window.history.replaceState({}, "", newUrl);
loadTranslation(projectId, next);
}
}}
>
<option value="en">English (default)</option>
<option value="de">Deutsch</option>
</select>
<div className="flex items-center gap-2">
<div className="custom-select">
<select
value={editLocale}
onChange={(e) => switchLocale(e.target.value)}
>
<option value="en">English (default)</option>
<option value="de">Deutsch</option>
</select>
</div>
<div className="inline-flex rounded-lg overflow-hidden border border-stone-700/40">
<button
type="button"
onClick={() => switchLocale("en")}
className={`px-3 py-1 text-sm ${
editLocale === "en" ? "bg-stone-700 text-white" : "bg-stone-800 text-stone-300 hover:bg-stone-700"
}`}
>
EN
</button>
<button
type="button"
onClick={() => switchLocale("de")}
className={`px-3 py-1 text-sm ${
editLocale === "de" ? "bg-stone-700 text-white" : "bg-stone-800 text-stone-300 hover:bg-stone-700"
}`}
>
DE
</button>
</div>
</div>
{editLocale !== "en" && (
<p className="text-xs text-stone-400 mt-2">
Title/description are saved as a translation. Other fields are global.
Title, description, and content are saved as a translation. Other fields are global.
</p>
)}
</div>