Category: Tech

0

Even though there are webservers out there serving static files better than Apache (like NginX), sometimes “you’re stuck” with Apache, or just don’t really care about a few milliseconds performance improvement.
OK, so you’ve got a single page application (a single HTML file with Javascript doing the heavy work) and you want to have it served by an Apache webserver. Your web-app probably has a few routes, and it probably works fine accessing the routes from the app’s starting point.
Example: myapp.com/inbox/incoming/123

But you want your app, to be accessible by “deep-links” too; in other words access myapp.com/inbox/incoming/123 directly. By default Apache will try and look up the directory /inbox/incoming/123 in your filesystem and tell the user there’s no such thing: 404.

You’ll have to tell Apache to rewrite everything to the index.html page, and let the single-page-application handle the routing. You do that like this:

RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]

This is the first article of the EcmaScript 6 niceties serie. ES6 also known as ES2015 brought a truck-load of new features and structures into Javascript and renewed the language as we knew it. This serie will highlight a few of the ES6 niceties you might find useful in your next Javascript project.
Today’s article is about arrow functions.

Arrow functions have a different syntax than traditional Javascript functions (using =>), but they’re not just a shorthand notation; they behave differently in a few ways. I’ll show you a few common examples of when and how to use those functions, compared to Javascript prior to ES6.

Arrow function syntax – pretty short!

Look at the following piece of code. Here you see the arrow function assembling the tradition Javascript function. You can see the syntax is rather short, and you’ll see its variations get shorter and shorter 🙂

var es5Function = function(a,b) {
    return a + b;
};
let es6Function = (a,b) => { // rather similar to es5Function
    return a + b;
};
// note implicit return
let es6FunctionShorthand = (a,b) => a+b;
let es6DamnShort = y => y+2;

console.log( es5Function(1,2) ); // 3
console.log( es6Function(1,2) ); // 3
console.log( es6FunctionShorthand(1,2) ); // 3
console.log( es6DamnShort(1) ); // 3

As anonymous function

In Javascript you’ll be using anonymous function more than often 🙂 – they’re all over the place! Arrow functions can be quite handy right here, because of their short syntax and their different behavior. Let’s start rather simple, an event handler:

function doSomething() {
    console.log("Do something");
}
myButton.addEventListener('click', doSomething, true);

Here the function doSomething is used as an event-handler, and can be (re)used by other code.
Every now and then, you first need to take care of some things in a handler before you start calling reusable code. Sometimes the reusable function expects other parameters or you need to do / check something with the given arguments. For example with the event argument:

/* ES <= 5 */
myAnchor.addEventListener('click', function(event){
    event.preventDefault();
    doSomething();
}, true);

/* ES6 */
myAnchor.addEventListener('click', event=>{
    event.preventDefault();
    doSomething();
}, true);

Nothing new happened here, but your code gets a lot cleaner.
Remember how short arrow functions’ syntax can get. Imagine how much cleaner the following code becomes compared to its ES5 counterpart:

function doX(x){
    console.log(`Do ${x}`);
}

myButton.addEventListener('click', ()=>{
    doX("something");
}, true);
// it can get very short!
myButton.addEventListener('click', ()=>doX("anything"), true);

Doesn’t bind ‘this’

So far arrow function just makes your code cleaner, but still function as always. One of the main new behaviors you’ll get to use a lot is that it doesn’t (re)bind this.
Apart from when your using a callback for jQuery.each you’ll be using a workaround if you need to access this from within your (anonymous) callback / event-handler function. Often by declaring something like ‘var that = this;‘ before creating the anonymous function, and use that in stead of this.
Compare these two examples:

/* ES &lt; 5 */
var ES5Clickers = {
    init: function() {
        this.myButton = document.getElementById("butt");
        this.myAnchor = document.getElementById("link");

        this.myButton.addEventListener('click', this.doSomething, true);
        var that = this;
        this.myAnchor.addEventListener('click', function(event){
            event.preventDefault();
            /* this.doSomething(); // this.doSomething is not a function */
            that.doSomething();
        }, true);
    },
    doSomething: function() {
        console.log("ES5Clickers: Do something with this");
    }
};
ES5Clickers.init();

/* ES6 */
const ES6Clickers = {
    init() {
        this.myButton = document.getElementById("butt");
        this.myAnchor = document.getElementById("link");

        this.myButton.addEventListener('click', this.doSomething, true);
        this.myAnchor.addEventListener('click', (event)=>{
            event.preventDefault();
            this.doSomething(); // is all good now!
        }, true);
    },
    doSomething() {
        console.log("ES6Clickers: Do something with this");
    }
};
ES6Clickers.init();

Lambda

The short notation variations and implicit returns makes lambda kinda work a breeze:

const x = [{name:"Jane", age:20}, {name:"John", age:30}];
const y = x.map(z=>z.name); // return name of each object into the new array
console.log(y); // ["Jane", "John"]
0

Being able to extend classes is the key to powerful programming. PHP provides you all the tools you need to obtain rich and extendible classes. But sometimes you find yourself using the wrong tool.. Look at this very simple class:

class Post {
    const TABLE = 'posts';
    const TYPE = 'post';

    static public function all() {
        $select = "SELECT * FROM `%s` WHERE `type` = '%s'\n";
        echo sprintf($select, self::TABLE, self::TYPE);
    }
}

Post::all();
// SELECT * FROM `posts` WHERE `type` = 'post'

Look at the class constants TABLE and TYPE and you can almost feel what our next subclass will look like, and how minimalistic it will be:

class Comment extends Post {
    const TYPE = 'comment';
}

Comment::all();
// SELECT * FROM `posts` WHERE `type` = 'post'
// ^ is not what we meant...

Though the intentions are good, the result is unwanted: we want to select all from posts where type is ‘comment’, not ‘post’.
That is the blame for using ’self’ to obtain the class constants. ’self’ Refers to current class it is defined in, which is not the inherited class. But we don’t want to rewrite all the methods – that misses the point of all this. Using ’static public $TABLE’ as property is an option, but why not use class constants now that PHP provides them.

A lot of people use ’self’ to refer to class constants or static methods, but as soon as you start inheriting such class (often classes are coded without the intention to inherit them at some stage), this issue comes up.

We will have to use ‘late static binding’ in stead of ’self’, simply by using ’static’, which is computed using runtime information. Now take a look at the next example:

class Post {
    const TABLE = 'posts';
    const TYPE = 'post';

    static public function all() {
        $select = "SELECT * FROM `%s` WHERE `type` = '%s'\n";
        echo sprintf($select, static::TABLE, static::TYPE);
    }
}

Post::all();
// SELECT * FROM `posts` WHERE `type` = 'post'


class Comment extends Post {
    const TYPE = 'comment';
}

Comment::all();
// SELECT * FROM `posts` WHERE `type` = 'comment'
// ^ now that's just what we want

Now we’re selecting all from posts where type is ‘comment’!
And if you explicitly want to call the class you inherited from (perhaps in a constructor for doing some logics) you use ‘parent’ in stead, as here on the ’save’-method:

class Post {
    static public function create() {
        return new static();
    }
    public function save() {
        echo "Inserts into the `posts` table\n";
    }
}

class Chat extends Post {
    public function save() {
        parent::save();
        echo "Inserts into the `post_recipient` table\n";
    }
}

Chat::create()->save();
// Inserts into the `posts` table
// Inserts into the `post_recipient` table
0

WooCommerce has an awful lot of hooks! Very useful if you want to customize a webshop without touching the WooCommerce core – so you’re safe when updating it. Check out the availability in the documentation!

Here’s an example:
In some occasions you want to do something extra with the WooCommerce checkout form. This could either be adding some HTML, or a while Javascript file just for this occasion, or something completely different.
You can use ‘woocommerce_after_checkout_form’ or ‘woocommerce_before_checkout_form’ from your plugin or (child-)theme.

function add_scripts_checkout() {
    wp_enqueue_script(
        'checkout-script',
        get_stylesheet_directory_uri() . '/includes/js/checkout.js',
        array( 'jquery' )
    );
}
function add_html_checkout(){
    $img_src = get_stylesheet_directory_uri() . '/img/certified-something.png'
    echo '<img src="'.$img_src.'"';
    echo ' class="certified-something">';
}

add_action( 'woocommerce_before_checkout_form', 'add_html_checkout' );
add_action( 'woocommerce_after_checkout_form', 'add_scripts_checkout');

As you can see, you can either output HTML straight away by echo’ing right before / after the markup of the checkout-form, or handle things programatically like enqueing a javascript file. This Javascript file will only be loaded wherever there’s a WooCommerce checkout-form present, and thus you can target those checkout forms directly with your Javascript functionality.

0

If you want to retrieve public data from Facebook (typically a Page) you nowadays will have to use Facebook Graph API and thus provide an access-token. In the old days you could use some of the available feeds (following the RSS-principles) but that was back then.
I’ve seen some strange hacks around, but it’s quite simple to pull public data with a non-expiring access-token. (Impatient ones go here!)

Access-tokens in short

The idea behind access-tokens is that the system you’re communicating with ‘knows something about somebody’; is that somebody a valid logged-in user, what is that user allowed to do, etc. For every request your application makes, on behalf of that user, you pass that access-token to the system.
Access tokens typically expire; to reduce the chance of getting in the wrong hands (Facebook offers appsecret_proof to prevent hijacking). When the user uses the application access-tokens get renewed in the background, when the user doesn’t use the application before the access-token expires, a new login / authorization action will provide a new one.
Facebook Graph API knows short- and long-term access-tokens, where the short-term tokens are used by the application’s client and the long-termed by the application’s server (using an app-secret).

Let the app do the talking

The access-tokens mentioned above are “user access-tokens”. If you just want to pull out some public data, having users logging in and expiring access tokens is probably not what you want. But you can also in make requests to Facebook’s API on behalf of an a Facebook App, in stead of a user, working with an “app access-token”.
When you create an app at Facebook for developers, you have an app-id and an app-secret. In stead of requesting “oauth” for an app access-token, you can construct one yourself  by combining your app-id and app-secret, when requesting from a server (not client!!), like this:

https://graph.facebook.com/endpoint?key=value&amp;access_token=app_id|app_secret

Note: using “appsecret_proof” doesn’t make much sense here

Let’s get practical

I needed to display posts from a Facebook Page via Javascript (with Ember.js to be exact). No further Facebook interaction was needed, so user access tokens were not what I was waiting for, I needed an access token that does not expire, and that I’m in control of, not some user. So I opted for using the “app_id|app_secret”-solution with an app access-token, mentioned above. That I had set up as a proxy on my webserver. The flow now is: Client -> Webserver -> FB API -> Webserver -> Client.

Flow proxy fb api permanent access-token

 

Here’s the method I used at the webserver (in PHP) to make calls to Facebook Graph API

/**
* Retrieves response from an call
* to Facebook Graph API
* on behalf of an Facebook app.
*
* Returns a JSON-string when $returnJson is true,
* when false it returns an object of the
* JSON decoded data.
*
* The object-parameter determines which FB object you
* are requesting (posts, photos etc)
*
* The fields-paramater determines which fields
* of that object should be included in your data
*
* Try things out at the FB Graph API explorer tool first:
* https://developers.facebook.com/tools/explorer
*
* @param string $fbUser (id of fb-user to pull from)
* @param string $appId
* @param string $appSecret
* @param string $appApiVersion
* @param string $object (what object to request. Default: posts)
* @param array $fields (what fields of the object to request. Default: message, created_time)
* @param boolean $returnJson (return JSON-string otherwise object. Default: true)
* @return string / object
*/

public function getFacebookPosts(
$fbUser,
$appId, $appSecret, $appApiVersion,
$object='posts',
$fields=array('message','created_time'),
$returnJson=true
)
{
$fieldsString = implode(",", $fields);
$accessToken = sprintf("%s|%s", $appId, $appSecret);

$fbApiUrl = sprintf("https://graph.facebook.com/%s/%s?fields=%s{%s}&amp;access_token=%s", $appApiVersion, $fbUser,$object, $fieldsString, $accessToken);

$json = file_get_contents($fbApiUrl);
return $returnJson ? $json : json_decode($json);
}

Load the Facebook data in Ember.js

For the ones interested in how you easily can work with the requested Facebook data in Ember, stay tuned and I’ll provide a snippet for your serializer.
Of course we’re using Ember Data to handle all this. First of all, model your data by adding a new model. You will at least have to define those attributes corresponding the fields-parameter from your API request.
For example ‘message’,’created_time’. Now setup your adapter, connecting to your ‘proxied webservice’.
When the data comes back to your client and back to Ember, Facebook has a slight different JSON-format than Ember expects, so we have to serialize it a little.
And what Ember expects depends whether you’re using the ‘old’ or ‘new’ adapter for handling JSON.
The previous de facto standard was the Ember Data RESTAdapter, the current de facto standard is the Ember Data v2 JSONAPIAdapter. What do they expect:

  • RESTAdapter: {“modelNamePlural”:[…]}
  • JSONAPIAdapter: {“data”:[…]}

Ember Data RESTSerializer (using the extractArray method)

export default DS.RESTSerializer.extend({

extractArray: function (store, primaryType, payload) {
const posts = payload.posts.data;
const newPayload = { posts: posts };
return this._super(store, primaryType, newPayload);
}

});

 

Ember Data JSONAPISerializer (using the normalizeArrayResponse method)

export default DS.JSONAPISerializer.extend({

normalizeArrayResponse: function (store, primaryType, payload, id, requestType) {
const posts = payload.posts.data;
const newPayload = { data: posts };
return this._super(store, primaryType, newPayload, id, requestType);
}

});

 

This is the model I’m using
Model

export default DS.Model.extend({

message: DS.attr('string'),
created_time: DS.attr('date'),
status_type: DS.attr('string'),
comment_count: DS.attr('number', {defaultValue: 0}),
like_count: DS.attr('number', {defaultValue: 0}),
share_count: DS.attr('number', {defaultValue: 0}),
link: DS.attr('string'),
picture: DS.attr('string'),
caption: DS.attr('string'),
description: DS.attr('string'),
name: DS.attr('string')

});

The fields comment_count, like_count and share_count do not exist in the response from Facebook, they’re nested in ‘comments’, ‘likes’ and ’shares’ objects. With the serializer’s normalize method you can make the response-data fit into your model.
Another thing I did here is adjusting the date-format given by Facebook, as Moment.js by default will fail with that input on Safari and IE, when there’s no colon in the time-zone.

Add this method to your serializer (either RESTSerializer or JSONAPISerializer) to get the model straight:

normalize : function (modelClass, resourceHash, prop) {
// likes
if( resourceHash.likes.summary.total_count ) {
resourceHash.like_count = resourceHash.likes.summary.total_count;
}
// comments
if( resourceHash.comments.summary.total_count ) {
resourceHash.comment_count = resourceHash.comments.summary.total_count;
}
// shares
if( resourceHash.shares &amp;&amp; resourceHash.shares.count ) {
resourceHash.share_count = resourceHash.shares.count;
}

// dates
// prevent "2016-02-16T12:59:03+0000" from failing in moment.js
// convert them to "2016-02-16T12:59:03+00:00"
// (note colon in milliseconds)
resourceHash.created_time = resourceHash.created_time.replace(/([A-Z0-9:-]\+[0-9]{2})([0-9]{2})/, "$1:$2");

return this._super(modelClass, resourceHash, prop);
}
0

For a project I’ve been working on recently I needed to convert text, retrieved from an API, to HTML.
The text isn’t “mark-down” but does contain line-breaks, URLs, hash-tags etc. etc. Since there wasn’t any add-on or snippet doing exactly this, I decided to roll my own.
Now I want to share my ’snippet’ (only one file) with you, so you hopefully can benefit from it too, or modify it if needed.

You can find it here on Github Gist

As you can see I’ve included my test-file as well. Download both files and place “text-2-html.js” into your “app/helpers” directory (and “text-2-html-test.js” into your “tests/units/helpers” directory).

Now you can use the helper like this:

{{text-2-html message}}

By default the helper converts:

  • new-lines (attribute “nl”)
  • URLs (attribute “url”)
  •  e-mails (attribute “email”)

When configured it also converts:

  • phone-numbers (attribute “phone”) — note: buggy!!
  • hash-tags (attribute “hashTagURLPrefix”)
  • mentions (attribute “mentionURLPrefix” and “mentionAtShown”)

Usage

Here are some examples to illustrate these options:

Handlebars template

basic:
{{text-2-html item.message}}

defining target:
{{text-2-html item.message target='_system'}}

convert phone-number, note this is somehow buggy:
{{text-2-html item.message target='_system' phone=true}}

hash-tags and mentions, twitter links:
{{text-2-html item.message hashTagURLPrefix='https://www.twitter.com/hashtag/' mentionURLPrefix='https://www.twitter.com/'}}

hash-tags and mentions, hiding the @-sign for mentions, facebook style
{{text-2-html item.message hashTagURLPrefix='https://www.facebook.com/hashtag/' mentionURLPrefix='https://www.facebook.com/' mentionAtShown=false}}

See full reference below

I must tell that the phone-number feature might act a bit buggy (that’s why it’s not a default feature); it does recognize must phone-number formats but also other numbers, intended for something else. Try and see if it works for your purposes, otherwise I might work on that one day.

Assume we have the following text:

Hey\nHow're ya doin'?\nSorry you can't get through.\nWhy don't you leave me your name, mine is @planetcrypton, and your number, mine is +4560632840, and I'll get back to you.\nDon't forget to visit http://www.wearedelasoul.com/\nMail me at plug2@reversed-yogurt.com #oldschool #plug1 #plug2 #plug3

With this in your Handlebars:

{{text-2-html item.message hashTagURLPrefix='https://www.twitter.com/hashtag/' mentionURLPrefix='https://www.twitter.com/'}}

Would result in the following HTML-output:
Hey<br>
How&#39;re ya doin&#39;?<br>
Sorry you can&#39;t get through.<br>
Why don&#39;t you leave me your name, mine is <a href=”https://www.twitter.com/planetcrypton” target=”_system”>@planetcrypton</a>, and your number, mine is <a href=”tel:+4560632840″>+4560632840</a>, and I&#39;ll get back to you.<br>
Don&#39;t forget to visit <a href=”http://www.wearedelasoul.com/” target=”_system”>http://www.wearedelasoul.com/</a><br>
Mail me at <a href=”mailto:plug2@reversed-yogurt.com”>plug2@reversed-yogurt.com</a> <a href=”https://www.twitter.com/hashtag/oldschool” target=”_system”>#oldschool</a> <a href=”https://www.twitter.com/hashtag/plug1″ target=”_system”>#plug1</a> <a href=”https://www.twitter.com/hashtag/plug2″ target=”_system”>#plug2</a> <a href=”https://www.twitter.com/hashtag/plug3″ target=”_system”>#plug3</a>

Just what we needed, eh? 🙂

Reference

Here’s a documentation of the available attributes, so you can go ahead and use it:

Attr. name Type Default value Note
nl boolean true convert new-lines
url boolean true convert URLs
email boolean true convert e-mail-addresses
phone boolean false convert phone-numbers, might be buggy
target string _blank define target for all links
hashTagURLPrefix string null convert hash-tags and define the URL-prefix
mentionURLPrefix string null convert mentions and define the URL-prefix
mentionAtShown boolean true show / hide the @-sign for mentions

 

0

Denne artikel skriver jeg i anledning af blog-posten på nedenstående link:

https://www.wordfence.com/blog/2016/02/wordpress-password-security/

Jeg anbefaler at du læser ovenstående blog-post, men her er en kort opsummering:
Wordfence sikkerhedskonsulenterne har i en måling på 16 timer registreret over 6 millioner password angreb på over 70 tusinde forskellige websites der bruger deres software.
Disse angreb er hovedsageligt såkaldte ‘brute force’ angreb, som betyder at en ‘robot’ forsøger forskellige login kombinationer indtil der er ‘bingo’.
De viser en liste over top lande hvor angrebene er blevet udført fra, og Ukraine får her en velfortjent førsteplads.

‘Brute force attacks’ er den mest enkele form for angreb og er derfor også en af de mest forekommende.

Hvordan undgår man disse angreb?

Ved en kombination af hosting hos Away IT og et af sejKos sikkerhedspakker står man stærkt:

  • Geo-blokkering gør, at ens backend kun er tilgængelig fra danske IP adresser. Skal du på ferie og arbejde på din hjemmeside, kan vi låse op for det pågældende land.
  • Der er begrænsning på antal login-forsøg, ved overskridelse bliver IP adressen blokkeret.
  • Blokkering af IP adresser som er blacklistet hos Wordfence.

Fordi kodeord ofte er det svageste led i en hjemmesides sikkerhed, kan man, sidst men ikke mindst, gøre hele websitet fri for kodeord via en såkaldt ‘two-factor authentication’. Læs mere herom i denne artikel “Bare glem dine kodeord”.

0

Models, as the name presumes, do ‘model’ data; they sort of outline data, from which content can be created. With that in mind, it’s not unlikely you want to validate created content every once and a while. Notify that given content is or isn’t the way the model intended the data to be.

The approach in this article is adding validation functionality (call it tools) to a model, before modelling. When a developer ‘models a model’ he can tell what each field should or shouldn’t accept for given content.
It’s a REALLY SIMPLE approach, there are other more complex, perhaps more ‘emberish’, solutions out there. But this has you up and running quickly, and has useful functionality for templates and can thus be used in combination with input elements, especially when you have your input bound with model-fields, such as {{ input type=”text” value=model.employeeName }}.

Download the file here

It’s on GitHub Gist now 🙂

I’m assuming you’re using Ember-CLI.
You can follow the extended instruction in the file-comment, but I’ll briefly show you what it’s about, so you get an idea whether this suits your project or not:

Put the file in you model directory (or somewhere more appropriate), and import it to your desired model, say a Person model:

/your-app/models/person.js

import DS from 'ember-data';
import Validation from 'your-app/models/validation';

export default Validation.extend({
    firstName :  DS.attr('string'),  // required
    lastName :   DS.attr('string'),  // required
    age :        DS.attr('integer'), // numeric
    town :       DS.attr('string'),  // optional
    married :    DS.attr('boolean',
             {defaultValue: false}), // boolean
    email :      DS.attr('string'),  // e-mail

    requiredFields : ['firstName', 'lastName', 'age'],
    numericFields : ['age'],
    booleanFields : ['married'],
    emailFields : ['email']
});

Elsewhere in your code you could be doing something like this:

var x = this.store.createRecord('person');
x.firstName = 'Austin'; // is OK
x.lastName = 'Powers'; // is OK
x.town = 'London'; // is OK
x.age = 'thirty'; // fails
x.married = 'not really'; // fails
x.email = 'fax only'; // fails
var ok = x.is_valid();
if( ok ) {
    console.log('OK');
}else{
    console.log('ERROR');
    console.log('All error-messages:', x.get('errors').get('messages'));
}

// or validate fields specifically:
if( !this.get('model').is_valid_field( 'firstName' ) ) {
    console.log('firstName' + ' has error:');
    this.get('model').get('errors').errorsFor( 'firstName' ).forEach(function(err){
        console.log(err.message);
    });
}else{
    console.log('firstName' + ' is all good');
}

Those model-errors are very useful in your templates, where you could either use all error-messages, model.errors.messages or field-specific error-messages, model.errors.age.errors (=array where each error-item has a message).
More info regarding model-errors at: http://emberjs.com/api/data/classes/DS.Errors.html.

Read how and when to set error-messages in the file’s comment.
Good luck and I hope you can use it some day!

0

Så er det fra i dag af, at Google søgemaskines indeksering afhænger af mobilvenlighed (Læs: http://googlewebmastercentral.blogspot.dk/…/finding-more-mo…)
Betragter Google din hjemmeside som mobilvenlig, får den en højere indeksering.
Test her om din hjemmeside er ‘Google Mobile Friendly’:
https://www.google.com/webmasters/tools/mobile-friendly/

Kontakt sejKo hvis du vil høre nærmere om din hjemmesidens mobilvenlighed.

0

As much as you want to offer localization for your project in your templates, you also want to offer that in your Javascript. When a project supports Spanish you don’t want any alerts from Javascript to be in English, comprendo?
There are several ways to accomplish that, but it would be easy for your translation-team to have all texts in the same PO file, and easy for you to have it all in the same MO file 🙂

Having that done in Django is fairly straightforward: Create a .JS file, as if it was any other document Django renders for you. You have rendered HTML and probably JSON documents before. We build a Javascript document in the very same way. This .JS file contains all the strings you’re using in the client-side of your project. Include this file on all, or the required pages, and all your scripts will be able to access its variables, containing your texts – in Spanish.

Get busy!

Let’s start where everything starts in Django; the URLs:

myproject/urls.py

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',

    # Admin
    url(r'^admin/', include(admin.site.urls)),

    # JS locale
    url(r'^js/locale.js$', 'myproject.views.js_locale', name='js-locale'),

    # etc your apps here etc
)

In other words: “/js/locale.js” will be rendered by this view: “myproject.views.js_locale”. Let’s create that view:

myproject/views.py

from django.shortcuts import render, render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.cache import cache_page

@cache_page(60 * 60 * 24)
def js_locale(request, template_name = 'js/vars.js', **kwargs):
    '''
    Javascript document
    containing strings for
    the client-side scripts
   
    '''

    vars = [
        ['var SK',                        'SK || {}', False],
        ['SK.text',                       'SK.text || {}', False],
        ['SK.text.rusure_cancel_booking', _("Are you sure that you want to cancel this booking?"), True],
        ['SK.text.error_occorred',         _("An error occurred"), True],
    ]
    return render_to_response(template_name,
                              {
                                'vars' : vars,
                              },
                              mimetype="text/javascript")

You can see what I’m intending to do: Apart from the document is heavily cached 🙂 I’m sending definitions to a template which looklike they can become Javascript.
In the end the document gets the header “text/javascript”, so everybody knows what we’re talking about.

Let’s have a look at the template:

myproject/templates/js/vars.js

{% for var in vars %}{{ var.0 }}={% if var.2 %}"{% endif %}{{ var.1|escapejs }}{% if var.2 %}"{% endif %};{% endfor %}

So we’re looping over the list where each item (also) is a list with three indexes:

  • 0: variable definition
  • 1: variable value
  • 2: boolean; quote the value (for strings) or not

Accessing the URL “/js/locale.js” will now provide you this:

var SK = SK || {};
SK.text = SK.text || {};
SK.text.rusure_cancel_booking = "Are you sure that you want to cancel this booking?";
SK.text.error_occorred = "An error occurred";

Let’s include that file in our document template (or where ever you need it):

myproject/templates/doc.html

{% load static %}

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">

    <title>What you hear is not a ....</title>

    <script src="{% url 'myproject.views.js_locale' %}" type="text/javascript"></script>
    <script src="{% static 'assets/js/scripts.js' %}" type="text/javascript"></script>
</head>
    <body>
        {% block markup %}
        {% endblock %}
    </body>
</html>

From now on you can access those translated texts from any script.
For example “assets/js/scripts.js” could now display a confirm in Spanish, when translated:

// stuff above

if( confirm( SK.text.rusure_cancel_booking ) ){
    cancel_booking();
}

// stuff below

Keep DRY, not dirty

Everything works! But…
As your texts will grow while proceeding your client-side scripts, it will pollute your view with a lot of strings, who don’t really belong there. So let’s move those to a separate file. My approach is to put all the ‘common’ texts in there, and let the text-namespace be defined in the view (or perhaps several views).

myproject/views.py

from django.shortcuts import render, render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.cache import cache_page

# note this new import
from js_locale import sk_js_vars

@cache_page(60 * 60 * 24)
def js_locale(request, template_name = 'js/vars.js', **kwargs):
    '''
    Javascript document
    containing strings for
    the client-side scripts

    '''

    namespacing = [
        ['var SK',  'SK || {}', False],
        ['SK.text', 'SK.text || {}', False],
    ]
    vars = namespacing + cw_js_vars
    return render_to_response(template_name,
                              {
                                'vars' : vars,
                              },
                              content_type="text/javascript")

myproject/js_locale.py

from django.utils.translation import ugettext_lazy as _

'''
[name, value, value must be quoted or not]
'''


sk_js_vars = [
    ['SK.text.save',                    _("Save"), True],
    ['SK.text.delete',                  _("Delete"), True],
    ['SK.text.add',                     _("Add"), True],
    ['SK.text.remove',                  _("Remove"), True],
    ['SK.text.confirm',                 _("Confirm"), True],
    ['SK.text.confirmed',               _("Confirmed"), True],
    ['SK.text.decline',                 _("Decline"), True],
    ['SK.text.declined',                _("Declined"), True],
    ['SK.text.pending',                 _("Pending"), True],
    ['SK.text.unsaved',                 _("Unsaved"), True],
    ['SK.text.not_invited_yet',         _("Not invited yet"), True],
    ['SK.text.awaiting_invitation',     _("Awaiting invitation"), True],
    ['SK.text.rusure_cancel_booking',   _("Are you sure that you want to cancel this booking?"), True],
    ['SK.text.rusure_make_booking',     _("Are you sure that you want to make this booking?"), True],
    ['SK.text.error_occured',           _("An error occured"), True],
    ['SK.text.could_not_save_booking',  _("Could not save booking"), True],
    ['SK.text.not_enough_credits',      _("You don't have enough credits for this."), True],
    ['SK.text.buy_credits',             _("Buy credits"), True],
    ['SK.text.space_n_location',        _("Space and location"), True],
    ['SK.text.location',                _("Location"), True],
    ['SK.text.for',                     _("for"), True],
    ['SK.text.hours',                   _("hours"), True],
    ['SK.text.at',                      _("at"), True],
    ['SK.text.until',                   _("until"), True],
    ['SK.text.error',                   _("Error"), True],
    ['SK.text.now',                     _("Now"), True],
    ['SK.text.max_exceeded',            _("You have exceeded the allowed maximum"), True],
    ['SK.text.invalid_email',           _("E-mail address not valid"), True],
    ['SK.text.double_entry',            _("Double entry"), True],
]