Changed Localization to JSON String

This commit is contained in:
2026-02-17 10:37:18 +02:00
parent 59c5049305
commit cb2c5c6aba
4 changed files with 59 additions and 20 deletions

View File

@@ -1,5 +1,5 @@
from pydantic import BaseModel, Field
from typing import Dict, List, Optional
from typing import List, Optional
from enum import Enum
@@ -17,8 +17,8 @@ class MelodyTone(str, Enum):
class MelodyInfo(BaseModel):
name: Dict[str, str] = {}
description: Dict[str, str] = {}
name: str = ""
description: str = ""
melodyTone: MelodyTone = MelodyTone.normal
customTags: List[str] = []
minSpeed: int = 0

View File

@@ -1,3 +1,5 @@
import json
from shared.firebase import get_db, get_bucket
from shared.exceptions import NotFoundError
from melodies.models import MelodyCreate, MelodyUpdate, MelodyInDB
@@ -5,16 +7,22 @@ from melodies.models import MelodyCreate, MelodyUpdate, MelodyInDB
COLLECTION = "melodies"
def _parse_localized_string(value: str) -> dict:
"""Parse a JSON-encoded localized string into a dict. Returns {} on failure."""
if not value:
return {}
try:
parsed = json.loads(value)
if isinstance(parsed, dict):
return parsed
except (json.JSONDecodeError, TypeError):
pass
return {}
def _doc_to_melody(doc) -> MelodyInDB:
"""Convert a Firestore document snapshot to a MelodyInDB model."""
data = doc.to_dict()
# Backward compat: if name/description are plain strings, wrap as dict
info = data.get("information", {})
if isinstance(info.get("name"), str):
info["name"] = {"en": info["name"]} if info["name"] else {}
if isinstance(info.get("description"), str):
info["description"] = {"en": info["description"]} if info["description"] else {}
data["information"] = info
return MelodyInDB(id=doc.id, **data)
@@ -48,14 +56,16 @@ def list_melodies(
continue
if search:
search_lower = search.lower()
name_dict = _parse_localized_string(melody.information.name)
desc_dict = _parse_localized_string(melody.information.description)
name_match = any(
search_lower in v.lower()
for v in melody.information.name.values()
for v in name_dict.values()
if isinstance(v, str)
)
desc_match = any(
search_lower in v.lower()
for v in melody.information.description.values()
for v in desc_dict.values()
if isinstance(v, str)
)
tag_match = any(search_lower in t.lower() for t in melody.information.customTags)

View File

@@ -7,14 +7,16 @@ import {
getLanguageName,
normalizeColor,
formatDuration,
parseLocalizedString,
serializeLocalizedString,
} from "./melodyUtils";
const MELODY_TYPES = ["orthodox", "catholic", "all"];
const MELODY_TONES = ["normal", "festive", "cheerful", "lamentation"];
const defaultInfo = {
name: {},
description: {},
name: "",
description: "",
melodyTone: "normal",
customTags: [],
minSpeed: 0,
@@ -155,7 +157,9 @@ export default function MelodyForm() {
// Update a localized field for the current edit language
const updateLocalizedField = (fieldKey, text) => {
updateInfo(fieldKey, { ...information[fieldKey], [editLang]: text });
const dict = parseLocalizedString(information[fieldKey]);
dict[editLang] = text;
updateInfo(fieldKey, serializeLocalizedString(dict));
};
const handleSubmit = async (e) => {
@@ -836,8 +840,8 @@ export default function MelodyForm() {
setTranslationModal((prev) => ({ ...prev, open: false }))
}
field={translationModal.field}
value={information[translationModal.fieldKey] || {}}
onChange={(updated) => updateInfo(translationModal.fieldKey, updated)}
value={parseLocalizedString(information[translationModal.fieldKey])}
onChange={(updated) => updateInfo(translationModal.fieldKey, serializeLocalizedString(updated))}
languages={languages}
multiline={translationModal.multiline}
/>

View File

@@ -10,11 +10,36 @@ export function formatDuration(seconds) {
}
/**
* Get a localized value from a dict, with fallback chain:
* Parse a JSON-encoded localized string into a dict.
* If already a dict, returns it as-is. Returns {} on failure.
*/
export function parseLocalizedString(value) {
if (!value) return {};
if (typeof value === "object") return value;
try {
const parsed = JSON.parse(value);
if (typeof parsed === "object" && parsed !== null) return parsed;
} catch {
// Not valid JSON — treat as plain string
return { en: value };
}
return {};
}
/**
* Serialize a localized dict back to a JSON string for storage.
*/
export function serializeLocalizedString(dict) {
if (!dict || typeof dict !== "object") return "";
return JSON.stringify(dict);
}
/**
* Get a localized value from a JSON string or dict, with fallback chain:
* selected lang → "en" → first available value → fallback
*/
export function getLocalizedValue(dict, lang, fallback = "") {
if (!dict || typeof dict !== "object") return fallback;
export function getLocalizedValue(value, lang, fallback = "") {
const dict = parseLocalizedString(value);
return dict[lang] || dict["en"] || Object.values(dict)[0] || fallback;
}