Skip to content

Commit 779de2d

Browse files
authored
feat: add i18n support (#115)
* feat: add i18n support This feature includes the core functions added to /internal, English and Hebrew json files, removal of text strings from the html files, updates to the routes and model updates. Also included are tests. Please note, that this feature is waiting on user registration for final hooks. By: Gonny <1@1>
1 parent 2055db7 commit 779de2d

File tree

21 files changed

+632
-84
lines changed

21 files changed

+632
-84
lines changed

app/babel_mapping.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[python: **.py]
2+
[jinja2: **/templates/**.html]
3+
extensions=jinja2.ext.i18n,jinja2.ext.autoescape,jinja2.ext.with_

app/config.py.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ MEDIA_DIRECTORY = 'media'
1212
PICTURE_EXTENSION = '.png'
1313
AVATAR_SIZE = (120, 120)
1414

15+
# DEFAULT WEBSITE LANGUAGE
16+
WEBSITE_LANGUAGE = "en"
17+
1518
email_conf = ConnectionConfig(
1619
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
1720
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",

app/database/models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ class User(Base):
1414
full_name = Column(String)
1515
description = Column(String, default="Happy new user!")
1616
avatar = Column(String, default="profile.png")
17-
1817
is_active = Column(Boolean, default=True)
18+
language_id = Column(Integer, ForeignKey("languages.id"))
1919

2020
events = relationship(
2121
"Event", cascade="all, delete", back_populates="owner")
@@ -32,3 +32,10 @@ class Event(Base):
3232
owner_id = Column(Integer, ForeignKey("users.id"))
3333

3434
owner = relationship("User", back_populates="events")
35+
36+
37+
class Language(Base):
38+
__tablename__ = "languages"
39+
40+
id = Column(Integer, primary_key=True, index=True)
41+
name = Column(String, unique=True, nullable=False)

app/dependencies.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44

55
from app import config
66

7-
87
APP_PATH = os.path.dirname(os.path.realpath(__file__))
98
MEDIA_PATH = os.path.join(APP_PATH, config.MEDIA_DIRECTORY)
109
STATIC_PATH = os.path.join(APP_PATH, "static")
1110
TEMPLATES_PATH = os.path.join(APP_PATH, "templates")
1211

1312
templates = Jinja2Templates(directory=TEMPLATES_PATH)
13+
templates.env.add_extension('jinja2.ext.i18n')

app/internal/languages.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import gettext
2+
import os
3+
from pathlib import Path
4+
from typing import Any, Generator
5+
6+
from app import config
7+
from app.dependencies import templates
8+
9+
LANGUAGE_DIR = "app/locales"
10+
LANGUAGE_DIR_TEST = "../app/locales"
11+
TRANSLATION_FILE = "base"
12+
13+
14+
def set_ui_language(language: str = None) -> None:
15+
"""Set the gettext translations to a given language.
16+
If the language requested is not supported, the translations default
17+
to the value of config.WEBSITE_LANGUAGE.
18+
19+
Args:
20+
language (str, optional): a valid language code that follows RFC 1766.
21+
Defaults to None.
22+
See also the Language Code Identifier (LCID) Reference for a list of
23+
valid language codes.
24+
25+
.. _RFC 1766:
26+
https://tools.ietf.org/html/rfc1766.html
27+
28+
.. _Language Code Identifier (LCID) Reference:
29+
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c # noqa: E501
30+
"""
31+
32+
# TODO: Connect when user registration is completed.
33+
# if not language:
34+
# language = _get_display_language(user_id: int)
35+
36+
language_dir = _get_language_directory()
37+
38+
if language not in _get_supported_languages(language_dir):
39+
language = config.WEBSITE_LANGUAGE
40+
41+
translations = gettext.translation(TRANSLATION_FILE,
42+
localedir=language_dir,
43+
languages=[language])
44+
translations.install()
45+
templates.env.install_gettext_translations(translations, newstyle=True)
46+
47+
48+
# TODO: Waiting for user registration. Add doc.
49+
# def _get_display_language(user_id: int) -> str:
50+
# # TODO: handle user language setting:
51+
# # If user is logged in, get language setting.
52+
# # If user is not logged in, get default site setting.
53+
#
54+
# if db_user:
55+
# return db_user.language
56+
# return config.WEBSITE_LANGUAGE
57+
58+
59+
def _get_language_directory() -> str:
60+
"""Get and return the language directory relative path.
61+
62+
Returns:
63+
str: the language directory relative path.
64+
"""
65+
language_dir = LANGUAGE_DIR
66+
if Path(LANGUAGE_DIR_TEST).is_dir():
67+
# If running from test, change dir path.
68+
language_dir = LANGUAGE_DIR_TEST
69+
return language_dir
70+
71+
72+
def _get_supported_languages(language_dir: str = None) -> \
73+
Generator[str, Any, None]:
74+
"""Get and return a generator of supported translation languages codes.
75+
76+
Args:
77+
language_dir (str, optional): the path of the language directory.
78+
Defaults to None.
79+
80+
Returns:
81+
Generator[str, Any, None]: a generator expression of supported
82+
translation languages codes.
83+
"""
84+
85+
if not language_dir:
86+
language_dir = _get_language_directory()
87+
88+
return (language.name for language in
89+
[Path(f.path) for f in os.scandir(language_dir) if f.is_dir()])

app/locales/base.pot

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Translations template for PROJECT.
2+
# Copyright (C) 2021 ORGANIZATION
3+
# This file is distributed under the same license as the PROJECT project.
4+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
5+
#
6+
#, fuzzy
7+
msgid ""
8+
msgstr ""
9+
"Project-Id-Version: PROJECT VERSION\n"
10+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
11+
"POT-Creation-Date: 2021-01-26 21:30+0200\n"
12+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14+
"Language-Team: LANGUAGE <LL@li.org>\n"
15+
"MIME-Version: 1.0\n"
16+
"Content-Type: text/plain; charset=utf-8\n"
17+
"Content-Transfer-Encoding: 8bit\n"
18+
"Generated-By: Babel 2.9.0\n"
19+
20+
#: app/routers/profile.py:19
21+
msgid "Not found"
22+
msgstr ""
23+
24+
#: app/templates/agenda.html:11
25+
msgid "From"
26+
msgstr ""
27+
28+
#: app/templates/agenda.html:13
29+
msgid "to"
30+
msgstr ""
31+
32+
#: app/templates/agenda.html:33
33+
msgid "Start date is greater than end date"
34+
msgstr ""
35+
36+
#: app/templates/agenda.html:35
37+
msgid "No events found..."
38+
msgstr ""
39+
40+
#: app/templates/base.html:18
41+
msgid "Calendar"
42+
msgstr ""
43+
44+
#: app/templates/base.html:26
45+
msgid "Home"
46+
msgstr ""
47+
48+
#: app/templates/base.html:29
49+
msgid "Profile"
50+
msgstr ""
51+
52+
#: app/templates/base.html:32
53+
msgid "Sign in"
54+
msgstr ""
55+
56+
#: app/templates/base.html:35
57+
msgid "Sign up"
58+
msgstr ""
59+
60+
#: app/templates/base.html:40
61+
msgid "Agenda"
62+
msgstr ""
63+
64+
#: app/templates/profile.html:53
65+
msgid "Update name"
66+
msgstr ""
67+
68+
#: app/templates/profile.html:60 app/templates/profile.html:82
69+
#: app/templates/profile.html:103 app/templates/profile.html:127
70+
msgid "Save changes"
71+
msgstr ""
72+
73+
#: app/templates/profile.html:74
74+
msgid "Update email"
75+
msgstr ""
76+
77+
#: app/templates/profile.html:95
78+
msgid "Update description"
79+
msgstr ""
80+
81+
#: app/templates/profile.html:118
82+
msgid "Update photo"
83+
msgstr ""
84+
85+
#: app/templates/profile.html:144
86+
msgid "Settings"
87+
msgstr ""
88+
89+
#: app/templates/profile.html:161
90+
msgid "Features"
91+
msgstr ""
92+
93+
#: app/templates/profile.html:165
94+
msgid "Export my calendar"
95+
msgstr ""
96+
97+
#: app/templates/profile.html:168 app/templates/profile.html:171
98+
msgid "Your feature"
99+
msgstr ""
100+
101+
#: app/templates/profile.html:192
102+
msgid "Upcoming event on (date)"
103+
msgstr ""
104+
105+
#: app/templates/profile.html:203
106+
msgid "The Event (event)"
107+
msgstr ""
108+
109+
#: app/templates/profile.html:206
110+
msgid "Last updated (time) ago"
111+
msgstr ""
112+
113+
#: app/templates/profile.html:223
114+
msgid "Explore MeetUps near you"
115+
msgstr ""
116+
117+
#: app/templates/profile.html:232 app/templates/profile.html:241
118+
msgid "Your Card"
119+
msgstr ""
120+
121+
#: tests/test_language.py:39
122+
msgid "test python translation"
123+
msgstr ""
124+

app/locales/en/LC_MESSAGES/base.mo

443 Bytes
Binary file not shown.

app/locales/en/LC_MESSAGES/base.po

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# English translations for PROJECT.
2+
# Copyright (C) 2021 ORGANIZATION
3+
# This file is distributed under the same license as the PROJECT project.
4+
# FIRST AUTHOR <EMAIL@ADDRESS>, 2021.
5+
#
6+
msgid ""
7+
msgstr ""
8+
"Project-Id-Version: PROJECT VERSION\n"
9+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10+
"POT-Creation-Date: 2021-01-26 21:30+0200\n"
11+
"PO-Revision-Date: 2021-01-26 21:31+0200\n"
12+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13+
"Language: en\n"
14+
"Language-Team: en <LL@li.org>\n"
15+
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
16+
"MIME-Version: 1.0\n"
17+
"Content-Type: text/plain; charset=utf-8\n"
18+
"Content-Transfer-Encoding: 8bit\n"
19+
"Generated-By: Babel 2.9.0\n"
20+
21+
#: app/routers/profile.py:19
22+
msgid "Not found"
23+
msgstr ""
24+
25+
#: app/templates/agenda.html:11
26+
msgid "From"
27+
msgstr ""
28+
29+
#: app/templates/agenda.html:13
30+
msgid "to"
31+
msgstr ""
32+
33+
#: app/templates/agenda.html:33
34+
msgid "Start date is greater than end date"
35+
msgstr ""
36+
37+
#: app/templates/agenda.html:35
38+
msgid "No events found..."
39+
msgstr ""
40+
41+
#: app/templates/base.html:18
42+
msgid "Calendar"
43+
msgstr ""
44+
45+
#: app/templates/base.html:26
46+
msgid "Home"
47+
msgstr ""
48+
49+
#: app/templates/base.html:29
50+
msgid "Profile"
51+
msgstr ""
52+
53+
#: app/templates/base.html:32
54+
msgid "Sign in"
55+
msgstr ""
56+
57+
#: app/templates/base.html:35
58+
msgid "Sign up"
59+
msgstr ""
60+
61+
#: app/templates/base.html:40
62+
msgid "Agenda"
63+
msgstr ""
64+
65+
#: app/templates/profile.html:53
66+
msgid "Update name"
67+
msgstr ""
68+
69+
#: app/templates/profile.html:60 app/templates/profile.html:82
70+
#: app/templates/profile.html:103 app/templates/profile.html:127
71+
msgid "Save changes"
72+
msgstr ""
73+
74+
#: app/templates/profile.html:74
75+
msgid "Update email"
76+
msgstr ""
77+
78+
#: app/templates/profile.html:95
79+
msgid "Update description"
80+
msgstr ""
81+
82+
#: app/templates/profile.html:118
83+
msgid "Update photo"
84+
msgstr ""
85+
86+
#: app/templates/profile.html:144
87+
msgid "Settings"
88+
msgstr ""
89+
90+
#: app/templates/profile.html:161
91+
msgid "Features"
92+
msgstr ""
93+
94+
#: app/templates/profile.html:165
95+
msgid "Export my calendar"
96+
msgstr ""
97+
98+
#: app/templates/profile.html:168 app/templates/profile.html:171
99+
msgid "Your feature"
100+
msgstr ""
101+
102+
#: app/templates/profile.html:192
103+
msgid "Upcoming event on (date)"
104+
msgstr ""
105+
106+
#: app/templates/profile.html:203
107+
msgid "The Event (event)"
108+
msgstr ""
109+
110+
#: app/templates/profile.html:206
111+
msgid "Last updated (time) ago"
112+
msgstr ""
113+
114+
#: app/templates/profile.html:223
115+
msgid "Explore MeetUps near you"
116+
msgstr ""
117+
118+
#: app/templates/profile.html:232 app/templates/profile.html:241
119+
msgid "Your Card"
120+
msgstr ""
121+
122+
#: tests/test_language.py:39
123+
msgid "test python translation"
124+
msgstr ""
125+

app/locales/he/LC_MESSAGES/base.mo

557 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)