i18n support.

This commit is contained in:
Dave Page 2015-02-25 17:06:00 +00:00
parent 39089cca21
commit 83cbe87040
28 changed files with 704 additions and 108 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.pyo
*.o
*.psp
_build
build-*
.DS_Store
runtime/.qmake.cache

View File

@ -75,6 +75,7 @@ learn how pgAdmin works, and how to develop improvements and new features.
coding-standards
code-overview
submitting-patches
translations
*******
Website

134
docs/en_US/translations.rst Normal file
View File

@ -0,0 +1,134 @@
Translations
============
pgAdmin supports multiple languages using the `Flask-Babel
<https://pythonhosted.org/Flask-Babel/>`_ Python module. A list of supported
languages is included in the **web/config.py** configuration file and must be
updated whenever langauges are added or removed.
Translation Marking
-------------------
Strings can be marked for translation in either Python code (using **gettext()**)
or Jinja templates (using **_()**). Here are some examples that show how this
is achieved.
Python::
errormsg = gettext('No server group name was specified')
Jinja:
.. code-block:: html
<input type="submit" value="{{ _('Change Password') }}">
.. code-block:: html
<title>{{ _('%(appname)s Password Change', appname=config.APP_NAME) }}</title>
.. code-block:: javascript
var alert = alertify.prompt(
'{{ _('Add a server group') }}',
'{{ _('Enter a name for the new server group') }}',
''
...
)
Updating and Merging
--------------------
Whenever new strings are added to the application, the template catalogues
(**web/pgadmin/messages.pot**) must be updated and the existing catalogues
merged with the updated template and compiled. This can be achieved using the
following command from the **web** directory, in the Python virtual environment
used for pgAdmin:
.. code-block:: bash
(pgadmin4)piranha:web dpage$ pybabel extract -F babel.cfg -o pgadmin/messages.pot pgadmin
For example:
.. code-block:: bash
(pgadmin4)piranha:web dpage$ pybabel extract -F babel.cfg -o pgadmin/messages.pot pgadmin
extracting messages from pgadmin/__init__.py
extracting messages from pgadmin/about/__init__.py
extracting messages from pgadmin/about/hooks.py
extracting messages from pgadmin/about/views.py
extracting messages from pgadmin/about/templates/about/index.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/browser/__init__.py
extracting messages from pgadmin/browser/hooks.py
extracting messages from pgadmin/browser/views.py
extracting messages from pgadmin/browser/nodes/CollectionNode.py
extracting messages from pgadmin/browser/nodes/ObjectNode.py
extracting messages from pgadmin/browser/nodes/__init__.py
extracting messages from pgadmin/browser/nodes/server_groups/__init__.py
extracting messages from pgadmin/browser/nodes/server_groups/hooks.py
extracting messages from pgadmin/browser/nodes/server_groups/views.py
extracting messages from pgadmin/browser/templates/browser/body.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/browser/templates/browser/index.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/browser/templates/browser/messages.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/help/__init__.py
extracting messages from pgadmin/help/hooks.py
extracting messages from pgadmin/help/views.py
extracting messages from pgadmin/redirects/__init__.py
extracting messages from pgadmin/redirects/views.py
extracting messages from pgadmin/settings/__init__.py
extracting messages from pgadmin/settings/hooks.py
extracting messages from pgadmin/settings/settings_model.py
extracting messages from pgadmin/settings/views.py
extracting messages from pgadmin/templates/base.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/change_password.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/fields.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/forgot_password.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/login_user.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/messages.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/panel.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/reset_password.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/templates/security/watermark.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from pgadmin/test/__init__.py
extracting messages from pgadmin/test/hooks.py
extracting messages from pgadmin/test/views.py
extracting messages from pgadmin/utils/__init__.py
extracting messages from pgadmin/utils/views.py
writing PO template file to pgadmin/messages.pot
Once the template has been updated, it needs to be merged into the existing
message catalogues, for example:
.. code-block:: bash
(pgadmin4)piranha:web dpage$ pybabel update -i pgadmin/messages.pot -d pgadmin/translations
updating catalog 'pgadmin/translations/fr/LC_MESSAGES/messages.po' based on 'pgadmin/messages.pot'
Finally, the message catalogues can be compiled for use:
.. code-block:: bash
(pgadmin4)piranha:web dpage$ pybabel compile -d pgadmin/translations
compiling catalog 'pgadmin/translations/fr/LC_MESSAGES/messages.po' to 'pgadmin/translations/fr/LC_MESSAGES/messages.mo'
Adding a new Language
---------------------
Adding a new language is simple. First, add the language name and identifier to
**web/config.py**::
# Languages we support in the UI
LANGUAGES = {
'en': 'English',
'fr': 'Français'
}
Then, create the new message catalogue from the **web** directory in the source
tree, in the Python virtual environment used for pgAdmin:
.. code-block:: bash
(pgadmin4)piranha:web dpage$ pybabel init -i pgadmin/messages.pot -d pgadmin/translations -l fr
This will initialise a new catalogue for a French translation.

View File

@ -1,4 +1,6 @@
Babel==1.3
Flask==0.10.1
Flask-Babel==0.9
Flask-Gravatar==0.4.1
Flask-Login==0.2.11
Flask-Mail==0.9.1
@ -19,5 +21,7 @@ html5lib==1.0b3
itsdangerous==0.24
passlib==1.6.2
psycopg2==2.5.2
pytz==2014.10
six==1.9.0
speaklater==1.3
wsgiref==0.1.2

3
web/babel.cfg Normal file
View File

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

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
@ -34,6 +36,12 @@ APP_COPYRIGHT = 'Copyright 2014 - 2015, The pgAdmin Development Team'
# Path to the online help.
HELP_PATH = '../../../docs/en_US/_build/html/'
# Languages we support in the UI
LANGUAGES = {
'en': 'English',
'fr': 'Français'
}
# DO NOT CHANGE!
# The application version string, constructed from the components
APP_VERSION = '%s.%s.%s-%s' % (APP_MAJOR, APP_MINOR, APP_REVISION, APP_SUFFIX)

View File

@ -10,7 +10,8 @@
"""The main pgAdmin module. This handles the application initialisation tasks,
such as setup of logging, dynamic loading of modules etc."""
from flask import Flask, abort
from flask import Flask, abort, request
from flask.ext.babel import Babel
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.security import Security, SQLAlchemyUserDatastore, login_required
from flask_security.utils import login_user
@ -66,6 +67,23 @@ def create_app(app_name=config.APP_NAME):
app.logger.info('Starting %s v%s...', config.APP_NAME, config.APP_VERSION)
app.logger.info('################################################################################')
##########################################################################
# Setup i18n
##########################################################################
# Initialise i18n
babel = Babel(app)
app.logger.debug('Available translations: %s' % babel.list_translations())
@babel.localeselector
def get_locale():
"""Get the best language for the user."""
language = request.accept_languages.best_match(config.LANGUAGES.keys())
app.logger.info('Using language: %s', language)
return language
##########################################################################
# Setup authentication
##########################################################################

View File

@ -1,25 +1,25 @@
<div class="row">
<div class="col-sm-3"><b>Version</b></div>
<div class="col-sm-3"><b>{{ _('Version') }}</b></div>
<div class="col-sm-9">{{ config.APP_VERSION }}</div>
</div>
<div class="row">
<div class="col-sm-3"><b>Copyright</b></div>
<div class="col-sm-3"><b>{{ _('Copyright') }}</b></div>
<div class="col-sm-9">{{ config.APP_COPYRIGHT }}</div>
</div>
<div class="row">
<div class="col-sm-3"><b>Python Version</b></div>
<div class="col-sm-3"><b>{{ _('Python Version') }}</b></div>
<div class="col-sm-9">{{ info.python_version }}</div>
</div>
<div class="row">
<div class="col-sm-3"><b>Flask Version</b></div>
<div class="col-sm-3"><b>{{ _('Flask Version') }}</b></div>
<div class="col-sm-9">{{ info.flask_version }}</div>
</div>
<div class="row">
<div class="col-sm-3"><b>Application Mode</b></div>
<div class="col-sm-3"><b>{{ _('Application Mode') }}</b></div>
<div class="col-sm-9">{{ info.app_mode }}</div>
</div>
<div class="row">
<div class="col-sm-3"><b>Current User</b></div>
<div class="col-sm-3"><b>{{ _('Current User') }}</b></div>
<div class="col-sm-9">{{ info.current_user }}</div>
</div>

View File

@ -11,6 +11,7 @@
MODULE_NAME = 'about'
from flask import Blueprint, Response, current_app, render_template, __version__
from flask.ext.babel import gettext
from flask.ext.security import current_user, login_required
import sys
@ -31,9 +32,9 @@ def index():
info['python_version'] = sys.version
info['flask_version'] = __version__
if config.SERVER_MODE == True:
info['app_mode'] = 'Server'
info['app_mode'] = gettext('Server')
else:
info['app_mode'] = 'Desktop'
info['app_mode'] = gettext('Desktop')
info['current_user'] = current_user.email
return render_template(MODULE_NAME + '/index.html', info=info)

View File

@ -10,6 +10,7 @@
"""Integration hooks for server groups."""
from flask import render_template, url_for
from flask.ext.babel import gettext
from flask.ext.security import current_user
from pgadmin.settings.settings_model import db, ServerGroup
@ -30,17 +31,17 @@ def get_file_menu_items():
"""Return a (set) of dicts of file menu items, with name, priority, URL,
target and onclick code."""
return [
{'name': 'mnu_add_server_group', 'label': 'Add a server group...', 'priority': 10, 'url': '#', 'onclick': 'add_server_group()'},
{'name': 'mnu_delete_server_group', 'label': 'Delete server group', 'priority': 20, 'url': '#', 'onclick': 'delete_server_group()'},
{'name': 'mnu_rename_server_group', 'label': 'Rename server group...', 'priority': 30, 'url': '#', 'onclick': 'rename_server_group()'}
{'name': 'mnu_add_server_group', 'label': gettext('Add a server group...'), 'priority': 10, 'url': '#', 'onclick': 'add_server_group()'},
{'name': 'mnu_delete_server_group', 'label': gettext('Delete server group'), 'priority': 20, 'url': '#', 'onclick': 'delete_server_group()'},
{'name': 'mnu_rename_server_group', 'label': gettext('Rename server group...'), 'priority': 30, 'url': '#', 'onclick': 'rename_server_group()'}
]
def get_context_menu_items():
"""Return a (set) of dicts of content menu items with name, label, priority and JS"""
return [
{'name': 'delete', 'label': 'Delete server group', 'priority': 100, 'onclick': 'delete_server_group(item);'},
{'name': 'rename', 'label': 'Rename server group...', 'priority': 200, 'onclick': 'rename_server_group(item);'}
{'name': 'delete', 'label': gettext('Delete server group'), 'priority': 100, 'onclick': 'delete_server_group(item);'},
{'name': 'rename', 'label': gettext('Rename server group...'), 'priority': 200, 'onclick': 'rename_server_group(item);'}
]

View File

@ -1,8 +1,8 @@
// Add a server group
function add_server_group() {
var alert = alertify.prompt(
'Add a server group',
'Enter a name for the new server group',
'{{ _('Add a server group') }}',
'{{ _('Enter a name for the new server group') }}',
'',
function(evt, value) {
$.post("{{ url_for('NODE-server-group.add') }}", { name: value })
@ -34,8 +34,8 @@ function add_server_group() {
// Delete a server group
function delete_server_group(item) {
alertify.confirm(
'Delete server group?',
'Are you sure you wish to delete the server group "{0}"?'.replace('{0}', tree.getLabel(item)),
'{{ _('Delete server group?') }}',
'{{ _('Are you sure you wish to delete the server group "{0}"?') }}'.replace('{0}', tree.getLabel(item)),
function() {
var id = tree.getId(item)
$.post("{{ url_for('NODE-server-group.delete') }}", { id: id })
@ -62,8 +62,8 @@ function delete_server_group(item) {
// Rename a server group
function rename_server_group(item) {
alertify.prompt(
'Rename server group',
'Enter a new name for the server group',
'{{ _('Rename server group') }}',
'{{ _('Enter a new name for the server group') }}',
tree.getLabel(item),
function(evt, value) {
var id = tree.getId(item)

View File

@ -15,6 +15,7 @@ NODE_PATH = '/browser/' + NODE_NAME
import traceback
from flask import Blueprint, Response, current_app, request
from flask.ext.babel import gettext
from flask.ext.security import current_user, login_required
from utils.ajax import make_json_response
@ -44,7 +45,7 @@ def add():
else:
success = 0
errormsg = "No server group name was specified"
errormsg = gettext('No server group name was specified')
if success == 1:
data['id'] = servergroup.id
@ -69,7 +70,7 @@ def delete():
if servergroup is None:
success = 0
errormsg = 'The specified server group could not be found.'
errormsg = gettext('The specified server group could not be found.')
else:
try:
db.session.delete(servergroup)
@ -80,7 +81,7 @@ def delete():
else:
success = 0
errormsg = "No server group was specified."
errormsg = gettext('No server group was specified.')
return make_json_response(success=success,
errormsg=errormsg,
@ -100,7 +101,7 @@ def rename():
if servergroup is None:
success = 0
errormsg = 'The specified server group could not be found.'
errormsg = gettext('The specified server group could not be found.')
else:
try:
servergroup.name = request.form['name']
@ -111,9 +112,10 @@ def rename():
else:
success = 0
errormsg = "No server group was specified."
errormsg = gettext('No server group was specified.')
return make_json_response(success=success,
errormsg=errormsg,
info=traceback.format_exc(),
result=request.form)
result=request.form)

View File

@ -1,39 +0,0 @@
function report_error(message, info) {
text = '<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">\
<div class="panel panel-default">\
<div class="panel-heading" role="tab" id="headingOne">\
<h4 class="panel-title">\
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">\
Error message\
</a>\
</h4>\
</div>\
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">\
<div class="panel-body" style="overflow: scroll;">' + message + '</div>\
</div>\
</div>'
if (info != '') {
text += '<div class="panel panel-default">\
<div class="panel-heading" role="tab" id="headingTwo">\
<h4 class="panel-title">\
<a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">\
Additional info\
</a>\
</h4>\
</div>\
<div id="collapseTwo" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingTwo">\
<div class="panel-body" style="overflow: scroll;">' + info + '</div>\
</div>\
</div>\
</div>'
}
text += '</div>'
alertify.alert(
'An error has occurred',
text
)
}

View File

@ -8,11 +8,11 @@
<div class="pane ui-layout-center browser-inner-pane" id="outer-center">
<div class="pane ui-layout-center browser-center-pane" id="inner-center">
<ul class="nav nav-tabs browser-tab-bar" role="tablist">
<li role="presentation" class="active"><a href="#dashboard" aria-controls="home" role="tab" data-toggle="tab">Dashboard</a></li>
<li role="presentation"><a href="#properties" aria-controls="profile" role="tab" data-toggle="tab">Properties</a></li>
<li role="presentation"><a href="#statistics" aria-controls="messages" role="tab" data-toggle="tab">Statistics</a></li>
<li role="presentation"><a href="#dependencies" aria-controls="settings" role="tab" data-toggle="tab">Dependencies</a></li>
<li role="presentation"><a href="#dependents" aria-controls="settings" role="tab" data-toggle="tab">Dependents</a></li>
<li role="presentation" class="active"><a href="#dashboard" aria-controls="home" role="tab" data-toggle="tab">{{ _('Dashboard') }}</a></li>
<li role="presentation"><a href="#properties" aria-controls="profile" role="tab" data-toggle="tab">{{ _('Properties') }}</a></li>
<li role="presentation"><a href="#statistics" aria-controls="messages" role="tab" data-toggle="tab">{{ _('Statistics') }}</a></li>
<li role="presentation"><a href="#dependencies" aria-controls="settings" role="tab" data-toggle="tab">{{ _('Dependencies') }}</a></li>
<li role="presentation"><a href="#dependents" aria-controls="settings" role="tab" data-toggle="tab">{{ _('Dependents') }}</a></li>
</ul>
<div class="tab-content browser-tab-panes">
<div role="tabpanel" class="tab-pane browser-tab-pane active" id="dashboard">

View File

@ -5,7 +5,7 @@
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-menu">
<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>
@ -18,35 +18,35 @@
<ul class="nav navbar-nav">
{% if file_items is defined and file_items|count > 0 %}<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">File <span class="caret"></span></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('File') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">{% for file_item in file_items %}
<li><a id="{{ file_item.name }}" href="{{ file_item.url }}" target="{{ file_item.target }}" onclick="{{ file_item.onclick|safe }}">{{ file_item.label }}</a></li>{% endfor %}
</ul>
</li>{% endif %}
{% if edit_items is defined and edit_items|count > 0 %}<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Edit <span class="caret"></span></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Edit') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">{% for edit_item in edit_items %}
<li><a id="{{ edit_item.name }}" href="{{ edit_item.url }}" target="{{ edit_item.target }}" onclick="{{ edit_item.onclick|safe }}">{{ edit_item.label }}</a></li>{% endfor %}
</ul>
</li>{% endif %}
{% if tools_items is defined and tools_items|count > 0 %}<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Tools <span class="caret"></span></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Tools') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">{% for tools_item in tools_items %}
<li><a id="{{ tools_item.name }}" href="{{ tools_item.url }}" target="{{ tools_item.target }}" onclick="{{ tools_item.onclick|safe }}">{{ tools_item.label }}</a></li>{% endfor %}
</ul>
</li>{% endif %}
{% if management_items is defined and management_items|count > 0 %}<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Management <span class="caret"></span></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Management') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">{% for management_item in management_items %}
<li><a id="{{ management_item.name }}" href="{{ management_item.url }}" target="{{ management_item.target }}" onclick="{{ management_item.onclick|safe }}">{{ management_item.label }}</a></li>{% endfor %}
</ul>
</li>{% endif %}
{% if help_items is defined and help_items|count > 0 %}<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">Help <span class="caret"></span></a>
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">{{ _('Help') }} <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">{% for help_item in help_items %}
<li><a id="{{ help_item.name }}" href="{{ help_item.url }}" target="{{ help_item.target }}" onclick="{{ help_item.onclick|safe }}">{{ help_item.label }}</a></li>{% endfor %}
</ul>
@ -58,9 +58,9 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><img src="{{ username | gravatar }}" width="18" height="18" alt="Gravatar image for {{ username }}"> {{ username }} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="{{ url_for('security.change_password') }}">Change Password</a></li>
<li><a href="{{ url_for('security.change_password') }}">{{ _('Change Password') }}</a></li>
<li class="divider"></li>
<li><a href="{{ url_for('security.logout') }}">Logout</a></li>
<li><a href="{{ url_for('security.logout') }}">{{ _('Logout') }}</a></li>
</ul>
</li>
</ul>

View File

@ -35,25 +35,25 @@ $(document).ready(function(){
center__maskContents: true,
center__onresize: "storeLayout",
south__maskContents: true,
south__size: {{ layout_settings.sql_size }},
south__size: {{ layout_settings.sql_size }},
south__initClosed: {{ layout_settings.sql_closed }},
south__spacing_closed: 22,
south__spacing_closed: 22,
south__togglerLength_closed: 140,
south__togglerAlign_closed: "right",
south__togglerContent_closed: 'SQL Pane',
south__togglerTip_closed: "Open & Pin SQL Pane",
south__sliderTip: "Slide Open SQL Pane",
south__togglerContent_closed: "{{ _('SQL Pane') }}",
south__togglerTip_closed: "{{ _('Open & Pin SQL Pane') }}",
south__sliderTip: "{{ _('Slide Open SQL Pane') }}",
south__slideTrigger_open: "mouseover",
}],
west__maskContents: true,
west__size: {{ layout_settings.browser_size }},
west__size: {{ layout_settings.browser_size }},
west__initClosed: {{ layout_settings.browser_closed }},
west__spacing_closed: 22,
west__spacing_closed: 22,
west__togglerLength_closed: 140,
west__togglerAlign_closed: "top",
west__togglerContent_closed: 'B<br />r<br />o<br />w<br />s<br />e<br />r',
west__togglerTip_closed: "Open & Pin Browser",
west__sliderTip: "Slide Open Browser",
west__togglerContent_closed: "{{ _('B<br />r<br />o<br />w<br />s<br />e<br />r') }}",
west__togglerTip_closed: "{{ _('Open & Pin Browser') }}",
west__sliderTip: "{{ _('Slide Open Browser') }}",
west__slideTrigger_open: "mouseover",
};
@ -126,3 +126,43 @@ $(document).ready(function(){
}
});
});
function report_error(message, info) {
text = '<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">\
<div class="panel panel-default">\
<div class="panel-heading" role="tab" id="headingOne">\
<h4 class="panel-title">\
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">\
{{ _('Error message') }}\
</a>\
</h4>\
</div>\
<div id="collapseOne" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">\
<div class="panel-body" style="overflow: scroll;">' + message + '</div>\
</div>\
</div>'
if (info != '') {
text += '<div class="panel panel-default">\
<div class="panel-heading" role="tab" id="headingTwo">\
<h4 class="panel-title">\
<a class="collapsed" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">\
{{ _('Additional info') }}\
</a>\
</h4>\
</div>\
<div id="collapseTwo" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingTwo">\
<div class="panel-body" style="overflow: scroll;">' + info + '</div>\
</div>\
</div>\
</div>'
}
text += '</div>'
alertify.alert(
'{{ _('An error has occurred') }}',
text
)
}

View File

@ -59,7 +59,6 @@ def index():
# Add browser scripts
scripts.append(url_for('static', filename='js/codemirror/codemirror.js'))
scripts.append(url_for('static', filename='js/codemirror/mode/sql.js'))
scripts.append(url_for('browser.static', filename='js/utils.js'))
scripts.append(url_for('browser.static', filename='js/aciTree/jquery.aciPlugin.min.js'))
scripts.append(url_for('browser.static', filename='js/aciTree/jquery.aciTree.dom.js'))
scripts.append(url_for('browser.static', filename='js/aciTree/jquery.aciTree.min.js'))

View File

@ -10,6 +10,7 @@
"""Browser integration functions for the Help module."""
from flask import url_for
from flask.ext.babel import gettext
import config
@ -17,7 +18,7 @@ def get_help_menu_items():
"""Return a (set) of dicts of help menu items, with name, priority, URL,
target and onclick code."""
return [{'name': 'mnu_contents',
'label': 'Contents',
'label': gettext('Contents'),
'priority': 1,
'target': '_new',
'url': url_for('help.static', filename='index.html') }]

210
web/pgadmin/messages.pot Normal file
View File

@ -0,0 +1,210 @@
# Translations template for PROJECT.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-02-25 16:30+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
#: pgadmin/about/views.py:35
msgid "Server"
msgstr ""
#: pgadmin/about/views.py:37
msgid "Desktop"
msgstr ""
#: pgadmin/about/templates/about/index.html:2
msgid "Version"
msgstr ""
#: pgadmin/about/templates/about/index.html:6
msgid "Copyright"
msgstr ""
#: pgadmin/about/templates/about/index.html:10
msgid "Python Version"
msgstr ""
#: pgadmin/about/templates/about/index.html:14
msgid "Flask Version"
msgstr ""
#: pgadmin/about/templates/about/index.html:18
msgid "Application Mode"
msgstr ""
#: pgadmin/about/templates/about/index.html:22
msgid "Current User"
msgstr ""
#: pgadmin/browser/nodes/server_groups/hooks.py:34
msgid "Add a server group..."
msgstr ""
#: pgadmin/browser/nodes/server_groups/hooks.py:35
#: pgadmin/browser/nodes/server_groups/hooks.py:43
msgid "Delete server group"
msgstr ""
#: pgadmin/browser/nodes/server_groups/hooks.py:36
#: pgadmin/browser/nodes/server_groups/hooks.py:44
msgid "Rename server group..."
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:48
msgid "No server group name was specified"
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:73
#: pgadmin/browser/nodes/server_groups/views.py:104
msgid "The specified server group could not be found."
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:84
msgid "No server group was specified."
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:115
msgid "No server group was specified."
msgstr ""
#: pgadmin/browser/templates/browser/body.html:11
msgid "Dashboard"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:12
msgid "Properties"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:13
msgid "Statistics"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:14
msgid "Dependencies"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:15
msgid "Dependents"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:8
msgid "Toggle navigation"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:21
msgid "File"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:28
msgid "Edit"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:35
msgid "Tools"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:42
msgid "Management"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:49
msgid "Help"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:61
#: pgadmin/templates/security/change_password.html:10
msgid "Change Password"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:63
msgid "Logout"
msgstr ""
#: pgadmin/help/hooks.py:21
msgid "Contents"
msgstr ""
#: pgadmin/templates/security/change_password.html:2
#, python-format
msgid "%(appname)s Password Change"
msgstr ""
#: pgadmin/templates/security/forgot_password.html:2
#, python-format
msgid "Recover %(appname)s Password"
msgstr ""
#: pgadmin/templates/security/forgot_password.html:4
msgid ""
"Enter the email address for the user account you wish to recover the "
"password for:"
msgstr ""
#: pgadmin/templates/security/forgot_password.html:9
msgid "Recover Password"
msgstr ""
#: pgadmin/templates/security/login_user.html:2
#, python-format
msgid "%(appname)s Login"
msgstr ""
#: pgadmin/templates/security/login_user.html:9
msgid "Login"
msgstr ""
#: pgadmin/templates/security/login_user.html:12
#, python-format
msgid "Forgotten your <a href=\"%(url)s\">password</a>?"
msgstr ""
#: pgadmin/templates/security/messages.html:6
msgid "Close"
msgstr ""
#: pgadmin/templates/security/reset_password.html:2
#, python-format
msgid "%(appname)s Password Reset"
msgstr ""
#: pgadmin/templates/security/reset_password.html:9
msgid "Reset Password"
msgstr ""
#: pgadmin/test/hooks.py:19
msgid "Generated Test HTML"
msgstr ""
#: pgadmin/test/hooks.py:20
msgid "Test Alert"
msgstr ""
#: pgadmin/test/hooks.py:21
msgid "Test Confirm"
msgstr ""
#: pgadmin/test/hooks.py:22
msgid "Test Dialog"
msgstr ""
#: pgadmin/test/hooks.py:23
msgid "Test Prompt"
msgstr ""
#: pgadmin/test/hooks.py:24
msgid "Test Notifier"
msgstr ""

View File

@ -1,5 +1,5 @@
{% extends "security/panel.html" %}
{% block panel_title %}pgAdmin Password Change{% endblock %}
{% block panel_title %}{{ _('%(appname)s Password Change', appname=config.APP_NAME) }}{% endblock %}
{% block panel_body %}
<form action="{{ url_for_security('change_password') }}" method="POST" name="change_password_form">
{{ change_password_form.hidden_tag() }}
@ -7,7 +7,7 @@
{{ render_field_with_errors(change_password_form.password, "password") }}
{{ render_field_with_errors(change_password_form.new_password, "password") }}
{{ render_field_with_errors(change_password_form.new_password_confirm, "password") }}
<input class="btn btn-lg btn-success btn-block" type="submit" value="Change Password">
<input class="btn btn-lg btn-success btn-block" type="submit" value="{{ _('Change Password') }}">
</fieldset>
</form>
{% endblock %}

View File

@ -1,12 +1,12 @@
{% extends "security/panel.html" %}
{% block panel_title %}Recover pgAdmin Password{% endblock %}
{% block panel_title %}{{ _('Recover %(appname)s Password', appname=config.APP_NAME) }}{% endblock %}
{% block panel_body %}
<p>Enter the email address for the user account you wish to recover the password for:</p>
<p>{{ _('Enter the email address for the user account you wish to recover the password for:') }}</p>
<form action="{{ url_for_security('forgot_password') }}" method="POST" name="forgot_password_form">
{{ forgot_password_form.hidden_tag() }}
<fieldset>
{{ render_field_with_errors(forgot_password_form.email, "text") }}
<input class="btn btn-lg btn-success btn-block" type="submit" value="Recover Password">
<input class="btn btn-lg btn-success btn-block" type="submit" value="{{ _('Recover Password') }}">
</fieldset>
</form>
{% endblock %}

View File

@ -1,13 +1,13 @@
{% extends "security/panel.html" %}
{% block panel_title %}pgAdmin Login{% endblock %}
{% block panel_title %}{{ _('%(appname)s Login', appname=config.APP_NAME) }}{% endblock %}
{% block panel_body %}
<form action="{{ url_for_security('login') }}" method="POST" name="login_user_form">
{{ login_user_form.hidden_tag() }}
<fieldset>
{{ render_field_with_errors(login_user_form.email, "text") }}
{{ render_field_with_errors(login_user_form.password, "password") }}
<input class="btn btn-lg btn-success btn-block" type="submit" value="Login">
<input class="btn btn-lg btn-success btn-block" type="submit" value="{{ _('Login') }}">
</fieldset>
</form>
<span class="help-block">Forgotten your <a href="{{ url_for('security.forgot_password') }}">password</a>?</span>
<span class="help-block">{{ _('Forgotten your <a href="%(url)s">password</a>?', url=url_for('security.forgot_password')) }}</span>
{% endblock %}

View File

@ -3,7 +3,7 @@
<div style="position: fixed; top: 20px; right: 20px; width: 400px; z-index: 9999">
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<button type="button" class="close" data-dismiss="alert" aria-label="{{ _('Close') }}"><span aria-hidden="true">&times;</span></button>
{{ message }}
</div>
{% endfor %}

View File

@ -1,12 +1,12 @@
{% extends "security/panel.html" %}
{% block panel_title %}pgAdmin Password Reset{% endblock %}
{% block panel_title %}{{ _('%(appname)s Password Reset', appname=config.APP_NAME) }}{% endblock %}
{% block panel_body %}
<form action="{{ url_for_security('reset_password', token=reset_password_token) }}" method="POST" name="reset_password_form">
{{ reset_password_form.hidden_tag() }}
<fieldset>
{{ render_field_with_errors(reset_password_form.password, "password") }}
{{ render_field_with_errors(reset_password_form.password_confirm, "password") }}
<input class="btn btn-lg btn-success btn-block" type="submit" value="Reset Password">
<input class="btn btn-lg btn-success btn-block" type="submit" value="{{ _('Reset Password') }}">
</fieldset>
</form>
{% endblock %}

View File

@ -1,5 +1,5 @@
{% block watermark %}
<div style="position: fixed; bottom: 0; right: 0;">
<img src="{{ url_for('static', filename='img/logo-right-256.png') }}" alt="pgAdmin 4">
<img src="{{ url_for('static', filename='img/logo-right-256.png') }}" alt="{{ config.APP_NAME }}">
</div>
{% endblock %}

View File

@ -10,17 +10,18 @@
"""Browser integration functions for the Test module."""
from flask import render_template, url_for
from flask.ext.babel import gettext
def get_file_menu_items():
"""Return a (set) of dicts of file menu items, with name, priority, URL,
target and onclick code."""
return [
{'name': 'mnu_generate_test_html', 'label': 'Generated Test HTML', 'priority': 100, 'url': url_for('test.generated')},
{'name': 'mnu_test_alert', 'label': 'Test Alert', 'priority': 200, 'url': '#', 'onclick': 'test_alert()'},
{'name': 'mnu_test_confirm', 'label': 'Test Confirm', 'priority': 300, 'url': '#', 'onclick': 'test_confirm()'},
{'name': 'mnu_test_dialog', 'label': 'Test Dialog', 'priority': 400, 'url': '#', 'onclick': 'test_dialog()'},
{'name': 'mnu_test_prompt', 'label': 'Test Prompt', 'priority': 500, 'url': '#', 'onclick': 'test_prompt()'},
{'name': 'mnu_test_notifier', 'label': 'Test Notifier', 'priority': 600, 'url': '#', 'onclick': 'test_notifier()'},
{'name': 'mnu_generate_test_html', 'label': gettext('Generated Test HTML'), 'priority': 100, 'url': url_for('test.generated')},
{'name': 'mnu_test_alert', 'label': gettext('Test Alert'), 'priority': 200, 'url': '#', 'onclick': 'test_alert()'},
{'name': 'mnu_test_confirm', 'label': gettext('Test Confirm'), 'priority': 300, 'url': '#', 'onclick': 'test_confirm()'},
{'name': 'mnu_test_dialog', 'label': gettext('Test Dialog'), 'priority': 400, 'url': '#', 'onclick': 'test_dialog()'},
{'name': 'mnu_test_prompt', 'label': gettext('Test Prompt'), 'priority': 500, 'url': '#', 'onclick': 'test_prompt()'},
{'name': 'mnu_test_notifier', 'label': gettext('Test Notifier'), 'priority': 600, 'url': '#', 'onclick': 'test_notifier()'},
]
def get_scripts():

Binary file not shown.

View File

@ -0,0 +1,211 @@
# French translations for PROJECT.
# Copyright (C) 2015 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2015.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2015-02-25 16:30+0000\n"
"PO-Revision-Date: 2015-02-25 17:04+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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 1.3\n"
#: pgadmin/about/views.py:35
msgid "Server"
msgstr ""
#: pgadmin/about/views.py:37
msgid "Desktop"
msgstr ""
#: pgadmin/about/templates/about/index.html:2
msgid "Version"
msgstr ""
#: pgadmin/about/templates/about/index.html:6
msgid "Copyright"
msgstr ""
#: pgadmin/about/templates/about/index.html:10
msgid "Python Version"
msgstr ""
#: pgadmin/about/templates/about/index.html:14
msgid "Flask Version"
msgstr ""
#: pgadmin/about/templates/about/index.html:18
msgid "Application Mode"
msgstr ""
#: pgadmin/about/templates/about/index.html:22
msgid "Current User"
msgstr ""
#: pgadmin/browser/nodes/server_groups/hooks.py:34
msgid "Add a server group..."
msgstr ""
#: pgadmin/browser/nodes/server_groups/hooks.py:35
#: pgadmin/browser/nodes/server_groups/hooks.py:43
msgid "Delete server group"
msgstr ""
#: pgadmin/browser/nodes/server_groups/hooks.py:36
#: pgadmin/browser/nodes/server_groups/hooks.py:44
msgid "Rename server group..."
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:48
msgid "No server group name was specified"
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:73
#: pgadmin/browser/nodes/server_groups/views.py:104
msgid "The specified server group could not be found."
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:84
msgid "No server group was specified."
msgstr ""
#: pgadmin/browser/nodes/server_groups/views.py:115
msgid "No server group was specified."
msgstr ""
#: pgadmin/browser/templates/browser/body.html:11
msgid "Dashboard"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:12
msgid "Properties"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:13
msgid "Statistics"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:14
msgid "Dependencies"
msgstr ""
#: pgadmin/browser/templates/browser/body.html:15
msgid "Dependents"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:8
msgid "Toggle navigation"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:21
msgid "File"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:28
msgid "Edit"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:35
msgid "Tools"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:42
msgid "Management"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:49
msgid "Help"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:61
#: pgadmin/templates/security/change_password.html:10
msgid "Change Password"
msgstr ""
#: pgadmin/browser/templates/browser/index.html:63
msgid "Logout"
msgstr ""
#: pgadmin/help/hooks.py:21
msgid "Contents"
msgstr ""
#: pgadmin/templates/security/change_password.html:2
#, python-format
msgid "%(appname)s Password Change"
msgstr ""
#: pgadmin/templates/security/forgot_password.html:2
#, python-format
msgid "Recover %(appname)s Password"
msgstr ""
#: pgadmin/templates/security/forgot_password.html:4
msgid ""
"Enter the email address for the user account you wish to recover the "
"password for:"
msgstr ""
#: pgadmin/templates/security/forgot_password.html:9
msgid "Recover Password"
msgstr ""
#: pgadmin/templates/security/login_user.html:2
#, python-format
msgid "%(appname)s Login"
msgstr ""
#: pgadmin/templates/security/login_user.html:9
msgid "Login"
msgstr ""
#: pgadmin/templates/security/login_user.html:12
#, python-format
msgid "Forgotten your <a href=\"%(url)s\">password</a>?"
msgstr ""
#: pgadmin/templates/security/messages.html:6
msgid "Close"
msgstr ""
#: pgadmin/templates/security/reset_password.html:2
#, python-format
msgid "%(appname)s Password Reset"
msgstr ""
#: pgadmin/templates/security/reset_password.html:9
msgid "Reset Password"
msgstr ""
#: pgadmin/test/hooks.py:19
msgid "Generated Test HTML"
msgstr ""
#: pgadmin/test/hooks.py:20
msgid "Test Alert"
msgstr ""
#: pgadmin/test/hooks.py:21
msgid "Test Confirm"
msgstr ""
#: pgadmin/test/hooks.py:22
msgid "Test Dialog"
msgstr ""
#: pgadmin/test/hooks.py:23
msgid "Test Prompt"
msgstr ""
#: pgadmin/test/hooks.py:24
msgid "Test Notifier"
msgstr ""