Localizing Go to JavaScript

While working a Go backend for a side-project, I implemented a custom templating system among other things. For my project, I needed to be able to pass nonce values down to my JavaScript. I realized that keeping the data in the front-end up-to-date with the backend would require a lot of leg work. In order to save time and effort, I built the localize package.

This package takes a pre-defined Go data structure and recursively translates it to JavaScript primitives. The JavaScript that is spit back out can be used in just about any fashion, but it is designed to work best with the html/template package. Since the html/template package provides support for calling functions assigned to data passed to the template.Template.Execute() function, templates can fire off the localization process themselves. Once you have a template setup to utilize the localize package, it’s a fire and forget situation. The best kind, in my opinion.

Here’s a simple example of the syntax:

import(
    "github.com/foresthoffman/localize"
)

func main() {

    // generates a new localization map with the provided data
    dataMap, err := localize.NewMap(
        &map[string]interface{}{

            // "motd" will hold an array with an element with the value, "Hello world, welcome
            // to a new day!"
            "motd": "Hello world, welcome to a new day!",

            // "nonce" will hold an object with an element with the key, "login", and the value,
            // "LaKJIIjIOUhjbKHdBJHGkhg"
            "nonce": map[string]string{
                "login": "LaKJIIjIOUhjbKHdBJHGkhg",
            },
        },

    // this will tell the localizer to assign the data to the "_localData" global JavaScript
    // variable
    ).SetVarName("_localData")

    // ...proper error handling, data manipulation, etc.
}

Here’s a more complex example using the standard html/template and net/http packages. This one is fully functional; copy-paste it, and try it out (make sure to go get github.com/foresthoffman/localize):

package main

import (
    "github.com/foresthoffman/localize"
    "html/template"
    "net/http"
)

var tmpl *template.Template
var page *Page

type Page struct {
    LocalizedData *localize.LocalizeMap
}

func RootHandler(w http.ResponseWriter, rq *http.Request) {

    // Executes the template, and runs any template actions, which includes the LocalizedData's
    // "JS()" function. The template will be returned to the client's browser along with the
    // new JavaScript data.
    err := tmpl.Execute(w, *page)
    if nil != err {
        panic(err)
    }
}

func main() {

    // prepares the localized data
    dataMap, err := localize.NewMap(
        &map[string]interface{}{

            // "motd" will hold an array with an element with the value, "Hello world, welcome
            // to a new day!"
            "motd": "Hello world, welcome to a new day!",

            // "nonce" will hold an object with an element with the key, "login", and the value,
            // "LaKJIIjIOUhjbKHdBJHGkhg"
            "nonce": map[string]string{
                "login": "LaKJIIjIOUhjbKHdBJHGkhg",
            },
        },
    ).SetVarName("_localData")
    if nil != err {
        panic(err)
    }

    // sets up a page that will provide the template with the LocalizedData field
    page = &Page{
        LocalizedData: dataMap,
    }

    // normally this would be in an HTML file on its own, but for the sake of brevity...
    templateBody := `
        <!DOCTYPE html>
        <html>
        <head>
            <title>Hello world!</title>
        </head>
        <body>
            <div class="page">
                <h1>The message of the day is: <span class="motd"></span></h1>
            </div>

            <!--
            calls the "JS()" function of the "LocalizedData" of the
            object that was passed to the template.
            -->
            <script type="text/javascript">{{.LocalizedData.JS}}</script>
            <script type="text/javascript">
                window.onload = function() {

                    // Access the first element of the motd property of the _localData variable
                    // to get the message of the day, and then insert it into the motd span of
                    // the header tag on the page.
                    document.querySelector(".page .motd").innerText = _localData.motd[0];
                };
            </script>
        </body>
        </html>
    `
    tmpl, err = template.New("hello").Parse(templateBody)
    if nil != err {
        panic(err)
    }

    // This fires up the webserver and waits for connections to "http://localhost:3000/". Hitting
    // that page will present the client with the following header text:
    //
    // "The message of the day is: Hello world, welcome to a new day!"
    http.HandleFunc("/", RootHandler)
    http.ListenAndServe(":3000", nil)
}

That’s it! 🙂