diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index 8d326a9e4..2de659342 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -195,6 +195,50 @@
     campaign_multiplayer_description: "... in which you code head-to-head against other players."
     campaign_old_multiplayer: "(Deprecated) Old Multiplayer Arenas"
     campaign_old_multiplayer_description: "Relics of a more civilized age. No simulations are run for these older, hero-less multiplayer arenas."
+    
+  code:
+    if: "if" # Keywords
+    else: "else"
+    elif: "elif"
+    while: "while"
+    loop: "loop"
+    for: "for"
+    break: "break"
+    continue: "continue"
+    then: "then"
+    do: "do"
+    end: "end"
+    function: "function"
+    def: "def"
+    self: "self"
+    hero: "hero"
+    this: "this"
+    Date: "Date" # Globals
+    Vector: "Vector"
+    Array: "Array"
+    Function: "Function"
+    Math: "Math"
+    Number: "Number"
+    Object: "Object"
+    RegExp: "RegExp"
+    String: "String"
+    isFinite: "isFinite" # Built-ins
+    isNaN: "isNaN"
+    parseFloat: "parseFloat"
+    parseInt: "parseInt"
+    decodeURI: "decodeURI"
+    decodeURIComponent: "decodeURIComponent"
+    encodeURI: "encodeURI"
+    encodeURIComponent: "encodeURIComponent"
+    escape: "escape"
+    unescape: "unescape"
+    Infinity: "Infinity"
+    NaN: "NaN"
+    undefined: "undefined"
+    null: "null"
+    Boolean: "Boolean"
+    Error: "Error"
+    arguments: "arguments"
 
   share_progress_modal:
     blurb: "You’re making great progress! Tell your parent how much you've learned with CodeCombat."
diff --git a/app/styles/play/level/tome/spell_translation.sass b/app/styles/play/level/tome/spell_translation.sass
new file mode 100644
index 000000000..daca32ef5
--- /dev/null
+++ b/app/styles/play/level/tome/spell_translation.sass
@@ -0,0 +1,18 @@
+@import "app/styles/mixins"
+@import "app/styles/bootstrap/variables"
+
+.spell-translation-view
+  position: absolute
+  z-index: 9001
+  max-width: 400px
+  pre
+    margin-bottom: 0
+    code
+      white-space: nowrap
+
+html.no-borderimage
+  .spell-translation-view
+    background: transparent url(/images/level/popover_background.png)
+    background-size: 100% 100%
+    border: 0
+      
diff --git a/app/templates/play/level/tome/spell_translation.jade b/app/templates/play/level/tome/spell_translation.jade
new file mode 100644
index 000000000..991b488e3
--- /dev/null
+++ b/app/templates/play/level/tome/spell_translation.jade
@@ -0,0 +1,3 @@
+pre
+  
+  code
diff --git a/app/views/play/level/tome/Spell.coffee b/app/views/play/level/tome/Spell.coffee
index 9d6ad2ba3..43650c064 100644
--- a/app/views/play/level/tome/Spell.coffee
+++ b/app/views/play/level/tome/Spell.coffee
@@ -48,7 +48,7 @@ module.exports = class Spell
       @source = @originalSource = p.aiSource
     @thangs = {}
     if @canRead()  # We can avoid creating these views if we'll never use them.
-      @view = new SpellView {spell: @, level: options.level, session: @session, otherSession: @otherSession, worker: @worker, god: options.god}
+      @view = new SpellView {spell: @, level: options.level, session: @session, otherSession: @otherSession, worker: @worker, god: options.god, @supermodel}
       @view.render()  # Get it ready and code loaded in advance
       @tabView = new SpellListTabEntryView spell: @, supermodel: @supermodel, codeLanguage: @language, level: options.level
       @tabView.render()
diff --git a/app/views/play/level/tome/SpellTranslationView.coffee b/app/views/play/level/tome/SpellTranslationView.coffee
new file mode 100644
index 000000000..bdb46636a
--- /dev/null
+++ b/app/views/play/level/tome/SpellTranslationView.coffee
@@ -0,0 +1,99 @@
+CocoView = require 'views/core/CocoView'
+LevelComponent = require 'models/LevelComponent'
+template = require 'templates/play/level/tome/spell_translation'
+Range = ace.require('ace/range').Range
+TokenIterator = ace.require('ace/token_iterator').TokenIterator
+serializedClasses =
+  Thang: require 'lib/world/thang'
+  Vector: require 'lib/world/vector'
+  Rectangle: require 'lib/world/rectangle'
+  Ellipse: require 'lib/world/ellipse'
+  LineSegment: require 'lib/world/line_segment'
+utils = require 'core/utils'
+
+module.exports = class SpellTranslationView extends CocoView
+  className: 'spell-translation-view'
+  template: template
+  
+  events:
+    'mousemove': ->
+      @$el.hide()
+
+  constructor: (options) ->
+    super options
+    @ace = options.ace
+    @thang = options.thang
+    @spell = options.spell
+    @supermodel = options.supermodel
+    
+    
+    levelComponents = @supermodel.getModels LevelComponent
+    @componentTranslations = levelComponents.reduce((acc, lc) ->
+      for doc in (lc.get('propertyDocumentation') ? [])
+        translated = utils.i18n(doc, 'name', null, false)
+        acc[doc.name] = translated if translated isnt doc.name
+      acc
+    , {})
+    
+    @onMouseMove = _.throttle @onMouseMove, 25
+    
+  afterRender: ->
+    super()
+    @ace.on 'mousemove', @onMouseMove
+
+  setTooltipText: (text) =>
+    @$el.find('code').text text
+    @$el.show().css(@pos)
+    
+  isIdentifier: (t) ->
+    t and (t.type in ['identifier', 'keyword'] or t.value is 'this')
+    
+  onMouseMove: (e) =>
+    return if @destroyed
+    pos = e.getDocumentPosition()
+    it = new TokenIterator e.editor.session, pos.row, pos.column
+    endOfLine = it.getCurrentToken()?.index is it.$rowTokens.length - 1
+    while it.getCurrentTokenRow() is pos.row and not @isIdentifier(token = it.getCurrentToken())
+      break if endOfLine or not token  # Don't iterate beyond end or beginning of line
+      it.stepBackward()
+    unless @isIdentifier(token)
+      @word = null
+      @update()
+      return
+    try
+      # Ace was breaking under some (?) conditions, dependent on mouse location.
+      #   with $rowTokens = [] (but should have things)
+      start = it.getCurrentTokenColumn()
+    catch error
+      start = 0
+    end = start + token.value.length
+    if @isIdentifier(token)
+      @word = token.value
+      @markerRange = new Range pos.row, start, pos.row, end
+      @reposition(e.domEvent)
+    @update()
+    
+  reposition: (e) ->
+    offsetX = e.offsetX ? e.clientX - $(e.target).offset().left
+    offsetY = e.offsetY ? e.clientY - $(e.target).offset().top
+    w = $(document).width() - 20
+    offsetX = w - $(e.target).offset().left - @$el.width() if e.clientX + @$el.width() > w
+    @pos = {left: offsetX + 80, top: offsetY - 20}
+    @$el.css(@pos)
+    
+  onMouseOut: ->
+    @word = null
+    @markerRange = null
+    @update()
+    
+  update: ->
+    i18nKey = 'code.'+@word
+    translation = @componentTranslations[@word] or $.t(i18nKey)
+    if @word and translation and translation not in [i18nKey, @word]
+      @setTooltipText translation
+    else
+      @$el.hide()
+
+  destroy: ->
+    @ace?.removeEventListener 'mousemove', @onMouseMove
+    super()
diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee
index 7af9134eb..f40b2772f 100644
--- a/app/views/play/level/tome/SpellView.coffee
+++ b/app/views/play/level/tome/SpellView.coffee
@@ -6,6 +6,7 @@ Range = ace.require('ace/range').Range
 UndoManager = ace.require('ace/undomanager').UndoManager
 Problem = require './Problem'
 SpellDebugView = require './SpellDebugView'
+SpellTranslationView = require './SpellTranslationView'
 SpellToolbarView = require './SpellToolbarView'
 LevelComponent = require 'models/LevelComponent'
 UserCodeProblem = require 'models/UserCodeProblem'
@@ -56,6 +57,7 @@ module.exports = class SpellView extends CocoView
 
   constructor: (options) ->
     super options
+    @supermodel = options.supermodel
     @worker = options.worker
     @session = options.session
     @listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@@ -638,6 +640,10 @@ module.exports = class SpellView extends CocoView
     return if @options.level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop', 'course', 'course-ladder']  # We'll turn this on later, maybe, but not yet.
     @debugView = new SpellDebugView ace: @ace, thang: @thang, spell:@spell
     @$el.append @debugView.render().$el.hide()
+    
+  createTranslationView: ->
+    @translationView = new SpellTranslationView { @ace, @thang, @spell, @supermodel }
+    @$el.append @translationView.render().$el.hide()
 
   createToolbarView: ->
     @toolbarView = new SpellToolbarView ace: @ace
@@ -661,6 +667,8 @@ module.exports = class SpellView extends CocoView
     @spellThang = @spell.thangs[@thang.id]
     @createDebugView() unless @debugView
     @debugView?.thang = @thang
+    @createTranslationView() unless @translationView
+    @translationView?.thang = @thang
     @toolbarView?.toggleFlow false
     @updateAether false, false
     # @addZatannaSnippets()
@@ -1336,6 +1344,7 @@ module.exports = class SpellView extends CocoView
     @aceSession?.selection.off 'changeCursor', @onCursorActivity
     @destroyAceEditor(@ace)
     @debugView?.destroy()
+    @translationView?.destroy()
     @toolbarView?.destroy()
     @zatanna.addSnippets [], @editorLang if @editorLang?
     $(window).off 'resize', @onWindowResize