mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Flag flyer language in Nostr events
This commit is contained in:
1 parent
5afda54a21
commit
1bda451604
4 files changed
+63
-2
No files matched your search
@@ -61,6 +61,14 @@ Required tags:
|
|||||||
- `["t", "voxvera"]`
|
- `["t", "voxvera"]`
|
||||||
- `["t", "flyer"]`
|
- `["t", "flyer"]`
|
||||||
|
|
||||||
|
Recommended language tags:
|
||||||
|
|
||||||
|
- `["language", "<lang>"]`
|
||||||
|
- `["L", "ISO-639-1"]`
|
||||||
|
- `["l", "<lang>", "ISO-639-1"]`
|
||||||
|
|
||||||
|
The payload `lang` field is authoritative. The language tags exist for relay/search metadata and as a fallback for older events whose payload omitted `lang`.
|
||||||
|
|
||||||
Payload:
|
Payload:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -118,6 +126,9 @@ Nostr editor/viewer controls and Nostr-specific flyer labels come from `NOSTR_UI
|
|||||||
|
|
||||||
Language changes should:
|
Language changes should:
|
||||||
|
|
||||||
|
- publish the selected flyer language in payload `lang`
|
||||||
|
- publish matching Nostr language tags
|
||||||
|
- infer language from event tags if a fetched payload omits `lang`
|
||||||
- update default flyer content while the flyer is still using default content
|
- update default flyer content while the flyer is still using default content
|
||||||
- preserve user-authored or fetched event content
|
- preserve user-authored or fetched event content
|
||||||
- update editor/viewer tool labels
|
- update editor/viewer tool labels
|
||||||
|
|||||||
@@ -751,6 +751,7 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
|||||||
|
|
||||||
function buildUnsignedEvent(payload) {
|
function buildUnsignedEvent(payload) {
|
||||||
const folderName = slugify(payload.folder_name);
|
const folderName = slugify(payload.folder_name);
|
||||||
|
const lang = supportedLang(payload.lang || FALLBACK_LANG);
|
||||||
return {
|
return {
|
||||||
kind: EVENT_KIND,
|
kind: EVENT_KIND,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
@@ -759,7 +760,9 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
|||||||
["t", "voxvera"],
|
["t", "voxvera"],
|
||||||
["t", "flyer"],
|
["t", "flyer"],
|
||||||
["title", payload.headline || payload.title || folderName],
|
["title", payload.headline || payload.title || folderName],
|
||||||
["language", payload.lang || "en"]
|
["language", lang],
|
||||||
|
["L", "ISO-639-1"],
|
||||||
|
["l", lang, "ISO-639-1"]
|
||||||
],
|
],
|
||||||
content: JSON.stringify(payload)
|
content: JSON.stringify(payload)
|
||||||
};
|
};
|
||||||
@@ -1011,10 +1014,21 @@ Join us in a revolution that values truth and transparency. Together, we can bui
|
|||||||
const hasTag = (name, value) => Array.isArray(event.tags) && event.tags.some((tag) => tag[0] === name && tag[1] === value);
|
const hasTag = (name, value) => Array.isArray(event.tags) && event.tags.some((tag) => tag[0] === name && tag[1] === value);
|
||||||
if (!hasTag("t", "voxvera") || !hasTag("t", "flyer")) throw new Error("Event is missing VoxVera flyer tags.");
|
if (!hasTag("t", "voxvera") || !hasTag("t", "flyer")) throw new Error("Event is missing VoxVera flyer tags.");
|
||||||
const payload = JSON.parse(event.content);
|
const payload = JSON.parse(event.content);
|
||||||
|
const taggedLang = languageFromTags(event.tags);
|
||||||
|
if (!payload.lang && taggedLang) {
|
||||||
|
payload.lang = taggedLang;
|
||||||
|
}
|
||||||
validatePayload(payload);
|
validatePayload(payload);
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function languageFromTags(tags) {
|
||||||
|
if (!Array.isArray(tags)) return "";
|
||||||
|
const languageTag = tags.find((tag) => Array.isArray(tag) && tag[0] === "language" && tag[1]);
|
||||||
|
const labelTag = tags.find((tag) => Array.isArray(tag) && tag[0] === "l" && tag[1]);
|
||||||
|
return supportedLang((languageTag && languageTag[1]) || (labelTag && labelTag[1]) || "");
|
||||||
|
}
|
||||||
|
|
||||||
function makeQrSvg(value) {
|
function makeQrSvg(value) {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
if (typeof qrcode !== "function") {
|
if (typeof qrcode !== "function") {
|
||||||
|
|||||||
@@ -29,7 +29,14 @@ def _event(payload=None, **overrides):
|
|||||||
event = {
|
event = {
|
||||||
"kind": 30078,
|
"kind": 30078,
|
||||||
"content": json.dumps(payload or _payload()),
|
"content": json.dumps(payload or _payload()),
|
||||||
"tags": [["d", "voxvera:nostr-flyer"], ["t", "voxvera"], ["t", "flyer"]],
|
"tags": [
|
||||||
|
["d", "voxvera:nostr-flyer"],
|
||||||
|
["t", "voxvera"],
|
||||||
|
["t", "flyer"],
|
||||||
|
["language", "en"],
|
||||||
|
["L", "ISO-639-1"],
|
||||||
|
["l", "en", "ISO-639-1"],
|
||||||
|
],
|
||||||
}
|
}
|
||||||
event.update(overrides)
|
event.update(overrides)
|
||||||
return event
|
return event
|
||||||
@@ -43,6 +50,21 @@ def test_validate_accepts_nostr_event_payload():
|
|||||||
assert validate_event_source(_event())["content"] == "This flyer came from a Nostr event."
|
assert validate_event_source(_event())["content"] == "This flyer came from a Nostr event."
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_uses_language_tag_when_payload_lang_is_missing():
|
||||||
|
payload = _payload()
|
||||||
|
payload.pop("lang")
|
||||||
|
event = _event(payload, tags=[
|
||||||
|
["d", "voxvera:nostr-flyer"],
|
||||||
|
["t", "voxvera"],
|
||||||
|
["t", "flyer"],
|
||||||
|
["language", "es"],
|
||||||
|
["L", "ISO-639-1"],
|
||||||
|
["l", "es", "ISO-639-1"],
|
||||||
|
])
|
||||||
|
|
||||||
|
assert validate_event_source(event)["lang"] == "es"
|
||||||
|
|
||||||
|
|
||||||
def test_normalize_maps_to_flyer_config_defaults():
|
def test_normalize_maps_to_flyer_config_defaults():
|
||||||
defaults = {
|
defaults = {
|
||||||
"folder_name": "voxvera",
|
"folder_name": "voxvera",
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ def _extract_payload(source: dict[str, Any]) -> dict[str, Any]:
|
|||||||
raise NostrValidationError(f"Nostr event content is not valid JSON: {exc.msg}") from exc
|
raise NostrValidationError(f"Nostr event content is not valid JSON: {exc.msg}") from exc
|
||||||
if not isinstance(payload, dict):
|
if not isinstance(payload, dict):
|
||||||
raise NostrValidationError("Nostr event content must decode to a JSON object.")
|
raise NostrValidationError("Nostr event content must decode to a JSON object.")
|
||||||
|
if not payload.get("lang"):
|
||||||
|
tagged_lang = _language_from_tags(source.get("tags", []))
|
||||||
|
if tagged_lang:
|
||||||
|
payload["lang"] = tagged_lang
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
@@ -182,6 +186,16 @@ def _has_tag(source: dict[str, Any], name: str, value: str) -> bool:
|
|||||||
return any(isinstance(tag, list) and len(tag) >= 2 and tag[0] == name and tag[1] == value for tag in tags)
|
return any(isinstance(tag, list) and len(tag) >= 2 and tag[0] == name and tag[1] == value for tag in tags)
|
||||||
|
|
||||||
|
|
||||||
|
def _language_from_tags(tags: Any) -> str:
|
||||||
|
if not isinstance(tags, list):
|
||||||
|
return ""
|
||||||
|
for tag_name in ("language", "l"):
|
||||||
|
for tag in tags:
|
||||||
|
if isinstance(tag, list) and len(tag) >= 2 and tag[0] == tag_name:
|
||||||
|
return _normalize_lang(str(tag[1]))
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _normalize_lang(lang: str) -> str:
|
def _normalize_lang(lang: str) -> str:
|
||||||
return lang if lang in SUPPORTED_LANGS else "en"
|
return lang if lang in SUPPORTED_LANGS else "en"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user