├── flaskr.db ├── babel.cfg ├── translations └── de │ └── LC_MESSAGES │ ├── messages.mo │ └── messages.po ├── schema.sql ├── requirements.txt ├── config.py ├── templates ├── login.html ├── show_entries.html └── layout.html ├── .gitignore ├── static └── style.css ├── LICENSE ├── README.md ├── test_flaskr.py └── flaskr.py /flaskr.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/flask-demo-application/HEAD/flaskr.db -------------------------------------------------------------------------------- /babel.cfg: -------------------------------------------------------------------------------- 1 | [python: **.py] 2 | [jinja2: **/templates/**.html] 3 | extensions=jinja2.ext.autoescape,jinja2.ext.with_ 4 | -------------------------------------------------------------------------------- /translations/de/LC_MESSAGES/messages.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/flask-demo-application/HEAD/translations/de/LC_MESSAGES/messages.mo -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | drop table if exists entries; 2 | create table entries ( 3 | id integer primary key autoincrement, 4 | title text not null, 5 | 'text' text not null 6 | ); 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Babel==2.10.3 2 | click==8.1.3 3 | Flask==2.2.2 4 | Flask-Babel==2.0.0 5 | Flask-Phrase==1.1.0 6 | importlib-metadata==4.12.0 7 | itsdangerous==2.1.2 8 | Jinja2==3.1.2 9 | MarkupSafe==2.1.1 10 | pytz==2022.2.1 11 | Werkzeug==2.2.2 12 | zipp==3.8.1 13 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Flask App config 2 | DATABASE='./flaskr.db' 3 | DEBUG=True 4 | SECRET_KEY='development key' 5 | USERNAME='admin' 6 | PASSWORD='default' 7 | 8 | # Babel config 9 | LANGUAGES = { 10 | 'en': 'English', 11 | 'de': 'Deutsch' 12 | } 13 | 14 | # Phrase config 15 | PHRASEAPP_ENABLED = False 16 | PHRASEAPP_PREFIX = '{{__' 17 | PHRASEAPP_SUFFIX = '__}}' 18 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 |

{{ gettext('Login') }}

4 | {% if error %}

{{ gettext('Error') }}: {{ error }}{% endif %} 5 |

6 |
7 |
{{ gettext('Username') }}: 8 |
9 |
{{ gettext('Password') }}: 10 |
11 |
12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /templates/show_entries.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block body %} 3 | {% if session.logged_in %} 4 |
5 |
6 |
{{ gettext('Title') }}: 7 |
8 |
{{ gettext('Text') }}: 9 |
10 |
11 |
12 |
13 | {% endif %} 14 | 21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | bin/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | pyvenv.cfg 58 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | body { font-family: sans-serif; background: #eee; } 2 | a, h1, h2 { color: #377BA8; } 3 | h1, h2 { font-family: 'Georgia', serif; margin: 0; } 4 | h1 { border-bottom: 2px solid #eee; } 5 | h2 { font-size: 1.2em; } 6 | 7 | .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; 8 | padding: 0.8em; background: white; } 9 | .entries { list-style: none; margin: 0; padding: 0; } 10 | .entries li { margin: 0.8em 1.2em; } 11 | .entries li h2 { margin-left: -1em; } 12 | .add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } 13 | .add-entry dl { font-weight: bold; } 14 | .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; 15 | margin-bottom: 1em; background: #fafafa; } 16 | .flash { background: #CEE5F5; padding: 0.5em; 17 | border: 1px solid #AACBE2; } 18 | .error { background: #F0D6D6; padding: 0.5em; } 19 | -------------------------------------------------------------------------------- /templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | Flaskr 3 | 4 |
5 |

Flaskr

6 |
7 | {% if not session.logged_in %} 8 | {{ gettext('log in') }} 9 | {% else %} 10 | {{ gettext('log out') }} 11 | {% endif %} 12 |
13 | {% for message in get_flashed_messages() %} 14 |
{{ message }}
15 | {% endfor %} 16 | {% block body %}{% endblock %} 17 |
18 | 19 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 Dynport GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flask-demo-application (DEPRECATED) 2 | 3 | ![maintenance-status](https://img.shields.io/badge/maintenance-deprecated-red.svg) 4 | 5 | > This repository for the demo app of Phrase Strings In-Context Editor with `Flask-Phrase` has been deprecated and is no longer maintained. Please refer to the [Flask-Phrase](https://github.com/phrase/Flask-Phrase) repository, where the demo has been moved to and will be maintained. 6 | 7 |
8 | 9 | A simple Flask demo application showing [Phrase](https://phrase.com/) In-Context-Editor integration. Built on top of [Flaskr](http://flask.pocoo.org/docs/0.10/tutorial/introduction/), the Flask tutorial app. 10 | 11 | For a full step-by-step tutorial, check out our blog post: [Localization For Flask Applications](https://phrase.com/blog/posts/python-localization-flask-applications/) 12 | 13 | ## Install 14 | 15 | Clone this repository 16 | 17 | ``` 18 | git clone https://github.com/phrase/flask-demo-application 19 | ``` 20 | 21 | Install the required dependencies: 22 | 23 | ``` 24 | pip install flask flask-phrase 25 | ``` 26 | 27 | Configure your ProjectID in ```templates/layout.html``` 28 | 29 | ``` 30 | projectId: "YOUR-PROJECT-ID" 31 | ```` 32 | 33 | Run the app! 34 | 35 | ``` 36 | python flaskr.py 37 | ```` 38 | 39 | ## Further reading 40 | * [Phrase](https://phrase.com) 41 | * [Phrase Documentation](https://help.phrase.com) 42 | * [Step-by-Step Tutorial for Flask localization and Phrase integration](https://phrase.com/blog/posts/python-localization-flask-applications/) 43 | -------------------------------------------------------------------------------- /translations/de/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # German translations for PROJECT. 2 | # Copyright (C) 2015 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2015. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2015-01-23 15:59+0100\n" 11 | "PO-Revision-Date: 2015-01-23 15:44+0100\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language-Team: de \n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1)\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 1.3\n" 19 | 20 | #: flaskr.py:86 21 | msgid "New entry was successfully posted" 22 | msgstr "Neuer Eintrag erfolgreich veröffentlicht" 23 | 24 | #: flaskr.py:95 25 | msgid "Invalid username" 26 | msgstr "Benutzername ungültig" 27 | 28 | #: flaskr.py:97 29 | msgid "Invalid password" 30 | msgstr "Kennwort ungültig" 31 | 32 | #: flaskr.py:100 33 | msgid "You were logged in" 34 | msgstr "Einloggen erfolgreich" 35 | 36 | #: flaskr.py:108 37 | msgid "You were logged out" 38 | msgstr "Ausloggen erfolgreich " 39 | 40 | #: templates/layout.html:8 41 | msgid "log in" 42 | msgstr "Einloggen" 43 | 44 | #: templates/layout.html:10 45 | msgid "log out" 46 | msgstr "Ausloggen" 47 | 48 | #: templates/login.html:3 templates/login.html:11 49 | msgid "Login" 50 | msgstr "Einloggen" 51 | 52 | #: templates/login.html:4 53 | msgid "Error" 54 | msgstr "Fehler" 55 | 56 | #: templates/login.html:7 57 | msgid "Username" 58 | msgstr "Benutzername" 59 | 60 | #: templates/login.html:9 61 | msgid "Password" 62 | msgstr "Kennwort" 63 | 64 | #: templates/show_entries.html:6 65 | msgid "Title" 66 | msgstr "Betreff" 67 | 68 | #: templates/show_entries.html:8 69 | msgid "Text" 70 | msgstr "Text" 71 | 72 | #: templates/show_entries.html:10 73 | msgid "Share" 74 | msgstr "Teilen" 75 | 76 | #: templates/show_entries.html:18 77 | msgid "Unbelievable. No entries here so far" 78 | msgstr "Unglaublich. Bisher keine Einträge." 79 | 80 | -------------------------------------------------------------------------------- /test_flaskr.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Flaskr Tests 4 | ~~~~~~~~~~~~ 5 | 6 | Tests the Flaskr application. 7 | 8 | :copyright: (c) 2015 by Armin Ronacher. 9 | :license: BSD, see LICENSE for more details. 10 | """ 11 | 12 | import pytest 13 | 14 | import os 15 | import flaskr 16 | import tempfile 17 | 18 | 19 | @pytest.fixture 20 | def client(request): 21 | db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp() 22 | flaskr.app.config['TESTING'] = True 23 | client = flaskr.app.test_client() 24 | with flaskr.app.app_context(): 25 | flaskr.init_db() 26 | 27 | def teardown(): 28 | os.close(db_fd) 29 | os.unlink(flaskr.app.config['DATABASE']) 30 | request.addfinalizer(teardown) 31 | 32 | return client 33 | 34 | 35 | def login(client, username, password): 36 | return client.post('/login', data=dict( 37 | username=username, 38 | password=password 39 | ), follow_redirects=True) 40 | 41 | 42 | def logout(client): 43 | return client.get('/logout', follow_redirects=True) 44 | 45 | 46 | def test_empty_db(client): 47 | """Start with a blank database.""" 48 | rv = client.get('/') 49 | assert b'No entries here so far' in rv.data 50 | 51 | 52 | def test_login_logout(client): 53 | """Make sure login and logout works""" 54 | rv = login(client, flaskr.app.config['USERNAME'], 55 | flaskr.app.config['PASSWORD']) 56 | assert b'You were logged in' in rv.data 57 | rv = logout(client) 58 | assert b'You were logged out' in rv.data 59 | rv = login(client, flaskr.app.config['USERNAME'] + 'x', 60 | flaskr.app.config['PASSWORD']) 61 | assert b'Invalid username' in rv.data 62 | rv = login(client, flaskr.app.config['USERNAME'], 63 | flaskr.app.config['PASSWORD'] + 'x') 64 | assert b'Invalid password' in rv.data 65 | 66 | 67 | def test_messages(client): 68 | """Test that messages work""" 69 | login(client, flaskr.app.config['USERNAME'], 70 | flaskr.app.config['PASSWORD']) 71 | rv = client.post('/add', data=dict( 72 | title='', 73 | text='HTML allowed here' 74 | ), follow_redirects=True) 75 | assert b'No entries here so far' not in rv.data 76 | assert b'<Hello>' in rv.data 77 | assert b'HTML allowed here' in rv.data 78 | -------------------------------------------------------------------------------- /flaskr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Flaskr + Babel + Phrase 5 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Modified Flask demo app showing localization with Flask-Babel and Phrase 8 | 9 | """ 10 | 11 | from sqlite3 import dbapi2 as sqlite3 12 | from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash 13 | from flask_babel import Babel 14 | from flask_phrase import Phrase, gettext 15 | 16 | # Create our little application :) 17 | app = Flask(__name__) 18 | # Read config 19 | app.config.from_pyfile('config.py') 20 | 21 | # Hook Babel to our app 22 | babel = Babel(app) 23 | 24 | # Hook Phrase to our app 25 | phrase = Phrase(app) 26 | 27 | # Check the Accept-Language header and make a smart choice 28 | @babel.localeselector 29 | def get_locale(): 30 | return request.accept_languages.best_match(app.config['LANGUAGES'].keys()) 31 | 32 | 33 | def connect_db(): 34 | """Connects to the specific database.""" 35 | rv = sqlite3.connect(app.config['DATABASE']) 36 | rv.row_factory = sqlite3.Row 37 | return rv 38 | 39 | 40 | def get_db(): 41 | """Opens a new database connection if there is none yet for the 42 | current application context. 43 | """ 44 | if not hasattr(g, 'sqlite_db'): 45 | g.sqlite_db = connect_db() 46 | return g.sqlite_db 47 | 48 | 49 | @app.teardown_appcontext 50 | def close_db(error): 51 | """Closes the database again at the end of the request.""" 52 | if hasattr(g, 'sqlite_db'): 53 | g.sqlite_db.close() 54 | 55 | 56 | @app.route('/') 57 | def show_entries(): 58 | db = get_db() 59 | cur = db.execute('select title, text from entries order by id desc') 60 | entries = cur.fetchall() 61 | return render_template('show_entries.html', entries=entries) 62 | 63 | 64 | @app.route('/add', methods=['POST']) 65 | def add_entry(): 66 | if not session.get('logged_in'): 67 | abort(401) 68 | db = get_db() 69 | db.execute('insert into entries (title, text) values (?, ?)', 70 | [request.form['title'], request.form['text']]) 71 | db.commit() 72 | flash(gettext('New entry was successfully posted')) 73 | return redirect(url_for('show_entries')) 74 | 75 | 76 | @app.route('/login', methods=['GET', 'POST']) 77 | def login(): 78 | error = None 79 | if request.method == 'POST': 80 | if request.form['username'] != app.config['USERNAME']: 81 | error = gettext('Invalid username') 82 | elif request.form['password'] != app.config['PASSWORD']: 83 | error = gettext('Invalid password') 84 | else: 85 | session['logged_in'] = True 86 | flash(gettext('You were logged in')) 87 | return redirect(url_for('show_entries')) 88 | return render_template('login.html', error=error) 89 | 90 | 91 | @app.route('/logout') 92 | def logout(): 93 | session.pop('logged_in', None) 94 | flash(gettext('You were logged out')) 95 | return redirect(url_for('show_entries')) 96 | 97 | app.run() 98 | --------------------------------------------------------------------------------