Files
TimeTracker/app/utils/i18n.py
T
Dries Peeters 69f9f1140d feat(i18n): add translations, locale switcher, and user language preference
- Integrate Flask-Babel and i18n utilities; initialize in app factory
- Add `preferred_language` to `User` with Alembic migration (011_add_user_preferred_language)
- Add `babel.cfg` and `scripts/extract_translations.py`
- Add `translations/` for en, de, fr, it, nl, fi
- Update templates to use `_()` and add language picker in navbar/profile
- Respect locale in routes and context processors; persist user preference
- Update requirements and Docker/Docker entrypoint for Babel/gettext support
- Minor copy and style adjustments across pages

Migration: run `alembic upgrade head`
2025-09-11 23:08:41 +02:00

61 lines
1.9 KiB
Python

import os
import io
import time
from typing import Optional
def _needs_compile(po_path: str, mo_path: str) -> bool:
try:
if not os.path.exists(mo_path):
return True
return os.path.getmtime(po_path) > os.path.getmtime(mo_path)
except Exception:
return True
def compile_po_to_mo(po_path: str, mo_path: str) -> bool:
"""Compile a .po file to .mo using Babel's message tools if available.
Returns True on success, False otherwise.
"""
try:
from babel.messages.pofile import read_po
from babel.messages.mofile import write_mo
with open(po_path, 'r', encoding='utf-8') as po_file:
catalog = read_po(po_file)
os.makedirs(os.path.dirname(mo_path), exist_ok=True)
with open(mo_path, 'wb') as mo_file:
write_mo(mo_file, catalog)
return True
except Exception:
return False
def ensure_translations_compiled(translations_dir: str) -> None:
"""Compile all .po catalogs under translations_dir if missing/stale.
Structure expected: translations/<lang>/LC_MESSAGES/messages.po
"""
try:
if not translations_dir:
return
if not os.path.isabs(translations_dir):
# Resolve relative to current working directory
translations_dir = os.path.abspath(translations_dir)
if not os.path.isdir(translations_dir):
return
for lang in os.listdir(translations_dir):
lang_dir = os.path.join(translations_dir, lang, 'LC_MESSAGES')
if not os.path.isdir(lang_dir):
continue
po_path = os.path.join(lang_dir, 'messages.po')
mo_path = os.path.join(lang_dir, 'messages.mo')
if os.path.exists(po_path) and _needs_compile(po_path, mo_path):
compile_po_to_mo(po_path, mo_path)
except Exception:
# Non-fatal; i18n will fall back to msgid if mo missing
pass