diff --git a/app/models/LevelComponent.coffee b/app/models/LevelComponent.coffee
index d8a9bc4f1..100ced453 100644
--- a/app/models/LevelComponent.coffee
+++ b/app/models/LevelComponent.coffee
@@ -12,6 +12,7 @@ module.exports = class LevelComponent extends CocoModel
   @LandID: '524b7aff7fc0f6d519000006'
   @CollidesID: '524b7b857fc0f6d519000012'
   @PlansID: '524b7b517fc0f6d51900000d'
+  @ProgrammableID: '524b7b5a7fc0f6d51900000e'
   urlRoot: '/db/level.component'
 
   set: (key, val, options) ->
diff --git a/app/templates/play/level/tome/spell_palette_entry.jade b/app/templates/play/level/tome/spell_palette_entry.jade
index 2d1a94f4d..5dfb2ae4f 100644
--- a/app/templates/play/level/tome/spell_palette_entry.jade
+++ b/app/templates/play/level/tome/spell_palette_entry.jade
@@ -1 +1 @@
-span.doc-title= doc.title
\ No newline at end of file
+span.doc-title(data-property-name=doc.name)= doc.title
\ No newline at end of file
diff --git a/app/treema-ext.coffee b/app/treema-ext.coffee
index ac9a141d9..a12630d3b 100644
--- a/app/treema-ext.coffee
+++ b/app/treema-ext.coffee
@@ -266,7 +266,7 @@ class InternationalizationNode extends TreemaNode.nodeMap.object
     res = super(arguments...)
     res = (r for r in res when r[0] isnt '-')
     res
-    
+
   populateData: ->
     super()
     if Object.keys(@data).length is 0
@@ -286,7 +286,12 @@ class InternationalizationNode extends TreemaNode.nodeMap.object
       @workingSchema.props = (prop for prop,_ of @parent.schema.properties when prop isnt 'i18n')
 
     for i18nProperty in @workingSchema.props
-      i18nChildSchema.properties[i18nProperty] = @parent.schema.properties[i18nProperty]
+      parentSchemaProperties = @parent.schema.properties ? {}
+      for extraSchemas in [@parent.schema.oneOf, @parent.schema.anyOf]
+        for extraSchema in extraSchemas ? []
+          for prop, schema of extraSchema?.properties ? {}
+            parentSchemaProperties[prop] ?= schema
+      i18nChildSchema.properties[i18nProperty] = parentSchemaProperties[i18nProperty]
     return i18nChildSchema
     #this must be filled out in order for the i18n node to work
 
diff --git a/app/views/game-menu/InventoryView.coffee b/app/views/game-menu/InventoryView.coffee
index 0c710deb2..dfc45a4ea 100644
--- a/app/views/game-menu/InventoryView.coffee
+++ b/app/views/game-menu/InventoryView.coffee
@@ -336,6 +336,7 @@ module.exports = class InventoryView extends CocoView
     gearByLevel =
       'dungeons-of-kithgard': {feet: 'simple-boots'}
       'gems-in-the-deep': {feet: 'simple-boots'}
+      'forgetful-gemsmith': {feet: 'simple-boots'}
       'shadow-guard': {feet: 'simple-boots'}
       'true-names': {feet: 'simple-boots', 'right-hand': 'longsword'}
       'the-raised-sword': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic'}
@@ -345,9 +346,11 @@ module.exports = class InventoryView extends CocoView
       'lowly-kithmen': {feet: 'simple-boots', 'right-hand': 'longsword', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
       'closing-the-distance': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', eyes: 'crude-glasses'}
       'the-final-kithmaze': {feet: 'simple-boots', 'right-hand': 'longsword', torso: 'leather-tunic', 'programming-book': 'programmaticon-i', eyes: 'crude-glasses'}
-      'kithgard-gates': {feet: 'simple-boots', 'right-hand': 'builders-hammer'}
+      'kithgard-gates': {feet: 'simple-boots', 'right-hand': 'builders-hammer', torso: 'leather-tunic'}
       'defense-of-plainswood': {feet: 'simple-boots', 'right-hand': 'builders-hammer'}
-      # TODO: figure out leather boots for plainswood (or next one?)
+      'winding-trail': {feet: 'leather-boots', 'right-hand': 'builders-hammer'}
+      'thornbush-farm': {feet: 'leather-boots', 'right-hand': 'builders-hammer', eyes: 'crude-glasses'}
+      'a-fiery-trap': {feet: 'leather-boots', 'right-hand': 'builders-hammer', eyes: 'crude-glasses'}
     return unless necessaryGear = gearByLevel[@options.levelID]
     if @inserted
       if @supermodel.finished()
diff --git a/app/views/i18n/I18NEditLevelView.coffee b/app/views/i18n/I18NEditLevelView.coffee
index 20dc275a4..475aef524 100644
--- a/app/views/i18n/I18NEditLevelView.coffee
+++ b/app/views/i18n/I18NEditLevelView.coffee
@@ -1,5 +1,6 @@
 I18NEditModelView = require './I18NEditModelView'
 Level = require 'models/Level'
+LevelComponent = require 'models/LevelComponent'
 
 module.exports = class I18NEditLevelView extends I18NEditModelView
   id: "i18n-edit-level-view"
@@ -25,24 +26,33 @@ module.exports = class I18NEditLevelView extends I18NEditModelView
       if i18n = doc.i18n
         @wrapRow "Guide article name", ['name'], doc.name, i18n[lang]?.name, ['documentation', 'specificArticles', index]
         @wrapRow "'#{doc.name}' description", ['description'], doc.description, i18n[lang]?.description, ['documentation', 'specificArticles', index], 'markdown'
-    
+
     # sprite dialogues
     for script, scriptIndex in @model.get('scripts') ? []
       for noteGroup, noteGroupIndex in script.noteChain ? []
         for spriteCommand, spriteCommandIndex in noteGroup.sprites ? []
           pathPrefix = ['scripts', scriptIndex, 'noteChain', noteGroupIndex, 'sprites', spriteCommandIndex, 'say']
-          
+
           if i18n = spriteCommand.say?.i18n
             if spriteCommand.say.text
               @wrapRow "Sprite text", ['text'], spriteCommand.say.text, i18n[lang]?.text, pathPrefix, 'markdown'
             if spriteCommand.say.blurb
               @wrapRow "Sprite blurb", ['blurb'], spriteCommand.say.blurb, i18n[lang]?.blurb, pathPrefix
-          
+
           for response, responseIndex in spriteCommand.say?.responses ? []
             if i18n = response.i18n
               @wrapRow "Response button", ['text'], response.text, i18n[lang]?.text, pathPrefix.concat(['responses', responseIndex])
-          
+
     # victory modal
     if i18n = @model.get('victory')?.i18n
       @wrapRow "Victory text", ['body'], @model.get('victory').body, i18n[lang]?.body, ['victory'], 'markdown'
- 
\ No newline at end of file
+
+    # code comments
+    for thang, thangIndex in @model.get('thangs') ? []
+      for component, componentIndex in thang.components ? []
+        continue unless component.original is LevelComponent.ProgrammableID
+        for methodName, method of component.config?.programmableMethods ? {}
+          if (i18n = method.i18n) and (context = method.context)
+            for key, value of context
+              path = ['thangs', thangIndex, 'components', componentIndex, 'config', 'programmableMethods', methodName]
+              @wrapRow "Code comment", ["context", key], value, i18n[lang]?.context[key], path
diff --git a/app/views/play/level/tome/DocFormatter.coffee b/app/views/play/level/tome/DocFormatter.coffee
index 259998c75..147702eef 100644
--- a/app/views/play/level/tome/DocFormatter.coffee
+++ b/app/views/play/level/tome/DocFormatter.coffee
@@ -105,7 +105,7 @@ module.exports = class DocFormatter
           while spokenLanguage
             spokenLanguage = spokenLanguage.substr 0, spokenLanguage.lastIndexOf('-') if fallingBack?
             if spokenLanguageContext = @doc.i18n[spokenLanguage]?.context
-              context = spokenLanguageContext
+              context = _.merge context, spokenLanguageContext
               break
             fallingBack = true
         obj[prop] = _.template val, context if context
diff --git a/app/views/play/level/tome/Spell.coffee b/app/views/play/level/tome/Spell.coffee
index 0c0e50e8d..847f79681 100644
--- a/app/views/play/level/tome/Spell.coffee
+++ b/app/views/play/level/tome/Spell.coffee
@@ -22,6 +22,8 @@ module.exports = class Spell
     @levelType = options.level.get('type', true)
 
     p = options.programmableMethod
+    @commentI18N = p.i18n
+    @commentContext = p.context
     @languages = p.languages ? {}
     @languages.javascript ?= p.source
     @name = p.name
@@ -61,6 +63,18 @@ module.exports = class Spell
   setLanguage: (@language) ->
     #console.log 'setting language to', @language, 'so using original source', @languages[language] ? @languages.javascript
     @originalSource = @languages[language] ? @languages.javascript
+    # Translate comments chosen spoken language.
+    return unless @commentContext
+    context = $.extend true, {}, @commentContext
+    if @commentI18N
+      spokenLanguage = me.get 'preferredLanguage'
+      while spokenLanguage
+        spokenLanguage = spokenLanguage.substr 0, spokenLanguage.lastIndexOf('-') if fallingBack?
+        if spokenLanguageContext = @commentI18N[spokenLanguage]?.context
+          context = _.merge context, spokenLanguageContext
+          break
+        fallingBack = true
+    @originalSource = _.template @originalSource, context
 
   addThang: (thang) ->
     if @thangs[thang.id]