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

View File

@@ -1,3 +1,5 @@
import json
from shared.firebase import get_db, get_bucket from shared.firebase import get_db, get_bucket
from shared.exceptions import NotFoundError from shared.exceptions import NotFoundError
from melodies.models import MelodyCreate, MelodyUpdate, MelodyInDB from melodies.models import MelodyCreate, MelodyUpdate, MelodyInDB
@@ -5,16 +7,22 @@ from melodies.models import MelodyCreate, MelodyUpdate, MelodyInDB
COLLECTION = "melodies" 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: def _doc_to_melody(doc) -> MelodyInDB:
"""Convert a Firestore document snapshot to a MelodyInDB model.""" """Convert a Firestore document snapshot to a MelodyInDB model."""
data = doc.to_dict() 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) return MelodyInDB(id=doc.id, **data)
@@ -48,14 +56,16 @@ def list_melodies(
continue continue
if search: if search:
search_lower = search.lower() search_lower = search.lower()
name_dict = _parse_localized_string(melody.information.name)
desc_dict = _parse_localized_string(melody.information.description)
name_match = any( name_match = any(
search_lower in v.lower() search_lower in v.lower()
for v in melody.information.name.values() for v in name_dict.values()
if isinstance(v, str) if isinstance(v, str)
) )
desc_match = any( desc_match = any(
search_lower in v.lower() search_lower in v.lower()
for v in melody.information.description.values() for v in desc_dict.values()
if isinstance(v, str) if isinstance(v, str)
) )
tag_match = any(search_lower in t.lower() for t in melody.information.customTags) tag_match = any(search_lower in t.lower() for t in melody.information.customTags)

View File

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