Move default templates to .jinja2 files + add live preview + update CLAUDE.md
Some checks failed
Validate / Hassfest (push) Has been cancelled

Templates:
- Default EN/RU templates moved from inline Python strings to
  14 .jinja2 files in templates/{en,ru}/ directory
- Properly formatted with readable indentation and Jinja2
  whitespace control ({%- -%})
- load_default_templates(locale) loads from files on first access
- Seed function uses file loader instead of inline dicts

Preview:
- New POST /api/template-configs/preview-raw endpoint: renders
  arbitrary Jinja2 text with sample data (for live editing preview)
- Route ordering fixed: /variables before /{config_id}

CLAUDE.md:
- Added Frontend Architecture Notes (i18n, Svelte 5 runes, auth
  flow, Tailwind v4 quirks)
- Added Backend Architecture Notes (SQLAlchemy+aiohttp greenlet
  issue, SandboxedEnvironment import, system-owned entities,
  FastAPI route ordering, __pycache__)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 19:12:46 +03:00
parent aab29e253f
commit 5870ebd216
18 changed files with 214 additions and 55 deletions

View File

@@ -141,20 +141,70 @@ class TemplateConfig(SQLModel, table=True):
created_at: datetime = Field(default_factory=_utcnow)
# --- Default template content (EN) ---
# --- Default template loading from .jinja2 files ---
DEFAULT_TEMPLATE_EN = {
"message_assets_added": """📷 {{ added_count }} new photo(s) added to album "{{ album_name }}".\
{% if people %}
👤 {{ people | join(", ") }}{% endif %}\
{% if added_assets %}
{% for asset in added_assets %}\
{% if asset.type == "VIDEO" %}🎬{% else %}🖼️{% endif %} {{ asset.filename }}\
{% if asset.city %} 📍 {{ asset.city }}{% if asset.country %}, {{ asset.country }}{% endif %}{% endif %}\
{% if asset.is_favorite %} ❤️{% endif %}
{% endfor %}\
{% endif %}\
{{ video_warning }}""",
from pathlib import Path as _Path
_TEMPLATES_DIR = _Path(__file__).parent.parent / "templates"
_SLOT_TO_FILE = {
"message_assets_added": "assets_added.jinja2",
"message_assets_removed": "assets_removed.jinja2",
"message_album_renamed": "album_renamed.jinja2",
"message_album_deleted": "album_deleted.jinja2",
"periodic_summary_message": "periodic_summary.jinja2",
"scheduled_assets_message": "scheduled_assets.jinja2",
"memory_mode_message": "memory_mode.jinja2",
}
def load_default_templates(locale: str) -> dict[str, str]:
"""Load default template files for a locale (en/ru)."""
result = {}
locale_dir = _TEMPLATES_DIR / locale
for slot, filename in _SLOT_TO_FILE.items():
path = locale_dir / filename
if path.exists():
result[slot] = path.read_text(encoding="utf-8").strip()
else:
result[slot] = ""
return result
# Lazy-loaded defaults (loaded from files on first access)
DEFAULT_TEMPLATE_EN: dict[str, str] = {}
DEFAULT_TEMPLATE_RU: dict[str, str] = {}
def get_default_templates(locale: str) -> dict[str, str]:
"""Get default templates, loading from files on first call."""
global DEFAULT_TEMPLATE_EN, DEFAULT_TEMPLATE_RU
if locale == "ru":
if not DEFAULT_TEMPLATE_RU:
DEFAULT_TEMPLATE_RU = load_default_templates("ru")
return DEFAULT_TEMPLATE_RU
if not DEFAULT_TEMPLATE_EN:
DEFAULT_TEMPLATE_EN = load_default_templates("en")
return DEFAULT_TEMPLATE_EN
_INLINE_TEMPLATES_REMOVED = {
"message_assets_added": (
'📷 {{ added_count }} new photo(s) added to album "{{ album_name }}".\n'
'{%- if people %}\n'
'👤 {{ people | join(", ") }}\n'
'{%- endif %}'
'{%- if added_assets %}\n'
'{%- for asset in added_assets %}\n'
'{%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {{ asset.filename }}'
'{%- if asset.city %} 📍 {{ asset.city }}'
'{%- if asset.country %}, {{ asset.country }}{% endif %}'
'{%- endif %}'
'{%- if asset.is_favorite %} ❤️{% endif %}\n'
'{%- endfor %}'
'{%- endif %}'
'{{ video_warning }}'
),
"message_assets_removed": '🗑️ {{ removed_count }} photo(s) removed from album "{{ album_name }}".',
@@ -162,36 +212,49 @@ DEFAULT_TEMPLATE_EN = {
"message_album_deleted": '🗑️ Album "{{ album_name }}" was deleted.',
"periodic_summary_message": """📋 Tracked Albums Summary ({{ albums | length }} albums):\
{% for album in albums %}
{{ album.name }}: {{ album.asset_count }} assets{% if album.url %} — {{ album.url }}{% endif %}\
{% endfor %}""",
"periodic_summary_message": (
'📋 Tracked Albums Summary ({{ albums | length }} albums):\n'
'{%- for album in albums %}\n'
'{{ album.name }}: {{ album.asset_count }} assets'
'{%- if album.url %} — {{ album.url }}{% endif %}\n'
'{%- endfor %}'
),
"scheduled_assets_message": """📸 Photos from "{{ album_name }}":\
{% for asset in assets %}
{% if asset.type == "VIDEO" %}🎬{% else %}🖼️{% endif %} {{ asset.filename }}\
{% endfor %}""",
"scheduled_assets_message": (
'📸 Photos from "{{ album_name }}":\n'
'{%- for asset in assets %}\n'
'{%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {{ asset.filename }}\n'
'{%- endfor %}'
),
"memory_mode_message": """📅 On this day:\
{% for asset in assets %}
{% if asset.type == "VIDEO" %}🎬{% else %}🖼️{% endif %} {{ asset.filename }} ({{ asset.created_at[:4] }})\
{% endfor %}""",
"memory_mode_message": (
'📅 On this day:\n'
'{%- for asset in assets %}\n'
'{%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {{ asset.filename }}'
' ({{ asset.created_at[:4] }})\n'
'{%- endfor %}'
),
}
# --- Default template content (RU) ---
DEFAULT_TEMPLATE_RU = {
"message_assets_added": """📷 {{ added_count }} новых фото добавлено в альбом "{{ album_name }}".\
{% if people %}
👤 {{ people | join(", ") }}{% endif %}\
{% if added_assets %}
{% for asset in added_assets %}\
{% if asset.type == "VIDEO" %}🎬{% else %}🖼️{% endif %} {{ asset.filename }}\
{% if asset.city %} 📍 {{ asset.city }}{% if asset.country %}, {{ asset.country }}{% endif %}{% endif %}\
{% if asset.is_favorite %} {% endif %}
{% endfor %}\
{% endif %}\
{{ video_warning }}""",
"message_assets_added": (
'📷 {{ added_count }} новых фото добавлено в альбом "{{ album_name }}".\n'
'{%- if people %}\n'
'👤 {{ people | join(", ") }}\n'
'{%- endif %}'
'{%- if added_assets %}\n'
'{%- for asset in added_assets %}\n'
'{%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼{% endif %} {{ asset.filename }}'
'{%- if asset.city %} 📍 {{ asset.city }}'
'{%- if asset.country %}, {{ asset.country }}{% endif %}'
'{%- endif %}'
'{%- if asset.is_favorite %} ❤️{% endif %}\n'
'{%- endfor %}'
'{%- endif %}'
'{{ video_warning }}'
),
"message_assets_removed": '🗑️ {{ removed_count }} фото удалено из альбома "{{ album_name }}".',
@@ -199,20 +262,28 @@ DEFAULT_TEMPLATE_RU = {
"message_album_deleted": '🗑️ Альбом "{{ album_name }}" был удалён.',
"periodic_summary_message": """📋 Сводка альбомов ({{ albums | length }}):\
{% for album in albums %}
{{ album.name }}: {{ album.asset_count }} файлов{% if album.url %} — {{ album.url }}{% endif %}\
{% endfor %}""",
"periodic_summary_message": (
'📋 Сводка альбомов ({{ albums | length }}):\n'
'{%- for album in albums %}\n'
'{{ album.name }}: {{ album.asset_count }} файлов'
'{%- if album.url %} — {{ album.url }}{% endif %}\n'
'{%- endfor %}'
),
"scheduled_assets_message": """📸 Фото из "{{ album_name }}":\
{% for asset in assets %}
{% if asset.type == "VIDEO" %}🎬{% else %}🖼️{% endif %} {{ asset.filename }}\
{% endfor %}""",
"scheduled_assets_message": (
'📸 Фото из "{{ album_name }}":\n'
'{%- for asset in assets %}\n'
'{%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {{ asset.filename }}\n'
'{%- endfor %}'
),
"memory_mode_message": """📅 В этот день:\
{% for asset in assets %}
{% if asset.type == "VIDEO" %}🎬{% else %}🖼️{% endif %} {{ asset.filename }} ({{ asset.created_at[:4] }})\
{% endfor %}""",
"memory_mode_message": (
'📅 В этот день:\n'
'{%- for asset in assets %}\n'
'{%- if asset.type == "VIDEO" %} 🎬{% else %} 🖼️{% endif %} {{ asset.filename }}'
' ({{ asset.created_at[:4] }})\n'
'{%- endfor %}'
),
}