diff --git a/app/babel_mapping.ini b/app/babel_mapping.ini new file mode 100644 index 00000000..4ba23e47 --- /dev/null +++ b/app/babel_mapping.ini @@ -0,0 +1,8 @@ +# Extraction from Python source files + +[python: **.py] + +# Extraction from Jinja2 HTML and text templates + +[jinja2: **/templates/**.html] +extensions=jinja2.ext.i18n,jinja2.ext.autoescape,jinja2.ext.with_ diff --git a/app/config.py.example b/app/config.py.example index 370d1c55..4acac145 100644 --- a/app/config.py.example +++ b/app/config.py.example @@ -27,6 +27,10 @@ MEDIA_DIRECTORY = 'media' PICTURE_EXTENSION = '.png' AVATAR_SIZE = (120, 120) + +# DEFAULT WEBSITE LANGUAGE +WEBSITE_LANGUAGE = "en" + # API-KEYS # Get a free API KEY for Astronomy feature @ www.weatherapi.com/signup.aspx ASTRONOMY_API_KEY = os.getenv('ASTRONOMY_API_KEY') diff --git a/app/database/models.py b/app/database/models.py index c1a63450..94f702d8 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -42,7 +42,7 @@ class User(Base): avatar = Column(String, default="profile.png") telegram_id = Column(String, unique=True) is_active = Column(Boolean, default=False) - + language_id = Column(Integer, ForeignKey("languages.id")) events = relationship("UserEvent", back_populates="participants") def __repr__(self): @@ -79,6 +79,13 @@ def __repr__(self): return f'' +class Language(Base): + __tablename__ = "languages" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, unique=True, nullable=False) + + class Category(Base): __tablename__ = "categories" diff --git a/app/dependencies.py b/app/dependencies.py index ded80166..8158f3ad 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -6,13 +6,13 @@ from app import config from app.internal.logger_customizer import LoggerCustomizer - APP_PATH = os.path.dirname(os.path.realpath(__file__)) MEDIA_PATH = os.path.join(APP_PATH, config.MEDIA_DIRECTORY) STATIC_PATH = os.path.join(APP_PATH, "static") TEMPLATES_PATH = os.path.join(APP_PATH, "templates") templates = Jinja2Templates(directory=TEMPLATES_PATH) +templates.env.add_extension('jinja2.ext.i18n') # Configure logger logger = LoggerCustomizer.make_logger(config.LOG_PATH, diff --git a/app/internal/languages.py b/app/internal/languages.py new file mode 100644 index 00000000..f8337fa1 --- /dev/null +++ b/app/internal/languages.py @@ -0,0 +1,86 @@ +import gettext +import os +from pathlib import Path +from typing import Any, Generator + +from app import config +from app.dependencies import templates + +LANGUAGE_DIR = "app/locales" +LANGUAGE_DIR_TEST = "../app/locales" +TRANSLATION_FILE = "base" + + +def set_ui_language(language: str = None) -> None: + """Set the gettext translations to a given language. + If the language requested is not supported, the translations default + to the value of config.WEBSITE_LANGUAGE. + + Args: + language (str, optional): a valid language code that follows RFC 1766. + Defaults to None. + See also the Language Code Identifier (LCID) Reference for a list of + valid language codes. + + .. _RFC 1766: + https://tools.ietf.org/html/rfc1766.html + + .. _Language Code Identifier (LCID) Reference: + https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c # noqa: E501 + """ + + # TODO: Connect when user registration is completed. + # if not language: + # language = _get_display_language(user_id: int) + + language_dir = _get_language_directory() + + if language not in list(_get_supported_languages(language_dir)): + language = config.WEBSITE_LANGUAGE + + translations = gettext.translation(TRANSLATION_FILE, + localedir=language_dir, + languages=[language]) + translations.install() + templates.env.install_gettext_translations(translations, newstyle=True) + + +# TODO: Waiting for user registration. Add doc. +# def _get_display_language(user_id: int) -> str: +# # TODO: handle user language setting: +# # If user is logged in, get language setting. +# # If user is not logged in, get default site setting. +# +# if db_user: +# return db_user.language +# return config.WEBSITE_LANGUAGE + + +def _get_language_directory() -> str: + """Get and return the language directory relative path. + + Returns: + str: the language directory relative path. + """ + language_dir = LANGUAGE_DIR + if Path(LANGUAGE_DIR_TEST).is_dir(): + # If running from test, change dir path. + language_dir = LANGUAGE_DIR_TEST + return language_dir + + +def _get_supported_languages(language_dir: str = _get_language_directory()) \ + -> Generator[str, Any, None]: + """Get and return a generator of supported translation languages codes. + + Args: + language_dir (str, optional): the path of the language directory. + Defaults to the return value of _get_language_directory(). + + Returns: + Generator[str, Any, None]: a generator expression of supported + translation languages codes. + """ + + paths = [Path(f.path) for f in os.scandir(language_dir) if f.is_dir()] + return (language.name for language in paths) diff --git a/app/locales/base.pot b/app/locales/base.pot new file mode 100644 index 00000000..77a0321f --- /dev/null +++ b/app/locales/base.pot @@ -0,0 +1,133 @@ +# Translations template for PROJECT. +# Copyright (C) 2021 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2021. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2021-02-01 10:21+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.9.0\n" + +#: app/routers/profile.py:19 +msgid "Not found" +msgstr "" + +#: app/templates/agenda.html:11 +msgid "From" +msgstr "" + +#: app/templates/agenda.html:13 +msgid "to" +msgstr "" + +#: app/templates/agenda.html:15 +msgid "Get Agenda" +msgstr "" + +#: app/templates/agenda.html:20 +msgid "Today" +msgstr "" + +#: app/templates/agenda.html:23 +msgid "Next week" +msgstr "" + +#: app/templates/agenda.html:26 +msgid "Next month" +msgstr "" + +#: app/templates/agenda.html:33 +msgid "Start date is greater than end date" +msgstr "" + +#: app/templates/agenda.html:35 +msgid "No events found..." +msgstr "" + +#: app/templates/base.html:18 +msgid "Calendar" +msgstr "" + +#: app/templates/base.html:26 +msgid "Home" +msgstr "" + +#: app/templates/base.html:29 +msgid "Profile" +msgstr "" + +#: app/templates/base.html:32 +msgid "Sign in" +msgstr "" + +#: app/templates/base.html:35 +msgid "Sign up" +msgstr "" + +#: app/templates/base.html:40 +msgid "Agenda" +msgstr "" + +#: app/templates/profile.html:59 +msgid "Update name" +msgstr "" + +#: app/templates/profile.html:66 app/templates/profile.html:88 +#: app/templates/profile.html:109 app/templates/profile.html:133 +msgid "Save changes" +msgstr "" + +#: app/templates/profile.html:80 +msgid "Update email" +msgstr "" + +#: app/templates/profile.html:101 +msgid "Update description" +msgstr "" + +#: app/templates/profile.html:124 +msgid "Update photo" +msgstr "" + +#: app/templates/profile.html:176 +msgid "Settings" +msgstr "" + +#: app/templates/profile.html:207 +msgid "Your feature" +msgstr "" + +#: app/templates/profile.html:228 +msgid "Upcoming event on (date)" +msgstr "" + +#: app/templates/profile.html:239 +msgid "The Event (event)" +msgstr "" + +#: app/templates/profile.html:242 +msgid "Last updated (time) ago" +msgstr "" + +#: app/templates/profile.html:259 +msgid "Explore MeetUps near you" +msgstr "" + +#: app/templates/profile.html:268 app/templates/profile.html:277 +msgid "Your Card" +msgstr "" + +#. i18n: String used in testing. Do not change. +#: tests/test_language.py:32 +msgid "test python translation" +msgstr "" + diff --git a/app/locales/en/LC_MESSAGES/base.mo b/app/locales/en/LC_MESSAGES/base.mo new file mode 100644 index 00000000..c56ad44f Binary files /dev/null and b/app/locales/en/LC_MESSAGES/base.mo differ diff --git a/app/locales/en/LC_MESSAGES/base.po b/app/locales/en/LC_MESSAGES/base.po new file mode 100644 index 00000000..c6f3b502 --- /dev/null +++ b/app/locales/en/LC_MESSAGES/base.po @@ -0,0 +1,140 @@ +# English translations for PROJECT. +# Copyright (C) 2021 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2021. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2021-02-01 10:21+0200\n" +"PO-Revision-Date: 2021-01-26 21:31+0200\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.9.0\n" + +#: app/routers/profile.py:19 +msgid "Not found" +msgstr "" + +#: app/templates/agenda.html:11 +msgid "From" +msgstr "" + +#: app/templates/agenda.html:13 +msgid "to" +msgstr "" + +#: app/templates/agenda.html:15 +msgid "Get Agenda" +msgstr "" + +#: app/templates/agenda.html:20 +msgid "Today" +msgstr "" + +#: app/templates/agenda.html:23 +msgid "Next week" +msgstr "" + +#: app/templates/agenda.html:26 +msgid "Next month" +msgstr "" + +#: app/templates/agenda.html:33 +msgid "Start date is greater than end date" +msgstr "" + +#: app/templates/agenda.html:35 +msgid "No events found..." +msgstr "" + +#: app/templates/base.html:18 +msgid "Calendar" +msgstr "" + +#: app/templates/base.html:26 +msgid "Home" +msgstr "" + +#: app/templates/base.html:29 +msgid "Profile" +msgstr "" + +#: app/templates/base.html:32 +msgid "Sign in" +msgstr "" + +#: app/templates/base.html:35 +msgid "Sign up" +msgstr "" + +#: app/templates/base.html:40 +msgid "Agenda" +msgstr "" + +#: app/templates/profile.html:59 +msgid "Update name" +msgstr "" + +#: app/templates/profile.html:66 app/templates/profile.html:88 +#: app/templates/profile.html:109 app/templates/profile.html:133 +msgid "Save changes" +msgstr "" + +#: app/templates/profile.html:80 +msgid "Update email" +msgstr "" + +#: app/templates/profile.html:101 +msgid "Update description" +msgstr "" + +#: app/templates/profile.html:124 +msgid "Update photo" +msgstr "" + +#: app/templates/profile.html:176 +msgid "Settings" +msgstr "" + +#: app/templates/profile.html:207 +msgid "Your feature" +msgstr "" + +#: app/templates/profile.html:228 +msgid "Upcoming event on (date)" +msgstr "" + +#: app/templates/profile.html:239 +msgid "The Event (event)" +msgstr "" + +#: app/templates/profile.html:242 +msgid "Last updated (time) ago" +msgstr "" + +#: app/templates/profile.html:259 +msgid "Explore MeetUps near you" +msgstr "" + +#: app/templates/profile.html:268 app/templates/profile.html:277 +msgid "Your Card" +msgstr "" + +#. i18n: String used in testing. Do not change. +#: tests/test_language.py:32 +msgid "test python translation" +msgstr "" + +#~ msgid "Features" +#~ msgstr "" + +#~ msgid "Export my calendar" +#~ msgstr "" + diff --git a/app/locales/he/LC_MESSAGES/base.mo b/app/locales/he/LC_MESSAGES/base.mo new file mode 100644 index 00000000..e112692a Binary files /dev/null and b/app/locales/he/LC_MESSAGES/base.mo differ diff --git a/app/locales/he/LC_MESSAGES/base.po b/app/locales/he/LC_MESSAGES/base.po new file mode 100644 index 00000000..5621a5e0 --- /dev/null +++ b/app/locales/he/LC_MESSAGES/base.po @@ -0,0 +1,140 @@ +# Hebrew translations for PROJECT. +# Copyright (C) 2021 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2021. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2021-02-01 10:21+0200\n" +"PO-Revision-Date: 2021-01-26 21:31+0200\n" +"Last-Translator: FULL NAME \n" +"Language: he\n" +"Language-Team: he \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.9.0\n" + +#: app/routers/profile.py:19 +msgid "Not found" +msgstr "" + +#: app/templates/agenda.html:11 +msgid "From" +msgstr "" + +#: app/templates/agenda.html:13 +msgid "to" +msgstr "" + +#: app/templates/agenda.html:15 +msgid "Get Agenda" +msgstr "" + +#: app/templates/agenda.html:20 +msgid "Today" +msgstr "" + +#: app/templates/agenda.html:23 +msgid "Next week" +msgstr "" + +#: app/templates/agenda.html:26 +msgid "Next month" +msgstr "" + +#: app/templates/agenda.html:33 +msgid "Start date is greater than end date" +msgstr "" + +#: app/templates/agenda.html:35 +msgid "No events found..." +msgstr "" + +#: app/templates/base.html:18 +msgid "Calendar" +msgstr "" + +#: app/templates/base.html:26 +msgid "Home" +msgstr "" + +#: app/templates/base.html:29 +msgid "Profile" +msgstr "פרופיל" + +#: app/templates/base.html:32 +msgid "Sign in" +msgstr "" + +#: app/templates/base.html:35 +msgid "Sign up" +msgstr "" + +#: app/templates/base.html:40 +msgid "Agenda" +msgstr "" + +#: app/templates/profile.html:59 +msgid "Update name" +msgstr "" + +#: app/templates/profile.html:66 app/templates/profile.html:88 +#: app/templates/profile.html:109 app/templates/profile.html:133 +msgid "Save changes" +msgstr "" + +#: app/templates/profile.html:80 +msgid "Update email" +msgstr "" + +#: app/templates/profile.html:101 +msgid "Update description" +msgstr "" + +#: app/templates/profile.html:124 +msgid "Update photo" +msgstr "" + +#: app/templates/profile.html:176 +msgid "Settings" +msgstr "" + +#: app/templates/profile.html:207 +msgid "Your feature" +msgstr "" + +#: app/templates/profile.html:228 +msgid "Upcoming event on (date)" +msgstr "" + +#: app/templates/profile.html:239 +msgid "The Event (event)" +msgstr "" + +#: app/templates/profile.html:242 +msgid "Last updated (time) ago" +msgstr "" + +#: app/templates/profile.html:259 +msgid "Explore MeetUps near you" +msgstr "" + +#: app/templates/profile.html:268 app/templates/profile.html:277 +msgid "Your Card" +msgstr "" + +#. i18n: String used in testing. Do not change. +#: tests/test_language.py:32 +msgid "test python translation" +msgstr "בדיקת תרגום בפייתון" + +#~ msgid "Features" +#~ msgstr "" + +#~ msgid "Export my calendar" +#~ msgstr "" + diff --git a/app/main.py b/app/main.py index 2e45f116..d800d11e 100644 --- a/app/main.py +++ b/app/main.py @@ -5,12 +5,9 @@ from app.config import PSQL_ENVIRONMENT from app.database import models from app.database.database import engine, get_db -from app.dependencies import (logger, MEDIA_PATH, STATIC_PATH, templates) +from app.dependencies import logger, MEDIA_PATH, STATIC_PATH, templates from app.internal import daily_quotes, json_data_loader -from app.routers import ( - agenda, calendar, categories, currency, dayview, email, - event, invitation, profile, search, telegram, whatsapp -) +from app.internal.languages import set_ui_language def create_tables(engine, psql_environment): @@ -25,13 +22,21 @@ def create_tables(engine, psql_environment): create_tables(engine, PSQL_ENVIRONMENT) + app = FastAPI() app.mount("/static", StaticFiles(directory=STATIC_PATH), name="static") app.mount("/media", StaticFiles(directory=MEDIA_PATH), name="media") +app.logger = logger -json_data_loader.load_to_db(next(get_db())) +# This MUST come before the app.routers imports. +set_ui_language() -app.logger = logger +from app.routers import ( # noqa: E402 + agenda, calendar, categories, currency, dayview, email, + event, invitation, profile, search, telegram, whatsapp +) + +json_data_loader.load_to_db(next(get_db())) routers_to_include = [ agenda.router, @@ -60,6 +65,5 @@ async def home(request: Request, db: Session = Depends(get_db)): quote = daily_quotes.quote_per_day(db) return templates.TemplateResponse("home.html", { "request": request, - "message": "Hello, World!", - "quote": quote + "quote": quote, }) diff --git a/app/routers/agenda.py b/app/routers/agenda.py index b65c6b3b..34432d7e 100644 --- a/app/routers/agenda.py +++ b/app/routers/agenda.py @@ -50,7 +50,7 @@ def agenda( for event_obj in events_objects: event_duration = agenda_events.get_time_delta_string( event_obj.start, event_obj.end - ) + ) events[event_obj.start.date()].append((event_obj, event_duration)) return templates.TemplateResponse("agenda.html", { diff --git a/app/routers/invitation.py b/app/routers/invitation.py index f92d7cd3..b496599b 100644 --- a/app/routers/invitation.py +++ b/app/routers/invitation.py @@ -5,14 +5,12 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from starlette.status import HTTP_302_FOUND -from starlette.templating import Jinja2Templates from app.database.database import get_db from app.database.models import Invitation +from app.dependencies import templates from app.routers.share import accept -templates = Jinja2Templates(directory="app/templates") - router = APIRouter( prefix="/invitations", tags=["invitation"], @@ -34,8 +32,8 @@ def view_invitations(request: Request, db: Session = Depends(get_db)): @router.post("/") async def accept_invitations( - request: Request, - db: Session = Depends(get_db) + request: Request, + db: Session = Depends(get_db) ): data = await request.form() invite_id = list(data.values())[0] diff --git a/app/routers/profile.py b/app/routers/profile.py index b4be7b6d..aaf076bf 100644 --- a/app/routers/profile.py +++ b/app/routers/profile.py @@ -17,7 +17,7 @@ router = APIRouter( prefix="/profile", tags=["profile"], - responses={404: {"description": "Not found"}}, + responses={404: {"description": _("Not found")}}, ) @@ -27,6 +27,7 @@ def get_placeholder_user(): email='my@email.po', password='1a2s3d4f5g6', full_name='My Name', + language_id=1, telegram_id='', language='english', ) @@ -37,7 +38,6 @@ async def profile( request: Request, session=Depends(get_db), new_user=Depends(get_placeholder_user)): - # Get relevant data from database upcoming_events = range(5) user = session.query(User).filter_by(id=1).first() @@ -63,7 +63,6 @@ async def profile( @router.post("/update_user_fullname") async def update_user_fullname( request: Request, session=Depends(get_db)): - user = session.query(User).filter_by(id=1).first() data = await request.form() new_fullname = data['fullname'] @@ -79,7 +78,6 @@ async def update_user_fullname( @router.post("/update_user_email") async def update_user_email( request: Request, session=Depends(get_db)): - user = session.query(User).filter_by(id=1).first() data = await request.form() new_email = data['email'] @@ -95,7 +93,6 @@ async def update_user_email( @router.post("/update_user_description") async def update_profile( request: Request, session=Depends(get_db)): - user = session.query(User).filter_by(id=1).first() data = await request.form() new_description = data['description'] @@ -111,7 +108,6 @@ async def update_profile( @router.post("/upload_user_photo") async def upload_user_photo( file: UploadFile = File(...), session=Depends(get_db)): - user = session.query(User).filter_by(id=1).first() pic = await file.read() diff --git a/app/templates/agenda.html b/app/templates/agenda.html index bc1ac6a3..69f1bc6e 100644 --- a/app/templates/agenda.html +++ b/app/templates/agenda.html @@ -8,38 +8,38 @@ {% block content %}
-
+

-
+

- +
{% if start_date > end_date %} -

Start date is greater than end date

+

{{ gettext("Start date is greater than end date") }}

{% elif events | length == 0 %} -

No events found...

+

{{ gettext("No events found...") }}

{% elif start_date == end_date %}

{{ start_date.strftime("%d/%m/%Y") }}

{% else %} -

{{ start_date.strftime("%d/%m/%Y") }} - {{end_date.strftime("%d/%m/%Y") }}

+

{{ start_date.strftime("%d/%m/%Y") }} - {{end_date.strftime("%d/%m/%Y") }}

{% endif %}
- +
{% for events_date, events_list in events.items() %}
{{ events_date.strftime("%d/%m/%Y") }}
@@ -49,4 +49,4 @@

{{ start_date.strftime("%d/%m/%Y") }} - {{end_date.strftime("%d/%m/%Y") }} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 4cc5d825..c0b405a0 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -16,28 +16,28 @@

+
@@ -211,7 +207,6 @@
{{ user.full_name }}
- @@ -219,15 +214,11 @@
{{ user.full_name }}
- @@ -257,7 +247,7 @@
{{ user.full_name }}

- Explore MeetUps near you + {{ gettext("Explore MeetUps near you") }}

@@ -266,7 +256,7 @@
{{ user.full_name }}

- Your Card + {{ gettext("Your Card") }}

diff --git a/requirements.txt b/requirements.txt index ff8f6bfb..10fc7494 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ asyncio==3.4.3 atomicwrites==1.4.0 atpublic==2.1.2 attrs==20.3.0 +Babel==2.9.0 beautifulsoup4==4.9.3 blinker==1.4 certifi==2020.12.5 @@ -52,19 +53,20 @@ packaging==20.8 Pillow==8.1.0 pluggy==0.13.1 priority==1.3.0 +psycopg2==2.8.6 psycopg2-binary==2.8.6 py==1.10.0 pycodestyle==2.6.0 pydantic==1.7.3 pyflakes==2.2.0 pyparsing==2.4.7 +pytest==6.2.1 pytest-asyncio<0.14.0 pytest-cov==2.10.1 pytest-forked==1.3.0 pytest-httpx==0.10.1 pytest-mock==3.5.1 pytest-xdist==2.2.0 -pytest==6.2.1 python-dateutil==2.8.1 python-dotenv==0.15.0 python-multipart==0.0.5 diff --git a/tests/asyncio_fixture.py b/tests/asyncio_fixture.py index cc2b7cdd..6f01eef3 100644 --- a/tests/asyncio_fixture.py +++ b/tests/asyncio_fixture.py @@ -30,6 +30,7 @@ def get_test_placeholder_user(): email='fake@mail.fake', password='123456fake', full_name='FakeName', + language_id=1, telegram_id='666666' ) diff --git a/tests/client_fixture.py b/tests/client_fixture.py index c4890b05..14a68528 100644 --- a/tests/client_fixture.py +++ b/tests/client_fixture.py @@ -65,5 +65,6 @@ def get_test_placeholder_user(): email='fake@mail.fake', password='123456fake', full_name='FakeName', + language_id=1, telegram_id='666666' ) diff --git a/tests/test_a_telegram_asyncio.py b/tests/test_a_telegram_asyncio.py index 23637355..3ebdd6f1 100644 --- a/tests/test_a_telegram_asyncio.py +++ b/tests/test_a_telegram_asyncio.py @@ -3,8 +3,8 @@ from fastapi import status import pytest -from .asyncio_fixture import today_date -from .client_fixture import get_test_placeholder_user +from tests.asyncio_fixture import today_date +from tests.client_fixture import get_test_placeholder_user from app.telegram.handlers import MessageHandler, reply_unknown_user from app.telegram.keyboards import DATE_FORMAT from app.telegram.models import Bot, Chat diff --git a/tests/test_app.py b/tests/test_app.py index 2a08e499..a5afa56f 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -7,3 +7,8 @@ class TestApp: def test_get_db(self): assert isinstance(next(get_db()), Session) + + @staticmethod + def test_home(client): + response = client.get("/") + assert response.ok diff --git a/tests/test_dayview.py b/tests/test_dayview.py index 6a3e7aaf..2ddc7f14 100644 --- a/tests/test_dayview.py +++ b/tests/test_dayview.py @@ -10,8 +10,12 @@ # TODO add user session login @pytest.fixture def user(): - return User(username='test1', email='user@email.com', - password='1a2b3c4e5f', full_name='test me') + return User(username='test1', + email='user@email.com', + password='1a2b3c4e5f', + full_name='test me', + language_id=1 + ) @pytest.fixture diff --git a/tests/test_language.py b/tests/test_language.py new file mode 100644 index 00000000..765002d5 --- /dev/null +++ b/tests/test_language.py @@ -0,0 +1,62 @@ +from pathlib import Path + +import pytest + +from app.dependencies import templates +from app.internal import languages + + +class TestLanguage: + # Empty, invalid, or valid, but unsupported language codes, + # (currently 'en' and 'he') are set to the default language setting + # at config.WEBSITE_LANGUAGE, which is currently set to 'en' (English). + LANGUAGE_TESTS = [ + ('en', 'test python translation', True), + ('he', 'בדיקת תרגום בפייתון', True), + (None, 'test python translation', False), + ('', 'test python translation', False), + ('de', 'test python translation', False), + (["en"], 'test python translation', False), + (3, 'test python translation', False), + ] + + NUMBER_OF_LANGUAGES = 2 + + @staticmethod + @pytest.mark.parametrize("language_code, translation, is_valid", + LANGUAGE_TESTS) + def test_gettext_python(language_code, translation, is_valid): + languages.set_ui_language(language_code) + + # i18n: String used in testing. Do not change. + gettext_translation = _("test python translation") + assert ((is_valid and gettext_translation == translation) + or (not is_valid and gettext_translation == translation)) + + @staticmethod + @pytest.mark.parametrize("language_code, translation, is_valid", + LANGUAGE_TESTS) + def test_gettext_html(language_code, translation, is_valid): + languages.set_ui_language(language_code) + + template = templates.env.from_string( + '{{ gettext("test python translation") }}') + text = template.render() + assert ((is_valid and translation in text) + or (not is_valid and translation in text)) + + @staticmethod + def test_get_supported_languages(): + number_of_languages = len(list(languages._get_supported_languages())) + assert number_of_languages == TestLanguage.NUMBER_OF_LANGUAGES + + @staticmethod + def test_get_language_directory(): + pytest.MonkeyPatch().setattr(Path, 'is_dir', lambda x: True) + assert languages._get_language_directory() + + @staticmethod + def test_get_display_language(): + # TODO: Waiting for user registration. + # Test: no user, user not logged in and user with non-english set. + pass diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..333a3779 --- /dev/null +++ b/tox.ini @@ -0,0 +1,6 @@ +# Flake8 Configuration +[flake8] + +# gettext() adds _() to the global namespace. This lets flake recognize it. +builtins = + _,