Flask-Babel localized strings within js - python

I'm pretty new to both Python and Flask (with Jinja2 as template engine) and I am not sure I am doing it the right way. I am using Flask-Babel extension to add i18n support to my web application. I want to get localized strings from my js code, for instance:
var helloWorld = gettext('Hello, world');
console.log(helloWorld); //should log a localized hello world message
For this, I configured babel (babel.cfg):
[python: **/**.py]
[jinja2: **/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
[javascript: **/**.js]
encoding = utf-8
And its initialization is (imports omitted for simplicity):
#main Flask app
app = Flask(__name__)
#localization
babel = Babel(app)
LANGUAGES = {
'ca': 'Catalan',
'en': 'English',
'de': 'Deutsch',
'es': 'Español',
'fi': 'Finnish',
'it': 'Italian'
}
#babel.localeselector
def get_locale():
return request.accept_languages.best_match(LANGUAGES.keys())
#some more stuff...
Babel identifies that string when building the POT/PO language files, but it seems I can't access these localized strings from js code since gettext function is not defined. It seems like Jinja2 is ignoring this part.
Any hints?

I have finally found a solution, although I am not sure it is the way to go. The idea is to wrap the javascript code within an html template, which is interpretated by Jinja2 before it is rendered and apply a custom Jinja2 filter to get rid of some minor issues. I tried to keep js files separately but it did not work.
It seems that gettext function can be used like so:
var helloWorld = {{gettext('Hello, world')}};
But then, no quotes are inserted, and hence, js interpreter throws an error:
var helloWorld = Hello, world;
That's why I have finally applied a custom filter. A working example would be as follows.
hello_world.html:
<script type="text/javascript">
var x = {{gettext('Hello, world')|generate_string|safe}};
console.log(x); //logs the localized hello world message
</script>
app.py:
#Jinja2 filters
from jinja2 import evalcontextfilter, Markup
#Mind the hack! Babel does not work well within js code
#app.template_filter()
#evalcontextfilter
def generate_string(eval_ctx, localized_value):
if localized_value is None:
return ""
else:
return Markup("\"" + localized_value + "\"").unescape()
Hope this helps!

Providing translations in rendered JavaScript is a bit fragile. Also, I usually do not generate JavaScript using Jinja because it uses the same type of brackets and easily turns into mess when abused (it's always possible to have dynamic data and static JavaScript).
Alternative lightweight approach is to do the same JSON trick, but using data-attributes:
<div id="view-i18n" data-i18n='{{ view_i18n|tojson }}'> ... </div>
(NB: single quotes!)
But it's also good for a limited number of translations.
Perhaps, the most solid approach is to have the same translations in JavaScript as there are in the Flask app.
With a help of a utility called pojson it is possible to convert po-files to json (see https://github.com/ZeWaren/flask-i18n-example for an example) as part of the build process (for instance, right after making mo-files). Translations can be easily added to some unique enough global namespace variable by prepending the output of pojson with var some_unique_name = to have access to it. Or put the file under static into locale-specific file (eg static/view-es.json , static/view-fr.json, etc) and get it with ajax call.
Some things to consider though. You may need to break your translation domain into separate Python and Javascript by controlling babel extraction options if you really want to make JSON smaller. Also, having all translation strings in Javascript has security aspects. Maybe, you do not want to expose certain phrases only admins see to be open to other category of users. But then more translation domains are needed for different levels of access. Also header information may need to be removed to prevent leaking translator's emails and whatnot. This, of course, complicates the build process somewhat, but the more translation JavaScript side needs with time, the more automation pays itself off.

Related

Django domain + regex parameter not working on production machine

I currently have a django view with a fairly simple search function (takes user input, returns a list of objects). For usability, I'd like the option of passing search paramters via url like so:
www.example.com/search/mysearchstring
Where mysearchstring is the input to the search function. I'm using regex to validate any alphanumeric or underscore characters.
The problem I'm having is that while this works perfectly in my development environment, it breaks on the live machine.
Currently, I am using this exact same method (with different regex patterns) in other django views without any issues. This leads me to believe that either.
1) My regex is truly bad (more likely)
2) There is a difference in regex validators between environments (less likely)
The machine running this is using django 1.6 and python 2.7, which are slightly behind my development machine, but not significantly.
urls.py
SEARCH_REGEX = '(?P<pdom>\w*)?'
urlpatterns = patterns('',
....
url(r'^polls/search/' + SEARCH_REGEX, 'polls.views.search'),
...)
Which are passed to the view like this
views. py
def search(request, pdom):
...
When loading up the page, I get the following error:
ImproperlyConfigured: "^polls/search/(?P<pdom>\w*)?" is not a valid regular expression: nothing to repeat
I've been scratching my head over this one for a while. I've attempted to use a few different methods of encapsulation around the expression with no change in results. Would appreciate any insight!
I would change it to this:
SEARCH_REGEX = r'(?P<pdom>.+)$'
It's usually a good idea to use raw strings r'' for regular expressions in python.
The group will match the entire content of the search part of your url. I would handle query string validation in the view, instead of in the url regex. If someone tries to search polls/search/two+words, you should not return a 404, but instead a 400 status and a error message explaining that the search string was malformed.
Finally, you might want to follow the common convention for search urls. Which is to use a query parameter called q. So your url-pattern would be ^polls/search/$, and then you just handle the q in the view using something like this:
def search_page_view(request):
query_string = request.GET.get('q', '')

How to properly set variables in a latex template for Django

I want to create a pdf with latex through a django view. I created the view and set up a latex template and it works fine except for one thing. When I place a variable in the latex template I have to use spaces around the curly brackets like so:
\somevalue{ {{variable}} }
Otherwise django won't check that it is as a variable. The latex syntax checker already tells me "Unintendes whitespace around }?!". I can pass the variable into the template through my view and the pdf get created but then I have whitespaces around the inserted text.
Does anybody has an idea how to solve this?
Based on some google research, I'd recommend switching templating engines to Jinja, which is supported by Django and has configurable syntax.
Be warned, I haven't fully tested this.
Here's how your latex templates would look like:
\somevalue{((variable))}
The most important part is setting the variable_start_string and variable_end_string options:
>>> import jinja2
>>> env = jinja2.Environment(variable_start_string='((', variable_end_string='))')
>>> t = env.from_string("\somevalue{((variable))}")
>>> t.render(name='Attention!')
'\\somevalue{Attention!}'
Jinja's switching documentation outlines the (few) syntax differences. From the FAQ:
The default syntax of Jinja2 matches Django syntax in many ways. However this similarity doesn’t mean that you can use a Django template unmodified in Jinja2. For example filter arguments use a function call syntax rather than a colon to separate filter name and arguments. Additionally the extension interface in Jinja is fundamentally different from the Django one which means that your custom tags won’t work any longer.
Django 1.9 and 1.8 and maybe other versions have built-in support for Jinja.
I haven't found an example of configuring Jinja syntax in Django, and I can't test this at the moment but I believe you need to change the
TEMPLATES['OPTIONS'] dictionary as needed:
block_start_string='(#',
block_end_string='#)',
variable_start_string='((',
variable_end_string='))',
comment_start_string='((#',
comment_end_string='#))',
The solution I found is to remove extra spaces after template rendering:
template = select_template(self.get_template_names())
latex_content = template.render(context)
latex_content = re.sub(r'\{ ', '{', latex_content)
latex_content = re.sub(r' \}', '}', latex_content)
This has the benefit of not requiring extra template tags flooding the template. However this as the drawback to require template writers to be aware of this behavior and take it into account wherever curly braces are used.
In practice, it would probably be better to write a custom Template class that handles this.
Edit
Note that using this method, there is no reason to use a space as separator rather than any other character. In order to make it clearer for template writers/readers, you can use another character that would ring him a bell. For instance:
template.tex
\somevalue{§{{ variable }}§}
views.py
template = select_template(self.get_template_names())
latex_content = template.render(context)
latex_content = re.sub(r'\{§', '{', latex_content)
latex_content = re.sub(r'§\}', '}', latex_content)
Which makes it clear that if there is a space, it is intended. And I think it's already clear that § everywhere is not intended to be displayed.

Django: localize the API calls's resposnse

I have API interface for my application and want the response of these calls to support various languages.
def get_student(request):
//code
return JsonResponse(content={"Message": "student is found"}, status=200)
I have gone through django-localization documentation and have created a po and mo files for specific language.
And now i'm stuck on how to use those files and give the response in particular language.
any help or reference will be appreciated.
EDIT: this one helped
settings.LOCALE_PATHS = (os.path.join(PROJECT_DIR, 'locale'))
and
https://docs.djangoproject.com/en/1.3/howto/i18n/
Before you worrying about .mo and .po files, you need to set up the various flags, languages and middlewares in your settings, and mark translatable text in your apps and templates.
You should start here https://docs.djangoproject.com/en/1.3/topics/i18n/ for the big pictures and definitions, then continue here https://docs.djangoproject.com/en/1.3/topics/i18n/translation/ for how to mark the translatable strings. Don't skip the notes as there are some configuration job to do.
About how to mark as string as translatable, in your above snippet, it should looks like:
from django.utils.translation import ugettext as _
def get_student(request):
//code
return JsonResponse(content={"Message": _(u"student is found")}, status=200)
Once you're done with marking all your translatable texts, it's time to generate the source translation files (.po), edit them with the actual translations and finally generate the compiled translations files (.mo) as documented.

Add custom tokens in Jinja2 (e.g. %% somevar %%)

I'm making a Flask app for local development (on a Mac) of HTML templates that will eventually be served through ASP.NET.
For the purposes of local development, I want a way to replace the contents of .NET-style tokens with some data, meaning that Jinja2 would need to be able to recognize %% ... %% tokens in addition to the standard ones: {{ ... }}, <% ... %>, etc.
Everything I've found online pertains to the inclusion of some new functionality within the existing tags (e.g. {{ my_custom_function | arg1 arg2 }})
But what about defining a new pattern for tags altogether? Has anyone done this successfully? And will it require modification to the Jinja2 core?
As far as I know, you can use one set for block_start_string and block_end_string, as well as one set for variable_start_string and variable_end_string.
From jinja2/environment.py
`block_start_string`
The string marking the begin of a block. Defaults to ``'{%'``.
`block_end_string`
The string marking the end of a block. Defaults to ``'%}'``.
`variable_start_string`
The string marking the begin of a print statement.
Defaults to ``'{{'``.
`variable_end_string`
The string marking the end of a print statement. Defaults to
``'}}'``.
You can override these with environment variables. Though, I don't think there is a way to have multiple types recognized. For instance, you can't have {{ and <% both work, but with a little hackery you certainly could.
In addition to the correct answer maybe an example implementation to change the variable marker:
Add following header to the first line in the jinja2 template file.
#jinja2: variable_start_string: "#{" , variable_end_string: "}#"

How can I send JSON-formatted cookie data using Python on Google App Engine?

I'm trying to encode an object in a Python script and set it as a cookie so I can read it with client-side JavaScript.
I've run into problems every way I've tried to do this. Generally, the cookie is formatted in a way that makes JSON.parse() break.
My current script:
cookie = Cookie.SimpleCookie()
data = {"name": "Janet", "if_nasty": "Ms. Jackson"}
cookie['test'] = json.dumps(data)
self.response.headers.add_header("Set-Cookie", cookie.output(header=''))
... which results in
test="{\"name\": \"janet\"\054 \"if_nasty\": \"Ms. Jackson\"}"
on the client.
I don't really want to introduce a hack-y solution to replace instances of commas when they appear. Any ideas how I can pass complex data structures (both by setting and reading cookies) with Python?
I also wanted to read a cookie (that had been set on the server) on the client. I worked around the issue by base64 encoding the JSON String, however there are a few small gotchas involved with this approach as well.
1: Base64 strings end with 0-2 equal signs, and these were being converted into the string \075. My approach is to revert those characters into equal characters on the client.
2: The base64 string is being enclosed in double quote characters in the cookie. I remove these on the client.
Server:
nav_json = json.dumps(nav_data)
nav_b64=base64.b64encode(nav_json)
self.response.set_cookie('nav_data', nav_b64)
Client:
var user_data_base64= $.cookie('nav_data');
// remove quotes from around the string
user_data_base64 = user_data_base64.replace(/"/g,"");
// replace \075 with =
user_data_base64 = user_data_base64.replace(/\\075/g,"=");
var user_data_encoded=$.base64.decode( user_data_base64 );
var user_data = $.parseJSON(user_data_encoded);
I am using 2 jquery plugins here:
https://github.com/carlo/jquery-base64
and
https://github.com/carhartl/jquery-cookie
Note: I consider this a hack: It would be better to re-implement the python code that encodes the cookie in javascript, however this also has the downside that you would need to notice and port and changes to that code.
I have now moved to a solution where I use a small html file to set the cookie on the client side and then redirect to the actual page requested. Here is a snippet from the JINJA2 template that I am using:
<script type="text/javascript">
var nav_data='{% autoescape false %}{{nav_data}}{% endautoescape %}';
$.cookie('nav_data', nav_data, { path: '/' });
window.location.replace("{{next}}")
</script>
Note 2: Cookies are not ideal for my use case and I will probably move on to Session or Local Storage to reduce network overhead (although my nav_data is quite small - a dozen characters or so.)
On the Python side:
json.dumps the string
escape spaces - just call .replace(' ', '%20')
Call urllib.parse.quote_plus() then write the string to the cookie
On the JavaScript side:
read the cookie
pass it through decodeURIComponent()
JSON.parse it
This seems to be the cleanest way I've found.
not sure a cookie is the best way of doing this? see the getting started guide for info rendering data to the client

Categories