add: support translations

This commit is contained in:
ziirish 2016-08-25 17:25:50 +02:00
parent b27a836bdc
commit d14d8fa58b
22 changed files with 437 additions and 62 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
*.pyc *.pyc
*.swp *.swp
*.mo
burpui-dev.cfg* burpui-dev.cfg*
burpui/RELEASE burpui/RELEASE
devel.sh devel.sh

10
babel.cfg Normal file
View file

@ -0,0 +1,10 @@
[python: **.py]
encoding = utf-8
[jinja2: **/templates/**.html]
encoding = utf-8
extensions=jinja2.ext.autoescape,jinja2.ext.with_
[jinja2: **/templates/**.js]
encoding = utf-8
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View file

@ -179,6 +179,7 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu
:returns: A :class:`burpui.server.BUIServer` object :returns: A :class:`burpui.server.BUIServer` object
""" """
from flask import g
from flask_login import LoginManager from flask_login import LoginManager
from flask_bower import Bower from flask_bower import Bower
from .utils import basic_login_from_request, ReverseProxied, lookup_file from .utils import basic_login_from_request, ReverseProxied, lookup_file
@ -186,6 +187,7 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu
from .routes import view from .routes import view
from .api import api, apibp from .api import api, apibp
from .ext.cache import cache from .ext.cache import cache
from .ext.i18n import babel, get_locale
logger = logging.getLogger('burp-ui') logger = logging.getLogger('burp-ui')
@ -261,6 +263,9 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu
app.gunicorn = gunicorn app.gunicorn = gunicorn
app.config['CFG'] = None app.config['CFG'] = None
# Some config
# FIXME: strange behavior when bundling errors # FIXME: strange behavior when bundling errors
# app.config['BUNDLE_ERRORS'] = True # app.config['BUNDLE_ERRORS'] = True
@ -391,6 +396,10 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu
force_scheduling_now() force_scheduling_now()
except: except:
pass pass
# Initialize i18n
babel.init_app(app)
# Create SQLAlchemy if enabled # Create SQLAlchemy if enabled
create_db(app) create_db(app)
@ -447,6 +456,10 @@ def init(conf=None, verbose=0, logfile=None, gunicorn=True, unittest=False, debu
if app.auth != 'none': if app.auth != 'none':
return basic_login_from_request(request, app) return basic_login_from_request(request, app)
@app.before_request
def before_request():
g.locale = get_locale()
return app return app

28
burpui/ext/i18n.py Normal file
View file

@ -0,0 +1,28 @@
# -*- coding: utf8 -*-
"""
.. module:: burpui.ext.i18n
:platform: Unix
:synopsis: Burp-UI external Internationalization module.
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
"""
from flask import request
from flask_babel import Babel
from flask_login import current_user
from ..config import config
babel = Babel()
LANGUAGES = {
'en': 'English',
'fr': 'Français',
}
config['LANGUAGES'] = LANGUAGES
@babel.localeselector
def get_locale():
locale = None
if current_user and not current_user.is_anonymous:
locale = getattr(current_user, 'language', None)
return locale or request.accept_languages.best_match(config['LANGUAGES'].keys())

View file

@ -7,12 +7,15 @@
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me> .. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
""" """
from .ext.i18n import LANGUAGES, get_locale
from flask_wtf import Form from flask_wtf import Form
from wtforms import TextField, PasswordField, BooleanField, validators from flask_babel import gettext
from wtforms import TextField, PasswordField, BooleanField, SelectField, validators
class LoginForm(Form): class LoginForm(Form):
username = TextField('Username', [validators.Length(min=2, max=25)]) username = TextField(gettext('Username'), [validators.Length(min=2, max=25)])
password = PasswordField('Password', [validators.Required()]) password = PasswordField(gettext('Password'), [validators.Required()])
remember = BooleanField('Remember me', [validators.Optional()]) language = SelectField(gettext('Language'), choices=LANGUAGES.items(), default=get_locale)
remember = BooleanField(gettext('Remember me'), [validators.Optional()])

View file

@ -44,6 +44,25 @@ def create_manager():
manager, migrate, app = create_manager() manager, migrate, app = create_manager()
@manager.command
def init_translation(language):
os.system('pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot burpui')
os.system('pybabel init -i messages.pot -d burpui/translations -l {}'.format(language))
os.unlink('messages.pot')
@manager.command
def update_translation():
os.system('pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot burpui')
os.system('pybabel update -i messages.pot -d burpui/translations')
os.unlink('messages.pot')
@manager.command
def compile_translation():
os.system('pybabel compile -f -d burpui/translations')
@manager.command @manager.command
def create_user(name, backend='BASIC', password=None, ask=False): def create_user(name, backend='BASIC', password=None, ask=False):
print('[*] Adding \'{}\' user...'.format(name)) print('[*] Adding \'{}\' user...'.format(name))

View file

@ -49,6 +49,7 @@ class UserHandler(BUIuser):
sess = session._get_current_object() sess = session._get_current_object()
self.active = False self.active = False
self.authenticated = sess.get('authenticated', False) self.authenticated = sess.get('authenticated', False)
self.language = sess.get('language', None)
self.backends = backends self.backends = backends
self.back = None self.back = None
self.name = name self.name = name
@ -83,6 +84,7 @@ class UserHandler(BUIuser):
self.authenticated = self.real.login(passwd) self.authenticated = self.real.login(passwd)
sess = session._get_current_object() sess = session._get_current_object()
sess['authenticated'] = self.authenticated sess['authenticated'] = self.authenticated
sess['language'] = self.language
return self.authenticated return self.authenticated
def get_id(self): def get_id(self):

View file

@ -12,12 +12,14 @@ import math
from flask import request, render_template, redirect, url_for, abort, \ from flask import request, render_template, redirect, url_for, abort, \
flash, Blueprint as FlaskBlueprint, session, current_app flash, Blueprint as FlaskBlueprint, session, current_app
from flask_login import login_user, login_required, logout_user, current_user from flask_login import login_user, login_required, logout_user, current_user
from flask_babel import gettext
from .server import BUIServer # noqa from .server import BUIServer # noqa
from ._compat import quote from ._compat import quote
from .forms import LoginForm from .forms import LoginForm
from .exceptions import BUIserverException from .exceptions import BUIserverException
from .utils import human_readable as _hr from .utils import human_readable as _hr
from .ext.i18n import LANGUAGES, get_locale
class Blueprint(FlaskBlueprint): class Blueprint(FlaskBlueprint):
@ -164,14 +166,14 @@ def live_monitor(server=None, name=None):
bui.cli.is_one_backup_running() bui.cli.is_one_backup_running()
if bui.standalone: if bui.standalone:
if not bui.cli.running: if not bui.cli.running:
flash('Sorry, there are no running backups', 'warning') flash(gettext('Sorry, there are no running backups'), 'warning')
return redirect(url_for('.home')) return redirect(url_for('.home'))
else: else:
run = False run = False
for a in bui.cli.servers: for a in bui.cli.servers:
run = run or (a in bui.cli.running and bui.cli.running[a]) run = run or (a in bui.cli.running and bui.cli.running[a])
if not run: if not run:
flash('Sorry, there are no running backups', 'warning') flash(gettext('Sorry, there are no running backups'), 'warning')
return redirect(url_for('.home')) return redirect(url_for('.home'))
return render_template( return render_template(
@ -191,7 +193,9 @@ def edit_server_initiated_restore(server=None, name=None):
to = None to = None
if not data or not data['found']: if not data or not data['found']:
flash( flash(
'Sorry, there are no restore file found for this client', gettext(
'Sorry, there are no restore file found for this client'
),
'warning' 'warning'
) )
return redirect(url_for('.home')) return redirect(url_for('.home'))
@ -227,8 +231,11 @@ def client_browse(server=None, name=None, backup=None, encrypted=None,
edit = bui.cli.is_server_restore(to, server) edit = bui.cli.is_server_restore(to, server)
if not edit or not edit['found']: if not edit or not edit['found']:
flash( flash(
'Sorry, there are no restore file found for this client', gettext(
'Sorry, there are no restore file found for this client'
),
'warning' 'warning'
) )
edit = None edit = None
else: else:
@ -369,14 +376,16 @@ def servers_report():
@view.route('/login', methods=['POST', 'GET']) @view.route('/login', methods=['POST', 'GET'])
def login(): def login():
form = LoginForm(request.form) form = LoginForm(request.form)
if form.validate_on_submit(): if form.validate_on_submit():
user = bui.uhandler.user(form.username.data) user = bui.uhandler.user(form.username.data)
user.language = form.language.data
if user.is_active and user.login(form.password.data): if user.is_active and user.login(form.password.data):
login_user(user, remember=form.remember.data) login_user(user, remember=form.remember.data)
flash('Logged in successfully', 'success') flash(gettext('Logged in successfully'), 'success')
return redirect(request.args.get("next") or url_for('.home')) return redirect(request.args.get("next") or url_for('.home'))
else: else:
flash('Wrong username or password', 'danger') flash(gettext('Wrong username or password'), 'danger')
return render_template('login.html', form=form, login=True) return render_template('login.html', form=form, login=True)
@ -386,6 +395,8 @@ def logout():
sess = session._get_current_object() sess = session._get_current_object()
if 'authenticated' in sess: if 'authenticated' in sess:
sess.pop('authenticated') sess.pop('authenticated')
if 'language' in sess:
sess.pop('language')
logout_user() logout_user()
return redirect(url_for('.home')) return redirect(url_for('.home'))

View file

@ -5,12 +5,12 @@
{% include "small_topbar.html" %} {% include "small_topbar.html" %}
<ul class="breadcrumb" style="margin-bottom: 5px;"> <ul class="breadcrumb" style="margin-bottom: 5px;">
{% if server -%} {% if server -%}
<li><a href="{{ url_for('view.home') }}">Home</a></li> <li><a href="{{ url_for('view.home') }}">{{ _('Home') }}</a></li>
<li><a href="{{ url_for('view.clients', server=server) }}">{{ server }} clients</a></li> <li><a href="{{ url_for('view.clients', server=server) }}">{{ server }} clients</a></li>
<li class="active">{{ cname }} overview</li> <li class="active">{{ cname }} {{ _('overview') }}</li>
{% else -%} {% else -%}
<li><a href="{{ url_for('view.home') }}">Home</a></li> <li><a href="{{ url_for('view.home') }}">{{ _('Home') }}</a></li>
<li class="active">{{ cname }} overview</li> <li class="active">{{ cname }} {{ _('overview') }}</li>
{% endif -%} {% endif -%}
</ul> </ul>
<br /> <br />
@ -18,22 +18,22 @@
<h2 class="sub-header">Backups</h2> <h2 class="sub-header">Backups</h2>
<p> <p>
Toggle column: <a class="toggle-vis" data-column="0" href="#">Number</a> - Toggle column: <a class="toggle-vis" data-column="0" href="#">{{ _('Number') }}</a> -
<a class="toggle-vis" data-column="1" href="#">Date</a> - <a class="toggle-vis" data-column="1" href="#">Date</a> -
<a class="toggle-vis" data-column="2" href="#">Bytes received</a> - <a class="toggle-vis" data-column="2" href="#">{{ _('Bytes received') }}</a> -
<a class="toggle-vis" data-column="3" href="#">Estimated size</a> - <a class="toggle-vis" data-column="3" href="#">{{ _('Estimated size') }}</a> -
<a class="toggle-vis" data-column="4" href="#">Deletable</a> - <a class="toggle-vis" data-column="4" href="#">{{ _('Deletable') }}</a> -
<a class="toggle-vis" data-column="5" href="#">Status</a> <a class="toggle-vis" data-column="5" href="#">Status</a>
</p> </p>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover nowrap" id="table-client" width="100%"> <table class="table table-striped table-hover nowrap" id="table-client" width="100%">
<thead> <thead>
<tr> <tr>
<th>Number</th> <th>{{ _('Number') }}</th>
<th class="desktop">Date</th> <th class="desktop">Date</th>
<th class="desktop">Bytes received</th> <th class="desktop">{{ _('Bytes received') }}</th>
<th class="desktop">Estimated size</th> <th class="desktop">{{ _('Estimated size') }}</th>
<th class="desktop">Deletable</th> <th class="desktop">{{ _('Deletable') }}</th>
<th class="desktop">Status</th> <th class="desktop">Status</th>
</tr> </tr>
</thead> </thead>
@ -41,24 +41,24 @@
</tbody> </tbody>
</table> </table>
<div class="alert alert-dismissable alert-danger" id="client-alert" style="display: none;"> <div class="alert alert-dismissable alert-danger" id="client-alert" style="display: none;">
<span class="glyphicon glyphicon-exclamation-sign"></span> <strong>Sorry!</strong> There are no backups for this client. <span class="glyphicon glyphicon-exclamation-sign"></span> <strong>{{ _('Sorry!') }}</strong> {{ _('There are no backups for this client.') }}
</div> </div>
</div> </div>
<br /> <br />
<div id="left" class="form-inline col-md-6"> <div id="left" class="form-inline col-md-6">
<div class="edit-restore" style="display: none;"> <div class="edit-restore" style="display: none;">
<a href="{{ url_for('view.edit_server_initiated_restore', name=cname, server=server) }}" id="btn-edit-restore" class="btn btn-info"><span class="glyphicon glyphicon-pencil"></span>&nbsp;Edit restore</a> <a href="{{ url_for('view.edit_server_initiated_restore', name=cname, server=server) }}" id="btn-edit-restore" class="btn btn-info"><span class="glyphicon glyphicon-pencil"></span>&nbsp;{{ _('Edit restore') }}</a>
</div> </div>
<div class="scheduled-backup"> <div class="scheduled-backup">
<button type="button" id="btn-schedule-backup" class="btn btn-info"><span class="glyphicon glyphicon-time"></span>&nbsp;Schedule backup</button> <button type="button" id="btn-schedule-backup" class="btn btn-info"><span class="glyphicon glyphicon-time"></span>&nbsp;{{ _('Schedule backup') }}</button>
</div> </div>
</div> </div>
<div id="right" class="form-inline col-md-6 text-right"> <div id="right" class="form-inline col-md-6 text-right">
<div class="edit-restore" style="display: none;"> <div class="edit-restore" style="display: none;">
<button type="button" id="btn-cancel-restore" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span>&nbsp;Cancel restore</button> <button type="button" id="btn-cancel-restore" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span>&nbsp;{{ _('Cancel restore') }}</button>
</div> </div>
<div class="cancel-backup" style="display: none;"> <div class="cancel-backup" style="display: none;">
<button type="button" id="btn-cancel-backup" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span>&nbsp;Cancel backup</button> <button type="button" id="btn-cancel-backup" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span>&nbsp;{{ _('Cancel backup') }}</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,10 +5,10 @@
{% include "small_topbar.html" %} {% include "small_topbar.html" %}
<ul class="breadcrumb" style="margin-bottom: 5px;"> <ul class="breadcrumb" style="margin-bottom: 5px;">
{% if server -%} {% if server -%}
<li><a href="{{ url_for('view.home') }}">Home</a></li> <li><a href="{{ url_for('view.home') }}">{{ _('Home') }}</a></li>
<li class="active">{{ server }} clients</li> <li class="active">{{ server }} clients</li>
{% else -%} {% else -%}
<li class="active">Home</li> <li class="active">{{ _('Home') }}</li>
{% endif -%} {% endif -%}
</ul> </ul>
<br /> <br />
@ -19,9 +19,9 @@
<table class="table table-striped table-hover nowrap" id="table-clients" width="100%"> <table class="table table-striped table-hover nowrap" id="table-clients" width="100%">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>{{ _('Name') }}</th>
<th class="desktop">State</th> <th class="desktop">{{ _('State') }}</th>
<th class="desktop">Last Backup</th> <th class="desktop">{{ _('Last Backup') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -56,6 +56,12 @@
<!-- Typeadhead + Bloodhound Javascript <!-- Typeadhead + Bloodhound Javascript
================================================== --> ================================================== -->
<script src="{{ url_for('bower.static', filename='typeahead.js/dist/typeahead.bundle.min.js') }}"></script> <script src="{{ url_for('bower.static', filename='typeahead.js/dist/typeahead.bundle.min.js') }}"></script>
<!-- MomentJS
================================================== -->
<script src="{{ url_for('bower.static', filename='moment/min/moment.min.js') }}"></script>
{% if g.locale != 'en' -%}
<script src="{{ url_for('bower.static', filename='moment/locale/{}.js'.format(g.locale)) }}"></script>
{% endif -%}
{% if report -%} {% if report -%}
<!-- d3 + nvd3 Javascript <!-- d3 + nvd3 Javascript
================================================== --> ================================================== -->
@ -101,7 +107,6 @@
================================================== --> ================================================== -->
<script src="{{ url_for('bower.static', filename='angular-bootstrap/ui-bootstrap.min.js') }}"></script> <script src="{{ url_for('bower.static', filename='angular-bootstrap/ui-bootstrap.min.js') }}"></script>
<script src="{{ url_for('bower.static', filename='angular-bootstrap/ui-bootstrap-tpls.min.js') }}"></script> <script src="{{ url_for('bower.static', filename='angular-bootstrap/ui-bootstrap-tpls.min.js') }}"></script>
<script src="{{ url_for('bower.static', filename='moment/min/moment.min.js') }}"></script>
<script src="{{ url_for('bower.static', filename='angular-ui-calendar/src/calendar.js') }}"></script> <script src="{{ url_for('bower.static', filename='angular-ui-calendar/src/calendar.js') }}"></script>
<script src="{{ url_for('bower.static', filename='fullcalendar/dist/fullcalendar.min.js') }}"></script> <script src="{{ url_for('bower.static', filename='fullcalendar/dist/fullcalendar.min.js') }}"></script>
<script src="{{ url_for('bower.static', filename='fullcalendar/dist/gcal.js') }}"></script> <script src="{{ url_for('bower.static', filename='fullcalendar/dist/gcal.js') }}"></script>

View file

@ -4,9 +4,10 @@
{% include "notifications.html" %} {% include "notifications.html" %}
<div class="main"> <div class="main">
<div class="wrapper"> <div class="wrapper">
{% call macros.render_form(form, action_text='Login', class_='form-signin', btn_class='btn btn-info btn-lg btn-primary btn-block') %} {% call macros.render_form(form, action_text=_('Login'), class_='form-signin', btn_class='btn btn-info btn-lg btn-primary btn-block') %}
{{ macros.render_field(form.username, placeholder='Username', type='text', autofocus=true) }} {{ macros.render_field(form.username, placeholder=_('Username'), type='text', autofocus=true) }}
{{ macros.render_field(form.password, placeholder='Password', type='password') }} {{ macros.render_field(form.password, placeholder=_('Password'), type='password') }}
{{ macros.render_field(form.language) }}
{{ macros.render_checkbox_field(form.remember) }} {{ macros.render_checkbox_field(form.remember) }}
{% endcall %} {% endcall %}
</div> </div>

View file

@ -11,7 +11,6 @@
{% macro render_field(field, label_visible=true) -%} {% macro render_field(field, label_visible=true) -%}
<div class="form-group {% if field.errors %}has-error{% endif %} {{ kwargs.pop('class_', '') }}"> <div class="form-group {% if field.errors %}has-error{% endif %} {{ kwargs.pop('class_', '') }}">
{# {% if (field.id != 'csrf_token' or field.type != 'HiddenField' or field.type !='CSRFTokenField') and label_visible %} #}
{% if (field.id != 'csrf_token' or field.type != 'HiddenField') %} {% if (field.id != 'csrf_token' or field.type != 'HiddenField') %}
<label for="{{ field.id }}" class="control-label">{{ field.label }}</label> <label for="{{ field.id }}" class="control-label">{{ field.label }}</label>
{% endif %} {% endif %}
@ -88,6 +87,8 @@
{{ render_checkbox_field(f) }} {{ render_checkbox_field(f) }}
{% elif f.type == 'RadioField' %} {% elif f.type == 'RadioField' %}
{{ render_radio_field(f) }} {{ render_radio_field(f) }}
{% elif f.type == 'SelectField' %}
{{ render_select_field(f) }}
{% else %} {% else %}
{{ render_field(f) }} {{ render_field(f) }}
{% endif %} {% endif %}

View file

@ -4,16 +4,16 @@
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main"> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
{% include "small_topbar.html" %} {% include "small_topbar.html" %}
<ul class="breadcrumb" style="margin-bottom: 5px;"> <ul class="breadcrumb" style="margin-bottom: 5px;">
<li class="active">Home</li> <li class="active">{{ _('Home') }}</li>
</ul> </ul>
<br /> <br />
<h1 class="page-header">Servers</h1> <h1 class="page-header">{{ _('Servers') }}</h1>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover nowrap" id="table-servers" width="100%"> <table class="table table-striped table-hover nowrap" id="table-servers" width="100%">
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>{{ _('Name') }}</th>
<th class="desktop">Clients</th> <th class="desktop">Clients</th>
<th class="desktop">Status</th> <th class="desktop">Status</th>
</tr> </tr>

View file

@ -11,32 +11,32 @@
<ul class="nav nav-sidebar"> <ul class="nav nav-sidebar">
<li {% if overview %}class="active"{% endif %}> <li {% if overview %}class="active"{% endif %}>
{% if backup %} {% if backup %}
<a href="{{ url_for('view.client_browse', name=cname, server=server, backup=nbackup) }}"><span class="glyphicon glyphicon-th"></span>&nbsp;Overview</a> <a href="{{ url_for('view.client_browse', name=cname, server=server, backup=nbackup) }}"><span class="glyphicon glyphicon-th"></span>&nbsp;{{ _('Overview') }}</a>
{% elif client %} {% elif client %}
<a href="{{ url_for('view.client', name=cname, server=server) }}"><span class="glyphicon glyphicon-th"></span>&nbsp;Overview</a> <a href="{{ url_for('view.client', name=cname, server=server) }}"><span class="glyphicon glyphicon-th"></span>&nbsp;{{ _('Overview') }}</a>
{% elif clients %} {% elif clients %}
<a href="{{ url_for('view.clients', server=server) }}"><span class="glyphicon glyphicon-th"></span>&nbsp;Overview</a> <a href="{{ url_for('view.clients', server=server) }}"><span class="glyphicon glyphicon-th"></span>&nbsp;{{ _('Overview') }}</a>
{% elif servers %} {% elif servers %}
<a href="{{ url_for('view.servers') }}"><span class="glyphicon glyphicon-th"></span>&nbsp;Overview</a> <a href="{{ url_for('view.servers') }}"><span class="glyphicon glyphicon-th"></span>&nbsp;{{ _('Overview') }}</a>
{% else %} {% else %}
<a href="#"><span class="glyphicon glyphicon-th"></span>&nbsp;Overview</a> <a href="#"><span class="glyphicon glyphicon-th"></span>&nbsp;{{ _('Overview') }}</a>
{% endif %} {% endif %}
</li> </li>
<li {% if report %}class="active"{% endif %}> <li {% if report %}class="active"{% endif %}>
{% if client and not backup %} {% if client and not backup %}
<a href="{{ url_for('view.client_report', name=cname, server=server) }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;Reports</a> <a href="{{ url_for('view.client_report', name=cname, server=server) }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;{{ _('Reports') }}</a>
{% elif clients %} {% elif clients %}
<a href="{{ url_for('view.clients_report', server=server) }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;Reports</a> <a href="{{ url_for('view.clients_report', server=server) }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;{{ _('Reports') }}</a>
{% elif backup %} {% elif backup %}
<a href="{{ url_for('view.backup_report', name=cname, backup=nbackup, server=server) }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;Reports</a> <a href="{{ url_for('view.backup_report', name=cname, backup=nbackup, server=server) }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;{{ _('Reports') }}</a>
{% elif servers %} {% elif servers %}
<a href="{{ url_for('view.servers_report') }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;Reports</a> <a href="{{ url_for('view.servers_report') }}"><span class="glyphicon glyphicon-stats"></span>&nbsp;{{ _('Reports') }}</a>
{% else %} {% else %}
<a href="#"><span class="glyphicon glyphicon-stats"></span>&nbsp;Reports</a> <a href="#"><span class="glyphicon glyphicon-stats"></span>&nbsp;{{ _('Reports') }}</a>
{% endif %} {% endif %}
</li> </li>
<li {% if calendar %} class="active"{% endif %}> <li {% if calendar %} class="active"{% endif %}>
<a href="{{ url_for('view.calendar', client=cname, server=server) }}"><span class="glyphicon glyphicon-calendar"></span>&nbsp;Calendar</a> <a href="{{ url_for('view.calendar', client=cname, server=server) }}"><span class="glyphicon glyphicon-calendar"></span>&nbsp;{{ _('Calendar') }}</a>
</li> </li>
</ul> </ul>
{% endif -%} {% endif -%}

View file

@ -1,6 +1,6 @@
<div id="navbar-config"> <div id="navbar-config">
<h4>Configuration navigation</h4> <h4>{{ _('Configuration navigation') }}</h4>
<ul class="nav nav-sidebar bui-scrollspy"> <ul class="nav nav-sidebar bui-scrollspy">
<li class="active" data-target="#boolean"><a href="#boolean">Booleans</a></li> <li class="active" data-target="#boolean"><a href="#boolean">Booleans</a></li>
<li data-target="#string"><a href="#string">Strings</a></li> <li data-target="#string"><a href="#string">Strings</a></li>
@ -8,7 +8,7 @@
<li data-target="#multi"><a href="#multi">Multi</a></li> <li data-target="#multi"><a href="#multi">Multi</a></li>
<li data-target="#includes_source"><a href="#includes_source">Includes</a></li> <li data-target="#includes_source"><a href="#includes_source">Includes</a></li>
</ul> </ul>
<h4>Client to configure</h4> <h4>{{ _('Client to configure') }}</h4>
<ul class="nav nav-sidebar" ng-cloak> <ul class="nav nav-sidebar" ng-cloak>
{% raw %} {% raw %}
<li> <li>
@ -28,7 +28,7 @@
<li> <li>
<form action="{{ url_for('api.new_client', server=server) }}" method="POST" ng-submit="createClient($event)"> <form action="{{ url_for('api.new_client', server=server) }}" method="POST" ng-submit="createClient($event)">
<div class="input-group"> <div class="input-group">
<input class="form-control" type="text" name="newclient" id="newclient" placeholder="Create new client"> <input class="form-control" type="text" name="newclient" id="newclient" placeholder="{{ _('Create new client') }}">
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-success" type="submit"><span class="glyphicon glyphicon-plus"></span></button> <button class="btn btn-success" type="submit"><span class="glyphicon glyphicon-plus"></span></button>
</span> </span>

View file

@ -2,7 +2,7 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span> <span class="sr-only">{{ _('Toggle navigation') }}</span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
@ -13,14 +13,14 @@
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li class="detail {% if about %}active{% endif %}"> <li class="detail {% if about %}active{% endif %}">
<a href="{{ url_for('view.about') }}"> <a href="{{ url_for('view.about') }}">
<span class="glyphicon glyphicon-question-sign"></span><span class="dtl">&nbsp;About</span> <span class="glyphicon glyphicon-question-sign"></span><span class="dtl">&nbsp;{{ _('About') }}</span>
</a> </a>
</li> </li>
{% if not login -%} {% if not login -%}
{% if not config.STANDALONE -%} {% if not config.STANDALONE -%}
<li class="detail {% if not server and not about %}active{% endif %}"> <li class="detail {% if not server and not about %}active{% endif %}">
<a href="{{ url_for('view.home') }}"> <a href="{{ url_for('view.home') }}">
<span class="glyphicon glyphicon-hdd"></span><span class="dtl">&nbsp;Servers</span> <span class="glyphicon glyphicon-hdd"></span><span class="dtl">&nbsp;{{ _('Servers') }}</span>
</a> </a>
</li> </li>
{% if server -%} {% if server -%}
@ -40,32 +40,32 @@
{% if config.STANDALONE or server -%} {% if config.STANDALONE or server -%}
<li class="detail {% if settings %}active{% endif %}"> <li class="detail {% if settings %}active{% endif %}">
<a href="{{ url_for('view.settings', server=server) }}"> <a href="{{ url_for('view.settings', server=server) }}">
<span class="glyphicon glyphicon-wrench"></span><span class="dtl">&nbsp;Settings</span> <span class="glyphicon glyphicon-wrench"></span><span class="dtl">&nbsp;{{ _('Settings') }}</span>
</a> </a>
</li> </li>
{% endif -%} {% endif -%}
<li class="detail {% if live %}active{% endif %}"> <li class="detail {% if live %}active{% endif %}">
<a href="{{ url_for('view.live_monitor', server=server) }}"> <a href="{{ url_for('view.live_monitor', server=server) }}">
<span id="toblink" class="glyphicon glyphicon-screenshot"></span><span class="dtl">&nbsp;Live monitor</span> <span id="toblink" class="glyphicon glyphicon-screenshot"></span><span class="dtl">&nbsp;{{ _('Live monitor') }}</span>
</a> </a>
</li> </li>
{% if current_user and current_user.is_authenticated -%} {% if current_user and current_user.is_authenticated -%}
<li class="detail"> <li class="detail">
<a href="{{ url_for('view.logout') }}"> <a href="{{ url_for('view.logout') }}">
<span class="glyphicon glyphicon-log-out"></span><span class="dtl">&nbsp;Logout<small>({{ current_user.get_id() }})</small></span> <span class="glyphicon glyphicon-log-out"></span><span class="dtl">&nbsp;{{ _('Logout') }}<small>({{ current_user.get_id() }})</small></span>
</a> </a>
</li> </li>
{% endif -%} {% endif -%}
<li {% if live %}ng-click="refresh"{% endif %}> <li {% if live %}ng-click="refresh"{% endif %}>
<a id="refresh" href="#"> <a id="refresh" href="#">
<span class="glyphicon glyphicon-refresh"></span><span class="hidden-md hidden-lg">&nbsp;Refresh</span> <span class="glyphicon glyphicon-refresh"></span><span class="hidden-md hidden-lg">&nbsp;{{ _('Refresh') }}</span>
</a> </a>
</li> </li>
{% endif -%} {% endif -%}
</ul> </ul>
{% if not login -%} {% if not login -%}
<form class="navbar-form navbar-right" id="search"> <form class="navbar-form navbar-right" id="search">
<input type="text" class="form-control" id="input-client" placeholder="Search client..." autocomplete="off"> <input type="text" class="form-control" id="input-client" placeholder="{{ _('Search client...') }}" autocomplete="off">
</form> </form>
{% endif -%} {% endif -%}
</div> </div>

View file

@ -0,0 +1,178 @@
# French translations for Burp-UI.
# Copyright (C) 2016 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# Ziirish <hi+burpui@ziirish.me>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2016-08-25 17:17+0200\n"
"PO-Revision-Date: 2016-08-25 15:19+0200\n"
"Last-Translator: Ziirish <hi+burpui@ziirish.me>\n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\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.3.4\n"
#: burpui/forms.py:18 burpui/templates/login.html:8
msgid "Username"
msgstr "Utilisateur"
#: burpui/forms.py:19 burpui/templates/login.html:9
msgid "Password"
msgstr "Mot de passe"
#: burpui/forms.py:20
msgid "Language"
msgstr "Langue"
#: burpui/forms.py:21
msgid "Remember me"
msgstr "Rester connecté"
#: burpui/routes.py:169 burpui/routes.py:176
msgid "Sorry, there are no running backups"
msgstr "Désolé, il n'y a pas de backups"
#: burpui/routes.py:196 burpui/routes.py:234
msgid "Sorry, there are no restore file found for this client"
msgstr "Désolé, il n'y a pas restoration prévue pour ce client"
#: burpui/routes.py:385
msgid "Logged in successfully"
msgstr "Connecté avec succès"
#: burpui/routes.py:388
msgid "Wrong username or password"
msgstr "Mauvais nom d'utilisateur ou mot de passe"
#: burpui/templates/client.html:8 burpui/templates/client.html:12
#: burpui/templates/clients.html:8 burpui/templates/clients.html:11
#: burpui/templates/servers.html:7
msgid "Home"
msgstr "Accueil"
#: burpui/templates/client.html:10 burpui/templates/client.html:13
msgid "overview"
msgstr "aperçu"
#: burpui/templates/client.html:21 burpui/templates/client.html:32
msgid "Number"
msgstr "Numéro"
#: burpui/templates/client.html:23 burpui/templates/client.html:34
msgid "Bytes received"
msgstr "Octets reçus"
#: burpui/templates/client.html:24 burpui/templates/client.html:35
msgid "Estimated size"
msgstr "Taille (estimation)"
#: burpui/templates/client.html:25 burpui/templates/client.html:36
msgid "Deletable"
msgstr "Supprimable"
#: burpui/templates/client.html:44
msgid "Sorry!"
msgstr "Désolé!"
#: burpui/templates/client.html:44
msgid "There are no backups for this client."
msgstr "Il n'y a pas de sauvegardes pour ce client."
#: burpui/templates/client.html:50
msgid "Edit restore"
msgstr "Éditer restauration"
#: burpui/templates/client.html:53
msgid "Schedule backup"
msgstr "Planifier sauvegarde"
#: burpui/templates/client.html:58
msgid "Cancel restore"
msgstr "Annuler restauration"
#: burpui/templates/client.html:61
msgid "Cancel backup"
msgstr "Annuler sauvegarde"
#: burpui/templates/clients.html:22 burpui/templates/servers.html:16
msgid "Name"
msgstr "Nom"
#: burpui/templates/clients.html:23
msgid "State"
msgstr "État"
#: burpui/templates/clients.html:24
msgid "Last Backup"
msgstr "Dernière sauvegarde"
#: burpui/templates/login.html:7
msgid "Login"
msgstr "Se connecter"
#: burpui/templates/servers.html:10 burpui/templates/topbar.html:23
msgid "Servers"
msgstr "Serveurs"
#: burpui/templates/sidebar.html:14 burpui/templates/sidebar.html:16
#: burpui/templates/sidebar.html:18 burpui/templates/sidebar.html:20
#: burpui/templates/sidebar.html:22
msgid "Overview"
msgstr "Aperçu"
#: burpui/templates/sidebar.html:27 burpui/templates/sidebar.html:29
#: burpui/templates/sidebar.html:31 burpui/templates/sidebar.html:33
#: burpui/templates/sidebar.html:35
msgid "Reports"
msgstr "Rapports"
#: burpui/templates/sidebar.html:39
msgid "Calendar"
msgstr "Calendrier"
#: burpui/templates/sideconfig.html:3
msgid "Configuration navigation"
msgstr "Navigation"
#: burpui/templates/sideconfig.html:11
msgid "Client to configure"
msgstr "Client à configurer"
#: burpui/templates/sideconfig.html:31
msgid "Create new client"
msgstr "Créer un nouveau client"
#: burpui/templates/topbar.html:5
msgid "Toggle navigation"
msgstr "Afficher menu"
#: burpui/templates/topbar.html:16
msgid "About"
msgstr "À propos"
#: burpui/templates/topbar.html:43
msgid "Settings"
msgstr "Paramètres"
#: burpui/templates/topbar.html:49
msgid "Live monitor"
msgstr "Moniteur temps réél"
#: burpui/templates/topbar.html:55
msgid "Logout"
msgstr "Se déconnecter"
#: burpui/templates/topbar.html:61
msgid "Refresh"
msgstr "Rafraîchir"
#: burpui/templates/topbar.html:68
msgid "Search client..."
msgstr "Rechercher client..."

View file

@ -19,6 +19,105 @@ You can financially support the project if you find it useful or if you would
like to sponsor a feature. Details on my `website <https://ziirish.info/>`__. like to sponsor a feature. Details on my `website <https://ziirish.info/>`__.
Translating
-----------
Translations are very welcome!
If you are willing to help, you will need some tools:
::
pip install Flask-Babel
Then you need to fork the project retrieve the sources:
::
git clone https://git.ziirish.me/<your_login>/burp-ui.git
cd burp-ui
You can have the list of available languages by running:
::
ls burpui/translations
New language
^^^^^^^^^^^^
If your language is not listed, you can create a new translation running the
following command:
::
./bui-manage init_translation <country_code> # where <country_code> can be "de", "ru", etc.
Update translation
^^^^^^^^^^^^^^^^^^
If you want to update an existing (and/or un-complete) translation, you probably
want to have a look at the *templates* files.
An un-translated file will contain things like:
::
<h1>Some title</h1>
The string *Some title* won't be translated as is.
You need to update the template like this:
::
<h1>{{ _('Some title') }}</h1>
Then you can update the translation files with the following command:
::
./bui-manage update_translation
Translation
^^^^^^^^^^^
Now you can proceed the translation in the file
*burpui/translations/<country_code>/LC_MESSAGES/messages.po*.
It looks like:
::
#: burpui/forms.py:18 burpui/templates/login.html:8
msgid "Username"
msgstr ""
You just have to put the translations in the *msgstr* line like:
::
#: burpui/forms.py:18 burpui/templates/login.html:8
msgid "Username"
msgstr "Utilisateur"
Once it's done, you can push the sources and create a Merge Request on GitLab:
::
git checkout -b translation-<country_code>
git add burpui/translations/<country_code>/LC_MESSAGES/messages.po
git commit -m "<country_code> translation"
git push -u origin translation-<country_code>
Issues / Bugs Issues / Bugs
------------- -------------

View file

@ -1,6 +1,7 @@
Flask==0.11.1 Flask==0.11.1
Flask-Login==0.3.2 Flask-Login==0.3.2
Flask-Bower==1.2.1 Flask-Bower==1.2.1
Flask-Babel==0.11.1
Flask-WTF==0.12 Flask-WTF==0.12
flask-restplus==0.9.2 flask-restplus==0.9.2
Flask-Cache==0.13.1 Flask-Cache==0.13.1

View file

@ -1,6 +1,7 @@
Flask==0.11.1 Flask==0.11.1
Flask-Login==0.3.2 Flask-Login==0.3.2
Flask-Bower==1.2.1 Flask-Bower==1.2.1
Flask-Babel==0.11.1
Flask-WTF==0.12 Flask-WTF==0.12
flask-restplus==0.9.2 flask-restplus==0.9.2
Flask-Cache==0.13.1 Flask-Cache==0.13.1

View file

@ -71,6 +71,7 @@ class BuildStatic(Command):
def run(self): def run(self):
os.chdir(ROOT) os.chdir(ROOT)
log.info("getting revision number") log.info("getting revision number")
call('./bui-manage compile_translation'.split())
try: try:
branch = check_output('sed s@^.*/@@g .git/HEAD'.split()).rstrip() branch = check_output('sed s@^.*/@@g .git/HEAD'.split()).rstrip()
ver = open(os.path.join('burpui', 'VERSION')).read().rstrip() ver = open(os.path.join('burpui', 'VERSION')).read().rstrip()
@ -122,6 +123,7 @@ class BuildStatic(Command):
'burpui/static/vendor/angular-strap/dist/angular-strap.tpl.min.js', 'burpui/static/vendor/angular-strap/dist/angular-strap.tpl.min.js',
'burpui/static/vendor/angular-onbeforeunload/build/angular-onbeforeunload.js', 'burpui/static/vendor/angular-onbeforeunload/build/angular-onbeforeunload.js',
'burpui/static/vendor/moment/min/moment.min.js', 'burpui/static/vendor/moment/min/moment.min.js',
'burpui/static/vendor/moment/locale/fr.js',
'burpui/static/vendor/angular-ui-calendar/src/calendar.js', 'burpui/static/vendor/angular-ui-calendar/src/calendar.js',
'burpui/static/vendor/fullcalendar/dist/fullcalendar.min.css', 'burpui/static/vendor/fullcalendar/dist/fullcalendar.min.css',
'burpui/static/vendor/fullcalendar/dist/fullcalendar.print.css', 'burpui/static/vendor/fullcalendar/dist/fullcalendar.print.css',