added translations support

This commit is contained in:
Daniel Tsvetkov 2020-07-08 14:38:55 +02:00
parent 92865d8783
commit fc28212ce0
20 changed files with 104 additions and 32 deletions

View File

@ -1 +1,2 @@
email,password,role_names email,password,role_names
admin@blog.pi2.dev,__SENSITIVE__.ADMIN_PASSWORD,admin
1 email password role_names
2 admin@blog.pi2.dev __SENSITIVE__.ADMIN_PASSWORD admin

View File

@ -0,0 +1,5 @@
[ignore: venv/**]
[ignore: migrations/**]
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View File

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Project</title> <title>{{ _("Project") }}</title>
<style type="text/css"> <style type="text/css">
body { body {
font-family: "Open Sans", Arial, sans-serif; font-family: "Open Sans", Arial, sans-serif;

View File

@ -1,9 +1,9 @@
<a href="{{ url_for('home') }}">Home</a> | <a href="{{ url_for('home') }}">{{ _("Home") }}</a> |
<div class="pull-right"> <div class="pull-right">
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<a href="#">{{ current_user.email }}</a> | <a href="#">{{ current_user.email }}</a> |
<a href="{{ url_for('security.logout') }}">Logout</a> | <a href="{{ url_for('security.logout') }}">{{ _("Logout") }}</a> |
{% else %} {% else %}
<a href="{{ url_for('security.login') }}">Login</a> | <a href="{{ url_for('security.login') }}">{{ _("Login") }}</a> |
{% endif %} {% endif %}
</div> </div>

View File

@ -0,0 +1,24 @@
# RENAME THIS FILE TO .yaml and use the structure below
#
name: Example # name of the model
searchable: # list of columns that are searchable
- body
interits: # list of inheritable classes, each line is one of (Ownable|Datable)
- Ownable
access:
- verb: all
login_required: true
roles_required:
- admin
- verb: get
login_required: false
- verb: list
login_required: false
- verb: table
login_required: false
- verb: search
login_required: false
columns:
- name: filename
- name: body
type: long_text

View File

@ -35,8 +35,6 @@ Usage $0 [ bootstrap | model | db_migrate | db_upgrade | db_populate | db_recrea
cert [DOMAIN] Install certificate cert [DOMAIN] Install certificate
" "
#!/usr/bin/env bash
HELP_TRANSLATION=" HELP_TRANSLATION="
USAGE ./manage.sh translate [extract|gen {lang}|compile|update] USAGE ./manage.sh translate [extract|gen {lang}|compile|update]
@ -47,8 +45,10 @@ USAGE ./manage.sh translate [extract|gen {lang}|compile|update]
" "
command_translate() { command_translate() {
shift
TRANSLATE_COMMAND=$1 TRANSLATE_COMMAND=$1
shift shift
source venv/bin/activate
case "$TRANSLATE_COMMAND" in case "$TRANSLATE_COMMAND" in
extract) pybabel extract -F translations/babel.cfg -o translations/messages.pot . extract) pybabel extract -F translations/babel.cfg -o translations/messages.pot .
;; ;;
@ -154,6 +154,9 @@ bootstrap() {
python manager.py db migrate -m "001" python manager.py db migrate -m "001"
_post_migrate _post_migrate
python manager.py db upgrade python manager.py db upgrade
git init .
git add .
git commit -m "Initial commit"
} }
run_in_prod() { run_in_prod() {

View File

@ -7,6 +7,9 @@ from importlib import import_module
from json import JSONEncoder from json import JSONEncoder
from uuid import uuid4 from uuid import uuid4
from flask import request
from flask_security import current_user
from config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE, SEARCH_INDEX_PATH, STATIC_DATA_DIR, basepath from config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE, SEARCH_INDEX_PATH, STATIC_DATA_DIR, basepath
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_migrate import upgrade as migrate_upgrade from flask_migrate import upgrade as migrate_upgrade
@ -21,7 +24,7 @@ from sqlalchemy import TypeDecorator
from sqlalchemy.ext.declarative import declared_attr, DeclarativeMeta from sqlalchemy.ext.declarative import declared_attr, DeclarativeMeta
from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy_utils import Choice from sqlalchemy_utils import Choice
from tww.lib import solve_query, resolve_timezone, dt_tz_translation, time_ago from tww.lib import solve_query, resolve_timezone, dt_tz_translation, time_ago, get_utcnow
from whooshalchemy import IndexService from whooshalchemy import IndexService
from oshipka.util.strings import camel_case_to_snake_case from oshipka.util.strings import camel_case_to_snake_case
from vm_gen.vm_gen import order_from_process_order from vm_gen.vm_gen import order_from_process_order
@ -40,6 +43,11 @@ class Ownable(object):
return db.relationship("User") return db.relationship("User")
class Datable(object):
created_dt = db.Column(db.UnicodeText())
updated_dt = db.Column(db.UnicodeText())
roles_users = db.Table('roles_users', roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
@ -236,11 +244,35 @@ def register_index_svc():
index_service.proxied.register_class(searchable) index_service.proxied.register_class(searchable)
def _init_translations(app):
from flask_babelex import Babel
babel = Babel(app)
@babel.localeselector
def get_locale():
# if a user is logged in, use the locale from the user settings
if current_user.is_authenticated:
return current_user.locale
# otherwise try to guess the language from the user accept
# header the browser transmits
return request.accept_languages.best_match(app.config.get('TRANSLATION_LANGUAGES', ['en']))
@babel.timezoneselector
def get_timezone():
if current_user is not None:
return current_user.timezone
def init_db(app): def init_db(app):
rv = False rv = False
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["WHOOSH_BASE"] = SEARCH_INDEX_PATH app.config["WHOOSH_BASE"] = SEARCH_INDEX_PATH
try:
from config import TRANSLATION_LANGUAGES
app.config["TRANSLATION_LANGUAGES"] = TRANSLATION_LANGUAGES
except:
app.config["TRANSLATION_LANGUAGES"] = ['en']
from oshipka.webapp import test_bp, oshipka_bp from oshipka.webapp import test_bp, oshipka_bp
app.register_blueprint(test_bp) app.register_blueprint(test_bp)
@ -249,6 +281,7 @@ def init_db(app):
db.init_app(app) db.init_app(app)
migrate.init_app(app, db) migrate.init_app(app, db)
security.init_app(app, user_datastore) security.init_app(app, user_datastore)
_init_translations(app)
register_filters(app) register_filters(app)

View File

@ -2,7 +2,7 @@
from sqlalchemy_utils import ChoiceType from sqlalchemy_utils import ChoiceType
[%- endif %] [%- endif %]
class [[ name ]](db.Model, ModelController): class [[ name ]](db.Model, ModelController[% for inherit in interits %], [[ inherit ]][% endfor %]):
[%- include "_model_choice_header_py" %] [%- include "_model_choice_header_py" %]
[%- include "_model_searchable_header_py" %] [%- include "_model_searchable_header_py" %]

View File

@ -3,7 +3,7 @@
<table> <table>
[%- for column in columns %] [%- for column in columns %]
<tr><td> <tr><td>
<label for="input-[[ name|camel_to_snake ]]-[[ column.name ]]">[[ column.name ]]</label>: <label for="input-[[ name|camel_to_snake ]]-[[ column.name ]]">{{ _("[[ column.name ]]") }}</label>:
</td><td> </td><td>
[%- if column.type in ['relationship'] %] [%- if column.type in ['relationship'] %]
[% if column.multiple %] [% if column.multiple %]
@ -12,7 +12,7 @@
<select id="input-[[ name|camel_to_snake ]]-[[ column.name ]]" name="[[ column.name ]]_id"> <select id="input-[[ name|camel_to_snake ]]-[[ column.name ]]" name="[[ column.name ]]_id">
[%- endif %] [%- endif %]
[%- if not column.secondary %] [%- if not column.secondary %]
<option selected="selected" value="">Choose...</option> <option selected="selected" value="">{{ _("Choose...") }}</option>
[%- endif %] [%- endif %]
{% if instance and instance.[[ column.name|pluralize ]] is defined %} {% if instance and instance.[[ column.name|pluralize ]] is defined %}
{% set [[ column.name|pluralize ]] = instance.[[ column.name|pluralize ]] %} {% set [[ column.name|pluralize ]] = instance.[[ column.name|pluralize ]] %}
@ -26,7 +26,7 @@
[%- elif column.type in ['choice', ] %] [%- elif column.type in ['choice', ] %]
<select id="input-[[ name|camel_to_snake ]]-[[ column.name ]]" <select id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
name="[[ column.name ]]"> name="[[ column.name ]]">
<option selected="selected" value="">Choose...</option> <option selected="selected" value="">{{ _("Choose...") }}</option>
[%- for value, display in column.choices.items() %] [%- for value, display in column.choices.items() %]
<option value="[[ value ]]" {% if instance and instance.[[ column.name ]] == "[[ value ]]" %}selected="selected"{% endif %}>[[ display ]]</option> <option value="[[ value ]]" {% if instance and instance.[[ column.name ]] == "[[ value ]]" %}selected="selected"{% endif %}>[[ display ]]</option>
[%- endfor %] [%- endfor %]
@ -40,6 +40,9 @@
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]" <input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
type="checkbox" value="1" name="_[[ column.name ]]" type="checkbox" value="1" name="_[[ column.name ]]"
/> />
[%- elif column.type in ['long_text', ] %]
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
name="[[ column.name ]]"></textarea>
[%- else %] [%- else %]
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]" <input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
type="text" name="[[ column.name ]]" autocomplete="off" type="text" name="[[ column.name ]]" autocomplete="off"

View File

@ -7,14 +7,14 @@
</video> </video>
[%- elif column.type in ['relationship'] %] [%- elif column.type in ['relationship'] %]
[%- if column.multiple %] [%- if column.multiple %]
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>[[ column.name|pluralize ]]</strong>: {{ instance.[[ column.name|pluralize ]] }}</li> <li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name|pluralize ]]") }}</strong>: {{ instance.[[ column.name|pluralize ]] }}</li>
[%- else %] [%- else %]
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>[[ column.name ]]</strong>: {{ instance.[[ column.name ]] }}</li> <li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{_("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]] }}</li>
[%- endif %] [%- endif %]
[%- elif column.type in ['bool', 'boolean', ] %] [%- elif column.type in ['bool', 'boolean', ] %]
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>[[ column.name ]]</strong>: {{ instance.[[ column.name ]]|bool }}</li> <li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]]|bool }}</li>
[%- else %] [%- else %]
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>[[ column.name ]]</strong>: {{ instance.[[ column.name ]] }}</li> <li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]] }}</li>
[%- endif %] [%- endif %]
{% endif %} {% endif %}
[%- endfor %] [%- endfor %]

View File

@ -3,10 +3,10 @@
<tr> <tr>
[%- for column in columns %] [%- for column in columns %]
{% if "[[ column.name ]]" not in skip_columns %} {% if "[[ column.name ]]" not in skip_columns %}
<th>[[ column.name ]]</th> <th>{{ _("[[ column.name ]]") }}</th>
{% endif %} {% endif %}
[%- endfor %] [%- endfor %]
<th>Actions</th> <th>{{ _("Actions") }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -6,5 +6,5 @@
- {{ instance.[[ display.secondary ]] }} - {{ instance.[[ display.secondary ]] }}
[%- endif %] [%- endif %]
[%- else %] [%- else %]
[[ name ]] {{ instance.id }} {{ _("[[ name ]]") }} {{ instance.id }}
[%- endif %] [%- endif %]

View File

@ -3,7 +3,7 @@
<table> <table>
[%- for column in columns %] [%- for column in columns %]
<tr><td> <tr><td>
<label for="input-[[ name|camel_to_snake ]]-[[ column.name ]]">[[ column.name ]]</label>: <label for="input-[[ name|camel_to_snake ]]-[[ column.name ]]">{{ _("[[ column.name ]]") }}</label>:
</td><td> </td><td>
[%- if column.type in ['relationship'] %] [%- if column.type in ['relationship'] %]
[%- if column.multiple %] [%- if column.multiple %]
@ -39,6 +39,9 @@
type="checkbox" name="_[[ column.name ]]" {% if instance.[[ column.name ]] %}checked=checked{% endif %} type="checkbox" name="_[[ column.name ]]" {% if instance.[[ column.name ]] %}checked=checked{% endif %}
value="1" value="1"
/> />
[%- elif column.type in ['long_text', ] %]
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
name="[[ column.name ]]">{{ instance.[[ column.name ]] }}</textarea>
[%- else %] [%- else %]
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]" <input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
value="{{ instance.[[ column.name ]] }}" value="{{ instance.[[ column.name ]] }}"

View File

@ -1,6 +1,6 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<h2>Create [[ name ]]</h2> <h2>{{ _("Create") }} {{_("[[ name ]]") }}</h2>
{% include "[[ name|camel_to_snake ]]/_create.html" %} {% include "[[ name|camel_to_snake ]]/_create.html" %}
{% endblock %} {% endblock %}

View File

@ -1,9 +1,9 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<a href="{{ url_for('list_[[ name|camel_to_snake ]]', uuid=instance.id) }}">list</a> | <a href="{{ url_for('list_[[ name|camel_to_snake ]]', uuid=instance.id) }}">{{ _("list") }}</a> |
<h2>{% include "[[ name|camel_to_snake ]]/_title.html" %}</h2> <h2>{% include "[[ name|camel_to_snake ]]/_title.html" %}</h2>
<a href="{{ url_for('update_[[ name|camel_to_snake ]]', uuid=instance.id, _next=request.path) }}">edit</a> | <a href="{{ url_for('update_[[ name|camel_to_snake ]]', uuid=instance.id, _next=request.path) }}">{{ _("edit") }}</a> |
<a href="{{ url_for('delete_[[ name|camel_to_snake ]]', uuid=instance.id) }}">delete</a> <a href="{{ url_for('delete_[[ name|camel_to_snake ]]', uuid=instance.id) }}">{{ _("delete") }}</a>
{% include "[[ name|camel_to_snake ]]/_get.html" %} {% include "[[ name|camel_to_snake ]]/_get.html" %}
{% endblock %} {% endblock %}

View File

@ -1,8 +1,8 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<h2>[[ name|pluralize ]]</h2> <h2>{{ _("[[ name|pluralize ]]") }}</h2>
<a href="{{ url_for('create_[[ name|camel_to_snake ]]') }}">Create</a> <a href="{{ url_for('create_[[ name|camel_to_snake ]]') }}">{{ _("Create") }}</a>
<br> <br>
{% include "[[ name|camel_to_snake ]]/_list.html" %} {% include "[[ name|camel_to_snake ]]/_list.html" %}
{% endblock %} {% endblock %}

View File

@ -1,8 +1,8 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<h2>Search results for [[ name|pluralize ]]</h2> <h2>{{ _("Search results for") }} {{ _("[[ name|pluralize ]]") }}</h2>
<a href="{{ url_for('create_[[ name|camel_to_snake ]]') }}">Create</a> <a href="{{ url_for('create_[[ name|camel_to_snake ]]') }}">{{ _("Create") }}</a>
<br> <br>
{% include "[[ name|camel_to_snake ]]/_search.html" %} {% include "[[ name|camel_to_snake ]]/_search.html" %}
{% endblock %} {% endblock %}

View File

@ -1,8 +1,8 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<h2>[[ name|pluralize ]]</h2> <h2>{{ _("[[ name|pluralize ]]") }}</h2>
<a href="{{ url_for('create_[[ name|camel_to_snake ]]') }}">Create</a> <a href="{{ url_for('create_[[ name|camel_to_snake ]]') }}">{{ _("Create") }}</a>
<br> <br>
{% include "[[ name|camel_to_snake ]]/_table.html" %} {% include "[[ name|camel_to_snake ]]/_table.html" %}
{% endblock %} {% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<h2>Edit {% include "[[ name|camel_to_snake ]]/_title.html" %}</h2> <h2>{{ _("Edit") }} {% include "[[ name|camel_to_snake ]]/_title.html" %}</h2>
{% include "[[ name|camel_to_snake ]]/_update.html" %} {% include "[[ name|camel_to_snake ]]/_update.html" %}
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
from oshipka.persistance import db, ModelController, index_service, LiberalBoolean from oshipka.persistance import db, ModelController, index_service, LiberalBoolean[% for inherit in interits %], [[ inherit ]][% endfor %]
[%- if imports %] [%- if imports %]
[%- for import in imports %] [%- for import in imports %]