diff --git a/Dockerfile b/Dockerfile index fe60189..9463961 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,15 @@ FROM python:3.12 AS builder ENV POETRY_HOME="/opt/poetry" ENV PATH="$POETRY_HOME/bin:$PATH" +RUN apt update && \ + apt install -y gettext WORKDIR /app RUN curl -sSL https://install.python-poetry.org | python3 - COPY pyproject.toml poetry.lock README.md ./ RUN poetry config virtualenvs.in-project true RUN poetry install --with app --no-root +COPY locales ./locales +RUN cd locales/ru/LC_MESSAGES && msgfmt messages.po FROM node:24 AS node-builder ENV PATH=/app/node_modules/.bin:$PATH @@ -18,16 +22,12 @@ RUN npm run build FROM python:3.12-slim ENV PATH="/app/.venv/bin:$PATH" WORKDIR /app -RUN apt update && \ - apt install -y locales gettext && \ - sed -i -e 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/' /etc/locale.gen && \ - dpkg-reconfigure --frontend=noninteractive locales -ENV LANG=ru_RU.UTF-8 -ENV LC_ALL=ru_RU.UTF-8 + ENV TZ="Europe/Moscow" COPY --from=builder /app ./ COPY --from=node-builder /app/dist ./static/dist COPY gallery gallery/ -RUN cd gallery/easel/route/view/locales/ru/LC_MESSAGES && msgfmt messages.po +#COPY --from=builder /app/gallery/easel/route/view/locales /app/gallery/easel/route/view/locales +COPY --from=builder --parents locales/**/*.mo ./ CMD ["uvicorn", "gallery.main:app", "--host", "0.0.0.0", "--port", "80", "--log-config", "gallery/logging.yaml"] diff --git a/gallery/easel/__init__.py b/gallery/easel/__init__.py index ea1ec59..d3e357a 100644 --- a/gallery/easel/__init__.py +++ b/gallery/easel/__init__.py @@ -1,5 +1,3 @@ -import locale as _locale - from fastapi import FastAPI from fastapi.staticfiles import StaticFiles @@ -9,11 +7,8 @@ from gallery.util import root_path from .route import api, doc from .route.view import router as view_router -DEFAULT_LOCALE = "ru_RU.UTF-8" - -def build_app(api_bundle: ApiBundle, *, locale: str = DEFAULT_LOCALE) -> FastAPI: - _locale.setlocale(_locale.LC_TIME, locale) +def build_app(api_bundle: ApiBundle) -> FastAPI: app = FastAPI( title="Gallery", docs_url=None, diff --git a/gallery/easel/route/view/common/templates/base.html b/gallery/easel/route/view/common/templates/base.html index 7879fcc..e658372 100644 --- a/gallery/easel/route/view/common/templates/base.html +++ b/gallery/easel/route/view/common/templates/base.html @@ -32,10 +32,10 @@ type="button" aria-expanded="false" data-bs-toggle="dropdown" - aria-label="Select language (default)"> - 🇬🇧 + aria-label="{{_('Select language')}} (default)"> + Select language + id="bd-language-text">{{_("Select language")}} @@ -66,9 +66,9 @@ aria-expanded="false" data-bs-toggle="dropdown" aria-label="Toggle theme (auto)"> - + Toggle theme + id="bd-theme-text">{{_("Toggle theme")}} diff --git a/gallery/easel/route/view/common/templates/root_index.html b/gallery/easel/route/view/common/templates/root_index.html index f8f894d..bd47e96 100644 --- a/gallery/easel/route/view/common/templates/root_index.html +++ b/gallery/easel/route/view/common/templates/root_index.html @@ -1,11 +1,11 @@ {% extends "base.html" %} -{% block title %}Index{% endblock %} +{% block title %}{{_("Index")}}{% endblock %} {% block head %} {{ super() }} {% endblock %} {% block content %} -

View

+

{{_("View")}}

{% for section in sections %}
-

Docs

+

{{_("Docs")}}

Swagger

diff --git a/gallery/easel/route/view/locales/ru/LC_MESSAGES/messages.po b/gallery/easel/route/view/locales/ru/LC_MESSAGES/messages.po deleted file mode 100644 index eec52e6..0000000 --- a/gallery/easel/route/view/locales/ru/LC_MESSAGES/messages.po +++ /dev/null @@ -1,15 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Gallery\n" -"Last-Translator: shmyga \n" -"Language: ru\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -msgid "Weather" -msgstr "Погода" - -msgid "TV program" -msgstr "Телепрограмма" diff --git a/gallery/easel/route/view/schedule/__init__.py b/gallery/easel/route/view/schedule/__init__.py index 7030345..e0a3a3a 100644 --- a/gallery/easel/route/view/schedule/__init__.py +++ b/gallery/easel/route/view/schedule/__init__.py @@ -1,6 +1,7 @@ import datetime from pathlib import Path +from babel.dates import format_date from fastapi import APIRouter from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates @@ -20,7 +21,13 @@ templates = Jinja2Templates( base_dir / "templates", ] ) -templates.env.globals.update({"_": _}) +templates.env.globals.update( + { + "_": _, + "version": __version__, + "format_date": format_date, + } +) templates.env.filters["timedelta_format"] = timedelta_format router = APIRouter() diff --git a/gallery/easel/route/view/schedule/templates/channel.html b/gallery/easel/route/view/schedule/templates/channel.html index 7f26a7b..b8a457c 100644 --- a/gallery/easel/route/view/schedule/templates/channel.html +++ b/gallery/easel/route/view/schedule/templates/channel.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% block title %} -Программа | {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}} +{{_("TV program")}} | {{response.channel.name}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}} {% endblock %} {% block header %} @@ -15,7 +15,7 @@ href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️
⬆️ - {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}} + {{response.channel.name}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}} ➡️ diff --git a/gallery/easel/route/view/schedule/templates/schedule.html b/gallery/easel/route/view/schedule/templates/schedule.html index 10d79c3..00b6cd7 100644 --- a/gallery/easel/route/view/schedule/templates/schedule.html +++ b/gallery/easel/route/view/schedule/templates/schedule.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% block title %} -{{'Прямые трансляции' if live else _("TV program")}} | {{response.date.strftime('%a, %d %B %Y')}} +{{_("Live broadcasts") if live else _("TV program")}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}} {% endblock %} {% block header %} @@ -15,7 +15,7 @@ href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️ ⬆️ - {{'Прямые трансляции' if live else 'Программа'}} | {{response.date.strftime('%a, %d %B %Y')}} + {{_("Live broadcasts") if live else _("TV program")}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}} ➡️ @@ -36,8 +36,6 @@
{{response.channel.name}}
- - {% for value in values %} diff --git a/gallery/easel/route/view/translation.py b/gallery/easel/route/view/translation.py index 05e6f7d..c1dd0da 100644 --- a/gallery/easel/route/view/translation.py +++ b/gallery/easel/route/view/translation.py @@ -1,9 +1,10 @@ import gettext from contextvars import ContextVar -from pathlib import Path from fastapi import Cookie, Header, Request +from gallery.util import root_path + _translation: ContextVar[gettext.GNUTranslations | gettext.NullTranslations] = ( ContextVar("translation") ) @@ -19,7 +20,7 @@ async def set_language( try: t = gettext.translation( - "messages", localedir=Path(__file__).parent / "locales", languages=[lang] + "messages", localedir=root_path / "locales", languages=[lang] ) except FileNotFoundError: t = gettext.NullTranslations() diff --git a/gallery/easel/route/view/weather/__init__.py b/gallery/easel/route/view/weather/__init__.py index 8d7461d..8da22a0 100644 --- a/gallery/easel/route/view/weather/__init__.py +++ b/gallery/easel/route/view/weather/__init__.py @@ -1,11 +1,10 @@ import datetime from pathlib import Path -from fastapi import APIRouter, FastAPI +from fastapi import APIRouter from fastapi.responses import HTMLResponse, RedirectResponse -from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates - +from babel.dates import format_date from gallery.easel.core import AppRequest from gallery.sketch.weather.model import WeatherResponse from gallery.version import __version__ @@ -22,7 +21,13 @@ templates = Jinja2Templates( base_dir / "templates", ] ) -templates.env.globals.update({"_": _}) +templates.env.globals.update( + { + "_": _, + "version": __version__, + "format_date": format_date, + } +) templates.env.filters["wind_direction_icon"] = wind_direction_icon templates.env.filters["cloudness_icon"] = cloudness_icon @@ -32,15 +37,16 @@ def build_weather_response(request: AppRequest, response: WeatherResponse): request=request, name="weather.html", context={ - "version": __version__, "tag_util": TagUtil, "datetime": datetime, "response": response, }, ) + router = APIRouter() + @router.get("/weather", response_class=HTMLResponse) async def get_weather_index(request: AppRequest, query: str | None = None): weather_api = request.app.state.api.weather @@ -49,7 +55,6 @@ async def get_weather_index(request: AppRequest, query: str | None = None): request=request, name="index.html", context={ - "version": __version__, "locations": locations, }, ) diff --git a/gallery/easel/route/view/weather/templates/index.html b/gallery/easel/route/view/weather/templates/index.html index e3456ea..7a6a2b5 100644 --- a/gallery/easel/route/view/weather/templates/index.html +++ b/gallery/easel/route/view/weather/templates/index.html @@ -1,8 +1,8 @@ {% extends "base.html" %} -{% block title %}Weather{% endblock %} +{% block title %}{{_("Weather")}}{% endblock %} {% block content %} -

Weather

+

{{_("Weather")}}

@@ -11,9 +11,9 @@ class="form-control" id="query" name="query" - placeholder="Enter the city name"> + placeholder="{{_('Enter the city name')}}"> + type="submit">{{_("Search")}}