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")}}
+
{{location.name}}
{{location.country}}, {{location.district}}, {{location.subdistrict}}
diff --git a/gallery/easel/route/view/weather/templates/weather.html b/gallery/easel/route/view/weather/templates/weather.html
index 82b793f..6698c8a 100644
--- a/gallery/easel/route/view/weather/templates/weather.html
+++ b/gallery/easel/route/view/weather/templates/weather.html
@@ -13,12 +13,12 @@
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️
⬆️
- {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}
+ {{response.location}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}
➡️
{% endif %}
{% if response.period == 'days' %}
- {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}
+ {{response.location}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}
{% endif %}
@@ -36,9 +36,9 @@
{% endif %}
{% if response.period == 'days' %}
-
+
- {{value.date.strftime('%a %d')}}
+ {{format_date(value.date, 'E d', locale=request.state.language)}}
|
@@ -49,7 +49,7 @@
@@ -65,7 +65,7 @@
@@ -84,7 +84,7 @@
@@ -98,7 +98,7 @@
@@ -118,7 +118,7 @@
@@ -133,7 +133,7 @@
@@ -151,7 +151,7 @@
diff --git a/gallery/painting/gismeteo/api.py b/gallery/painting/gismeteo/api.py
index 7681339..d9b19ad 100644
--- a/gallery/painting/gismeteo/api.py
+++ b/gallery/painting/gismeteo/api.py
@@ -87,6 +87,7 @@ class GismeteoApi(WeatherApi):
lat=item["coordinates"]["latitude"],
lon=item["coordinates"]["longitude"],
country=item["translations"]["kk"]["country"]["name"],
+ country_code=item["country"]["code"].lower(),
district=item["translations"]["kk"]["district"]["name"],
subdistrict=(
item["translations"]["kk"]["subdistrict"]["name"]
diff --git a/gallery/sketch/weather/model.py b/gallery/sketch/weather/model.py
index e9ce269..ee8e1bc 100644
--- a/gallery/sketch/weather/model.py
+++ b/gallery/sketch/weather/model.py
@@ -15,6 +15,7 @@ class Location(Model):
lat: float
lon: float
country: str
+ country_code: str
district: str
subdistrict: str
diff --git a/locales/ru/LC_MESSAGES/messages.po b/locales/ru/LC_MESSAGES/messages.po
new file mode 100644
index 0000000..16bfb0b
--- /dev/null
+++ b/locales/ru/LC_MESSAGES/messages.po
@@ -0,0 +1,77 @@
+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 "Index"
+msgstr "Содержание"
+
+msgid "View"
+msgstr "Просмотр"
+
+msgid "Docs"
+msgstr "Документация"
+
+msgid "Toggle theme"
+msgstr "Переключить тему"
+
+msgid "Light"
+msgstr "Светлая"
+
+msgid "Dark"
+msgstr "Тёмная"
+
+msgid "Auto"
+msgstr "Авто"
+
+msgid "Select language"
+msgstr "Выберите язык"
+
+msgid "English"
+msgstr "Английский"
+
+msgid "Russian"
+msgstr "Русский"
+
+# weather
+msgid "Weather"
+msgstr "Погода"
+
+msgid "Enter the city name"
+msgstr "Введите название города"
+
+msgid "Search"
+msgstr "Поиск"
+
+msgid "Cloudiness"
+msgstr "Облачность"
+
+msgid "Temperature, °C"
+msgstr "Температура, °C"
+
+msgid "Wind direction"
+msgstr "Направление ветра"
+
+msgid "Wind speed, m/s"
+msgstr "Скорость ветра, м/с"
+
+msgid "Precipitation, mm"
+msgstr "Осадки, мм"
+
+msgid "Pressure, mmHg"
+msgstr "Давление, мм рт. ст."
+
+msgid "Humidity, %"
+msgstr "Влажность, %"
+
+# tv
+msgid "TV program"
+msgstr "Телепрограмма"
+
+msgid "Live broadcasts"
+msgstr "Прямые трансляции"
diff --git a/poetry.lock b/poetry.lock
index 7181748..6114daf 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -196,6 +196,21 @@ tests = ["attrs[tests-no-zope]", "zope-interface"]
tests-mypy = ["mypy (>=1.6) ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.8\""]
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
+[[package]]
+name = "babel"
+version = "2.18.0"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.8"
+groups = ["app"]
+files = [
+ {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"},
+ {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"},
+]
+
+[package.extras]
+dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
+
[[package]]
name = "beautifulsoup4"
version = "4.12.3"
@@ -1898,4 +1913,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.1"
python-versions = "^3.12"
-content-hash = "7295e9ec7f7492017c5bbda489026f19bbf155f0ea82402d348b0aa4c03beaca"
+content-hash = "39107ded0683832d1ca3d637dc50fac7dd441375ead30cdb56d64edee13c97e2"
diff --git a/pyproject.toml b/pyproject.toml
index 04e97ba..f08cc29 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,6 +17,7 @@ aiocache = {extras = ["redis"], version = "^0.12.2"}
[tool.poetry.group.app.dependencies]
fastapi = "^0.111.1"
jinja2 = "^3.1.4"
+babel = "^2.18.0"
[tool.poetry.group.test.dependencies]
pytest = "^8.3.1"
diff --git a/static/package-lock.json b/static/package-lock.json
index bd17c62..93ef009 100644
--- a/static/package-lock.json
+++ b/static/package-lock.json
@@ -1,17 +1,18 @@
{
- "name": "gallery-static",
+ "name": "gallery",
"version": "0.2.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "gallery-static",
+ "name": "gallery",
"version": "0.2.1",
"license": "ISC",
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
+ "flag-icons": "^7.5.0",
"sass": "^1.99.0"
},
"devDependencies": {
@@ -699,6 +700,12 @@
}
}
},
+ "node_modules/flag-icons": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz",
+ "integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==",
+ "license": "MIT"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
diff --git a/static/package.json b/static/package.json
index 7ecce03..4e173ca 100644
--- a/static/package.json
+++ b/static/package.json
@@ -12,6 +12,7 @@
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
+ "flag-icons": "^7.5.0",
"sass": "^1.99.0"
},
"devDependencies": {
diff --git a/static/src/components.ts b/static/src/components.ts
index 4d8a0a9..2f44cc2 100644
--- a/static/src/components.ts
+++ b/static/src/components.ts
@@ -7,7 +7,7 @@ class AppLinkElement extends HTMLElement {
-
+
${this.textContent}
`;
diff --git a/static/src/language.ts b/static/src/language.ts
index 419b558..e531861 100644
--- a/static/src/language.ts
+++ b/static/src/language.ts
@@ -28,7 +28,7 @@
const languageSwitcherText = document.querySelector("#bd-language-text");
const activeLanguageIcon = document.querySelector(".language-icon-active");
const btnToActive = document.querySelector(`[data-bs-language-value="${language}"]`);
- const activeLanguageIconContent = btnToActive?.querySelector("span")?.textContent;
+ const activeLanguageIconClass = btnToActive.querySelector(".fi").className.match(/fi-[\w-]+/)[0];
document.querySelectorAll("[data-bs-language-value]").forEach((element) => {
element.classList.remove("active");
@@ -37,7 +37,9 @@
btnToActive.classList.add("active");
btnToActive.setAttribute("aria-pressed", "true");
- activeLanguageIcon.textContent = activeLanguageIconContent;
+ const classesToRemove = Array.from(activeLanguageIcon.classList).filter((className) => className.startsWith("fi-"));
+ activeLanguageIcon.classList.remove(...classesToRemove);
+ activeLanguageIcon.classList.add(activeLanguageIconClass);
const languageSwitcherLabel = `${languageSwitcherText.textContent} (${btnToActive.dataset.bsLanguageValue})`;
languageSwitcher.setAttribute("aria-label", languageSwitcherLabel);
diff --git a/static/src/lib/bootstrap-icons.scss b/static/src/lib/bootstrap-icons.scss
new file mode 100644
index 0000000..d9ba7a6
--- /dev/null
+++ b/static/src/lib/bootstrap-icons.scss
@@ -0,0 +1,40 @@
+//$bootstrap-icons: "circle-half", "moon-stars-fill", "brightness-high", "gear", "sun-fill", "tv";
+
+.bi {
+ display: inline-block;
+ text-transform: none;
+ line-height: 1;
+ vertical-align: -0.125em;
+ width: 1em;
+ height: 1em;
+
+ mask-size: contain;
+ mask-position: 50%;
+ mask-repeat: no-repeat;
+ background-color: currentColor;
+
+ //@each $icon in $bootstrap-icons {
+ // &.bi-#{$icon} {
+ // mask-image: url("bootstrap-icons/icons/#{$icon}.svg");
+ // }
+ ///
+
+ &.bi-circle-half {
+ mask-image: url(bootstrap-icons/icons/circle-half.svg);
+ }
+ &.bi-moon-stars-fill {
+ mask-image: url(bootstrap-icons/icons/moon-stars-fill.svg);
+ }
+ &.bi-brightness-high {
+ mask-image: url(bootstrap-icons/icons/brightness-high.svg);
+ }
+ &.bi-gear {
+ mask-image: url(bootstrap-icons/icons/gear.svg);
+ }
+ &.bi-sun-fill {
+ mask-image: url(bootstrap-icons/icons/sun-fill.svg);
+ }
+ &.bi-tv {
+ mask-image: url(bootstrap-icons/icons/tv.svg);
+ }
+}
diff --git a/static/src/lib/bootstrap.scss b/static/src/lib/bootstrap.scss
new file mode 100644
index 0000000..943d598
--- /dev/null
+++ b/static/src/lib/bootstrap.scss
@@ -0,0 +1,52 @@
+@import "bootstrap/scss/mixins/banner";
+@include bsBanner("");
+
+
+// scss-docs-start import-stack
+// Configuration
+@import "bootstrap/scss/functions";
+@import "bootstrap/scss/variables";
+@import "bootstrap/scss/variables-dark";
+@import "bootstrap/scss/maps";
+@import "bootstrap/scss/mixins";
+@import "bootstrap/scss/utilities";
+
+// Layout & components
+@import "bootstrap/scss/root";
+@import "bootstrap/scss/reboot";
+@import "bootstrap/scss/type";
+//@import "bootstrap/scss/images";
+//@import "bootstrap/scss/containers";
+@import "bootstrap/scss/grid";
+@import "bootstrap/scss/tables";
+@import "bootstrap/scss/forms";
+@import "bootstrap/scss/buttons";
+//@import "bootstrap/scss/transitions";
+@import "bootstrap/scss/dropdown";
+//@import "bootstrap/scss/button-group";
+@import "bootstrap/scss/nav";
+@import "bootstrap/scss/navbar";
+//@import "bootstrap/scss/card";
+//@import "bootstrap/scss/accordion";
+//@import "bootstrap/scss/breadcrumb";
+//@import "bootstrap/scss/pagination";
+//@import "bootstrap/scss/badge";
+//@import "bootstrap/scss/alert";
+//@import "bootstrap/scss/progress";
+@import "bootstrap/scss/list-group";
+//@import "bootstrap/scss/close";
+//@import "bootstrap/scss/toasts";
+//@import "bootstrap/scss/modal";
+//@import "bootstrap/scss/tooltip";
+//@import "bootstrap/scss/popover";
+//@import "bootstrap/scss/carousel";
+//@import "bootstrap/scss/spinners";
+//@import "bootstrap/scss/offcanvas";
+//@import "bootstrap/scss/placeholders";
+
+// Helpers
+@import "bootstrap/scss/helpers";
+
+// Utilities
+@import "bootstrap/scss/utilities/api";
+// scss-docs-end import-stack
diff --git a/static/src/main.scss b/static/src/main.scss
index 48fb276..98b8d93 100644
--- a/static/src/main.scss
+++ b/static/src/main.scss
@@ -1,6 +1,10 @@
-@import "bootstrap/scss/bootstrap";
-$bootstrap-icons-font-dir: "bootstrap-icons/font/fonts";
-@import "bootstrap-icons/font/bootstrap-icons";
+@use "flag-icons/sass/flag-icons" with (
+ $flag-icons-path: "flag-icons/flags",
+ $flag-icons-included-countries: ("gb", "ru", "by", "ua", "kz")
+);
+
+@import "./lib/bootstrap";
+@import "./lib/bootstrap-icons";
@import "./widget.scss";
@import "./weather.scss";
diff --git a/static/src/theme.ts b/static/src/theme.ts
index 980fc09..ea5da8a 100644
--- a/static/src/theme.ts
+++ b/static/src/theme.ts
@@ -34,7 +34,7 @@
const themeSwitcherText = document.querySelector("#bd-theme-text");
const activeThemeIcon = document.querySelector(".theme-icon-active");
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
- const activeThemeIconClass = btnToActive.querySelector("i.bi").className.match(/bi-[\w-]+/)[0];
+ const activeThemeIconClass = btnToActive.querySelector(".bi").className.match(/bi-[\w-]+/)[0];
document.querySelectorAll("[data-bs-theme-value]").forEach((element) => {
element.classList.remove("active");