Basic Translation for Squarespace

If you need a more detailed translation, just drop me an eMail.
This script translates what is common to all templates: the mobile bar, contact formular and commenting. You can download it here.
This script translates what is common to all templates: the mobile bar, contact formular and commenting. You can download it here. If you need a more detailed translation of a specific template, just drop me an eMail.
The basic usage is very simple. Just copy and paste the snippet under SETTINGS>ADVANCED>CODE INJECTION into the HEADER field. We have used jQuery, so if you already have it in the HEADER field, you should leave this first line out. The provided example is in German. Just replace those translation strings with the language of your preference and that's it.
The script is very straightforward and became beautifully simple, but don't hesitate to improve it – on GitHub, in a comment or just send us an email. As well, for us, the overhead of learning the native YUI framework did not scale-up to the advantages (missing documentation from side of Squarespace) therefore we embedded jQuery – the geeks among you are invited to fork a YUI or a vanilla JS solution.
I will thus not further waste time with the superficial and obvious but if you are interested, I will focus on code-quality and get into theoretical aspects unproportional to the code snippet itself. Too seldom in discussion, I just have to seize this opportunity.
<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript">
  basicTranslation_dictionary = {
    "mobileBar": {
        "Email":     "Email", 
        "Call":      "Anruf", 
        "Map":       "Karte", 
        "Hours":     "Zeiten"
      }, 
    "contactFormLabel": {
        "First Name":     "Vorname", 
        "Last Name":      "Nachname"
      }, 
    "contactFormError": {
        "is required.": "ist erforderlich.", 
        "is not valid. Email addresses should follow the format user@domain.com.": "folgt die Konvention benutzer@domain.com.", 
        "Your form has encountered a problem.": "Oops, da ging etwas schief.",
        "Please scroll down to review.": "", 
        "Please scroll up to review.": ""
      }, 
    "commentingSectionHeader": {
        "Comments":                 "Kommentare", 
        "Newest First":             "Neuste zuerst", 
        "Oldest First":             "Älteste zuerst", 
        "Most Liked":               "Beliebteste zuerst", 
        "Least Liked":              "Unbeliebteste zuerst", 
        "Subscribe via e-mail":     "Per E-Mail Abonieren"
      }, 
    "commentingEntryHeader": {
        "Pending":                 "In Prozess", 
        "Awaiting Moderation":     "Wartet auf Moderator", 
        "Not Logged In":           "Nicht eingelogged"
      }, 
    "commentingEntryButtons": {
        "Preview":           "Vorschau", 
        "Edit":              "Editieren", 
        "Post Comment":      "Veröffentlichen", 
        "Post Reply":        "Antworten"
      }, 
    "commentingTime": {
        "Just now":         "Jetzt", 
        "An":               "", 
        "A":                "", 
        "minute ago":       "Minute alt", 
        "minutes ago":      "Minuten alt", 
        "hour ago":         "Stunde alt", 
        "hours ago":        "Stunden alt", 
        "day ago":          "Tag alt", 
        "days ago":         "Tage alt"
    }
  };

  /************************************************************************************/
  basicTranslation_selectors = {
    "mobileBar":                   ".sqs-mobile-info-bar-trigger-label", //direct, assign
    "contactFormLabel":            "form label.caption", //filter, assign
    "contactFormError":            "form div.field-error", //direct, replace
    "commentingSectionHeader":     "h3.comment-count span, div.comment-controls span, div.comment-controls option", //direct, assign
    "commentingEntryHeader":       "div.comment-header span", //direct, replace
    "commentingEntryButtons":      "div.comment-btn-wrapper>span", //direct, assign
    "commentingTime":              "div.comment-header span.timesince" //direct, replace
  };

  /************************************************************************************/
  /************************************************************************************/
  /************************************************************************************/
  jQuery(document).ready( function() {
    
    var mutationObserver = new MutationObserver(basicTranslation_mutationObserver);
    mutationObserver.observe(document, { subtree: true, childList: true, characterData: true });
    
    basicTranslation_naiveReplace(jQuery(basicTranslation_selectors["mobileBar"]), "mobileBar", basicTranslation_isTextNode);
    basicTranslation_naiveReplace(jQuery(basicTranslation_selectors["contactFormLabel"]), "contactFormLabel", basicTranslation_isTextNode);
  }); //onStartup

  /************************************************************************************/
  basicTranslation_isTextNode = function() {
    return this.nodeType === 3;
  }; //routine
  basicTranslation_isAnyNode = function() {
    return true;
  }; //routine

  /************************************************************************************/
  basicTranslation_mutationObserver = function(mutations, observer) {
    
    for (var i=0; i<mutations.length; i++) {
      var mutationRecord = mutations[i];
      switch (mutationRecord.type) {
        case "childList":
          if (!("addedNodes" in mutationRecord) || mutationRecord.addedNodes.length===0) {
            break;
          } //if
          
          var addedNodes = jQuery(mutationRecord.addedNodes);
          
          basicTranslation_naiveReplace(addedNodes, "contactFormLabel", basicTranslation_isTextNode);
          basicTranslation_naiveReplace(addedNodes, "contactFormError", basicTranslation_isTextNode);
          basicTranslation_naiveReplace(addedNodes, "commentingSectionHeader", basicTranslation_isTextNode);
          basicTranslation_naiveReplace(addedNodes, "commentingEntryHeader", basicTranslation_isTextNode);
          basicTranslation_naiveReplace(addedNodes, "commentingEntryButtons", basicTranslation_isTextNode);
          basicTranslation_naiveReplace(addedNodes, "commentingTime", basicTranslation_isTextNode);
          break;
          
        case "characterData": 
          
          switch (mutationRecord.target.parentNode.className) {
            case "timesince":
              basicTranslation_naiveReplace(jQuery(mutationRecord.target.parentNode), "commentingTime", basicTranslation_isTextNode);
              break;
              
            case "btn-text preview-comment top-level-preview-btn":
              basicTranslation_naiveReplace(jQuery(mutationRecord.target.parentNode), "commentingEntryButtons", basicTranslation_isTextNode);
              break;
          } //switch
          
          break;
      } //switch
    } //for
    
    observer.takeRecords();
  }; //onDemand

  /************************************************************************************/
  basicTranslation_naiveReplace = function(nodes, section, filter) {
    
    nodes
      .find(basicTranslation_selectors[section])
      .addBack(basicTranslation_selectors[section])
      .contents()
      .filter(filter)
      .each(function() {
        for (var originPhrase in basicTranslation_dictionary[section]) {
          var destinationPhrase = basicTranslation_dictionary[section][originPhrase];
          this.data = this.data.replace(originPhrase, destinationPhrase);
        }
      });
  }; //routine
</script>
Whereas the (meta-)model has always to be common to both data and process, the code was designed to make a clear distinction between them. This neatly splits it into two parts and saying that this simplifies maintenance does not quite reveal the whole picture: such designs are of clarity (even more thanks to built-in syntax highlighters), which allow non-programmers to grasp and modify the embedded data naturally.
This very favorable property often crystallizes in larger designs, partially inevitably but often not intentionally. It pays off to keep it in focus all the time, it is a characteristic for well done design and here I provide an example how this exists even in the micro-design – producing not only good code but a productive work environment in turn.
High compactness and splits along natural roles pays off – and that does not always mean stronger computational models or hi-tech tools are needed.
Now to the latter half of the code snippet, the process of translation. Upon close inspection, it becomes obvious, that the unification of the apply procedure went a little too high, using an overly simple find-replace logic for everything. This introduces deformation on the semantics of data (cleverly hidden) which by itself presents an impression of straightforwardness. This is a no-go for code which is subject to further re-usage, expansion or deeper embedding.
So much to the code as it is now. Allow now me to show you, the most beautiful structural feature of the previous version, which went lost in the cleanup of the design:
var dictionary = {
  "contactForm": {
    "First Name": "Vorname", 
    "Last Name": "Nachname"
    }, 
  "mobileBar": {
      "Email": "Email", 
      "Call": "Anruf", 
      "Map": "Karte", 
      "Hours": "Zeiten"
    }
  };

for (var translationType in dictionary) {
  for (var originPhrase in dictionary[translationType]) {
    var destinationPhrase = dictionary[translationType][originPhrase];
    
    switch (translationType) {
      case "contactForm":
        jQuery("label.caption:contains('"+originPhrase+"')").contents().each(function(){ if (this.nodeType == 3) { this.data=destinationPhrase; }} );
        break;
        
      case "mobileBar":
        jQuery(".sqs-mobile-info-bar-trigger-label:contains('"+originPhrase+"')").text(destinationPhrase);
        break;
    } //switch
  } //for
} //for
The classical approach is to have separate traversals of the corresponding dictionary parts, the other approach is to split the control flow inside a common traversal. Both approaches have their prerequisites, sanity trip-over points, readability, maintainability, copy-paste properties etc. and I will not go into detail on the decision. I like how the model which ties data and process together turns here inside-out, classifying in data items in the large but providing instructions on branching "inside of leaf items" - which are (have possibility to be) treated uniformly by a common traversal.
There are often just so many details which one could point out and I guess that is exactly why we love programming so much – the transparent structurality which can be revealed in the final product, but even more through the variety which passes by on the way and always surprises; by hard resistance on wrong approach and by fine, cumbersome nuances all the way through.