jean-web/jweb/context.py

98 lines
3.9 KiB
Python

import gettext
from contextlib import contextmanager
from gettext import GNUTranslations, NullTranslations
from importlib import import_module
from pathlib import Path
from typing import Any, Iterator
from jinja2 import pass_context
from jinja2.environment import Environment
from jinja2.loaders import FileSystemLoader
from jinja2.runtime import Context as JinjaContext
from jweb.content import Content
_DEFAULT_LANGUAGE = "en"
class Context:
def __init__(self, root_directory: Path) -> None:
self.__root_directory = root_directory
self.__output_directory = root_directory / "build"
self.__content_directory = root_directory / "content"
self.__environment = Environment(
loader=FileSystemLoader(searchpath=root_directory / "src"),
)
self.__translations: dict[str, GNUTranslations | NullTranslations] = {}
self.__current_language: str | None = None
self.add_filters(load=self.__load, write=self.__write)
@property
def current_language(self) -> str | None:
return self.__current_language
def add_filters(self, **filters: Any) -> None:
self.__environment.filters.update(**filters)
def load_extensions(self, *extensions: str) -> None:
for extension in extensions:
module = import_module(extension)
module.load_extension(self)
def load_translations(self, domain: str, locale_dir: str | Path, *languages: str) -> None:
locale_dir = str(locale_dir)
self.__environment.add_extension("jinja2.ext.i18n")
self.__translations[_DEFAULT_LANGUAGE] = NullTranslations()
for language in languages:
self.__translations[language] = gettext.translation(
domain, localedir=str(locale_dir), languages=[language]
)
@contextmanager
def set_language(self, language: str) -> Iterator[None]:
translation = self.__translations[language]
old_language = self.__current_language
self.__environment.install_gettext_translations(translation, newstyle=True) # type: ignore
self.__current_language = language
yield
self.__current_language = old_language
self.__environment.uninstall_gettext_translations(translation) # type: ignore
def render(self, source: str, output: str | Path, **context: Any) -> None:
if self.__translations:
for language in self.__translations:
with self.set_language(language):
self.__render(source, self.__output_directory / language / output, **context)
else:
self.__render(source, self.__output_directory / output, **context)
@pass_context
def __load(self, context: JinjaContext, path: str | Path) -> Content:
current_language = self.current_language
if current_language:
localized_path = self.__content_directory / current_language / path
if not localized_path.exists():
localized_path = self.__content_directory / _DEFAULT_LANGUAGE / path
else:
localized_path = self.__content_directory / path
with localized_path.open("r", encoding="utf-8") as content_file:
return Content(localized_path, content_file.read(), current_language)
def __write(self, content: Content) -> Path:
relative_path = content.path.relative_to(self.__content_directory)
output_path = self.__output_directory / relative_path
with output_path.open("w") as output_file:
output_file.write(content.data)
return Path(f"/{relative_path}")
def __render(self, source: str, output_path: Path, **context: Any) -> None:
output_path.parent.mkdir(exist_ok=True, parents=True)
template = self.__environment.get_template(source)
content = template.render(**context)
with open(output_path, "w") as output_file:
output_file.write(content)