Changed Localization to JSON String
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user