diff --git a/eeschema/cmp_tree_model.cpp b/eeschema/cmp_tree_model.cpp
index 605e44b823..a8308fbd43 100644
--- a/eeschema/cmp_tree_model.cpp
+++ b/eeschema/cmp_tree_model.cpp
@@ -25,6 +25,7 @@
 #include <eda_pattern_match.h>
 #include <make_unique.h>
 #include <utility>
+#include <pgm_base.h>
 
 
 // Each node gets this lowest score initially, without any matches applied.
@@ -114,13 +115,24 @@ CMP_TREE_NODE::CMP_TREE_NODE()
 
 CMP_TREE_NODE_UNIT::CMP_TREE_NODE_UNIT( CMP_TREE_NODE* aParent, int aUnit )
 {
+    static void* locale = nullptr;
+    static wxString namePrefix;
+
+    // Fetching translations can take a surprising amount of time when loading libraries,
+    // so only do it when necessary.
+    if( Pgm().GetLocale() != locale )
+    {
+        namePrefix = _( "Unit" );
+        locale = Pgm().GetLocale();
+    }
+
     Parent = aParent;
     Type = UNIT;
 
     Unit = aUnit;
     LibId = aParent->LibId;
 
-    Name = _( "Unit" ) + " " + LIB_PART::SubReference( aUnit, false );
+    Name = namePrefix + " " + LIB_PART::SubReference( aUnit, false );
     Desc = wxEmptyString;
     MatchName = wxEmptyString;
 
@@ -158,7 +170,7 @@ void CMP_TREE_NODE_LIB_ID::Update( LIB_ALIAS* aAlias )
     // Search text spaces out keywords and description to penalize description
     // matches - earlier matches are worth more.
     MatchName   = aAlias->GetName().Lower();
-    SearchText  = (aAlias->GetKeyWords() + "        " + Desc).Lower();
+    SearchText  = (aAlias->GetKeyWords() + "        " + Desc);
 
     // Extract default footprint text
     LIB_PART* part = aAlias->GetPart();
@@ -177,7 +189,7 @@ void CMP_TREE_NODE_LIB_ID::Update( LIB_ALIAS* aAlias )
     if( !footprint.IsEmpty() )
     {
         SearchText += "        ";
-        SearchText += footprint.Lower();
+        SearchText += footprint;
     }
 
     Children.clear();
@@ -195,6 +207,12 @@ void CMP_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
     if( Score <= 0 )
         return; // Leaf nodes without scores are out of the game.
 
+    if( !SearchTextNormalized )
+    {
+        SearchText = SearchText.Lower();
+        SearchTextNormalized = true;
+    }
+
     // Keywords and description we only count if the match string is at
     // least two characters long. That avoids spurious, low quality
     // matches. Most abbreviations are at three characters long.
diff --git a/eeschema/cmp_tree_model.h b/eeschema/cmp_tree_model.h
index e9e04cb7e5..30788e13d0 100644
--- a/eeschema/cmp_tree_model.h
+++ b/eeschema/cmp_tree_model.h
@@ -100,6 +100,8 @@ public:
     wxString    Desc;        ///< Description to be displayed
     wxString    MatchName;   ///< Normalized name for matching
     wxString    SearchText;  ///< Descriptive text to search
+    bool        SearchTextNormalized;  ///< Support for lazy normalization.
+
 
     LIB_ID      LibId;       ///< LIB_ID determined by the parent library nickname and alias name.
     int         Unit;        ///< Actual unit, or zero
diff --git a/eeschema/lib_arc.cpp b/eeschema/lib_arc.cpp
index 0b5531e6db..88d8edf97a 100644
--- a/eeschema/lib_arc.cpp
+++ b/eeschema/lib_arc.cpp
@@ -92,7 +92,6 @@ LIB_ARC::LIB_ARC( LIB_PART*      aParent ) : LIB_ITEM( LIB_ARC_T, aParent )
     m_Width         = 0;
     m_Fill          = NO_FILL;
     m_isFillable    = true;
-    m_typeName      = _( "Arc" );
     m_editState     = 0;
     m_lastEditState = 0;
     m_editCenterDistance = 0.0;
diff --git a/eeschema/lib_arc.h b/eeschema/lib_arc.h
index 9bd050b98b..886d464d15 100644
--- a/eeschema/lib_arc.h
+++ b/eeschema/lib_arc.h
@@ -91,6 +91,11 @@ public:
         return wxT( "LIB_ARC" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "Arc" );
+    }
+
     bool HitTest( const wxPoint& aPosition ) const override;
 
     bool HitTest( const wxPoint& aPosition, int aThreshold, const TRANSFORM& aTransform ) const override;
diff --git a/eeschema/lib_bezier.cpp b/eeschema/lib_bezier.cpp
index 2039049e76..916b610483 100644
--- a/eeschema/lib_bezier.cpp
+++ b/eeschema/lib_bezier.cpp
@@ -47,7 +47,6 @@ LIB_BEZIER::LIB_BEZIER( LIB_PART* aParent ) :
     m_Fill       = NO_FILL;
     m_Width      = 0;
     m_isFillable = true;
-    m_typeName   = _( "Bezier" );
 }
 
 
diff --git a/eeschema/lib_bezier.h b/eeschema/lib_bezier.h
index 7e649d59de..919a7f37f1 100644
--- a/eeschema/lib_bezier.h
+++ b/eeschema/lib_bezier.h
@@ -58,6 +58,10 @@ public:
         return wxT( "LIB_BEZIER" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "Bezier" );
+    }
 
     void AddPoint( const wxPoint& aPoint ) { m_BezierPoints.push_back( aPoint ); }
 
diff --git a/eeschema/lib_circle.cpp b/eeschema/lib_circle.cpp
index 2ba1570083..e2f1bd31eb 100644
--- a/eeschema/lib_circle.cpp
+++ b/eeschema/lib_circle.cpp
@@ -50,7 +50,6 @@ LIB_CIRCLE::LIB_CIRCLE( LIB_PART*      aParent ) :
     m_Width      = 0;
     m_Fill       = NO_FILL;
     m_isFillable = true;
-    m_typeName   = _( "Circle" );
 }
 
 
diff --git a/eeschema/lib_circle.h b/eeschema/lib_circle.h
index 41c02794ed..1a2740285d 100644
--- a/eeschema/lib_circle.h
+++ b/eeschema/lib_circle.h
@@ -56,6 +56,10 @@ public:
         return wxT( "LIB_CIRCLE" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "Circle" );
+    }
 
     bool HitTest( const wxPoint& aPosition ) const override;
 
diff --git a/eeschema/lib_draw_item.cpp b/eeschema/lib_draw_item.cpp
index c9e16b0e06..efd9986af4 100644
--- a/eeschema/lib_draw_item.cpp
+++ b/eeschema/lib_draw_item.cpp
@@ -52,7 +52,6 @@ LIB_ITEM::LIB_ITEM( KICAD_T        aType,
     m_Convert           = aConvert;
     m_Fill              = aFillType;
     m_Parent            = (EDA_ITEM*) aComponent;
-    m_typeName          = _( "Undefined" );
     m_isFillable        = false;
     m_eraseLastDrawItem = false;
 }
@@ -62,7 +61,7 @@ void LIB_ITEM::GetMsgPanelInfo( MSG_PANEL_ITEMS& aList )
 {
     wxString msg;
 
-    aList.push_back( MSG_PANEL_ITEM( _( "Type" ), m_typeName, CYAN ) );
+    aList.push_back( MSG_PANEL_ITEM( _( "Type" ), GetTypeName(), CYAN ) );
 
     if( m_Unit == 0 )
         msg = _( "All" );
diff --git a/eeschema/lib_draw_item.h b/eeschema/lib_draw_item.h
index 650c4f8a50..28549177ba 100644
--- a/eeschema/lib_draw_item.h
+++ b/eeschema/lib_draw_item.h
@@ -128,8 +128,6 @@ protected:
      */
     FILL_T   m_Fill;
 
-    wxString m_typeName;          ///< Name of object displayed in the message panel.
-
     wxPoint  m_initialPos;        ///< Temporary position when moving an existing item.
     wxPoint  m_initialCursorPos;  ///< Initial cursor position at the beginning of a move.
 
@@ -148,7 +146,11 @@ public:
 
     virtual ~LIB_ITEM() { }
 
-    wxString GetTypeName() { return m_typeName; }
+    /**
+     * Provide a user-consumable name of the object type.  Perform localization when
+     * called so that run-time language selection works.
+     */
+    virtual wxString GetTypeName() = 0;
 
     /**
      * Begin an editing a component library draw item in \a aEditMode at \a aPosition.
diff --git a/eeschema/lib_field.cpp b/eeschema/lib_field.cpp
index d8fef90fb2..1260fbb7f3 100644
--- a/eeschema/lib_field.cpp
+++ b/eeschema/lib_field.cpp
@@ -84,8 +84,6 @@ void LIB_FIELD::Init( int id )
     SetTextWidth( GetDefaultTextSize() );
     SetTextHeight( GetDefaultTextSize() );
 
-    m_typeName = _( "Field" );
-
     SetTextAngle( TEXT_ANGLE_HORIZ );    // constructor already did this.
 
     m_rotate = false;
diff --git a/eeschema/lib_field.h b/eeschema/lib_field.h
index e64af1e067..f622729ed1 100644
--- a/eeschema/lib_field.h
+++ b/eeschema/lib_field.h
@@ -100,6 +100,11 @@ public:
         return wxT( "LIB_FIELD" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "Field" );
+    }
+
     /**
      * Object constructor initialization helper.
      */
diff --git a/eeschema/lib_manager.cpp b/eeschema/lib_manager.cpp
index e9ec9a6c75..fc82ed2f42 100644
--- a/eeschema/lib_manager.cpp
+++ b/eeschema/lib_manager.cpp
@@ -288,14 +288,15 @@ std::list<LIB_ALIAS*> LIB_MANAGER::GetAliases( const wxString& aLibrary ) const
     }
     else
     {
-        wxArrayString symbols;
+        std::vector<LIB_ALIAS*> aliases;
 
-        try {
-            symTable()->EnumerateSymbolLib( aLibrary, symbols );
+        try
+        {
+            symTable()->LoadSymbolLib( aliases, aLibrary );
+        }
+        catch( IO_ERROR& ) {}
 
-            for( const auto& symbol : symbols )
-                ret.push_back( symTable()->LoadSymbol( aLibrary, symbol ) );
-        } catch( IO_ERROR& e ) {}
+        std::copy( aliases.begin(), aliases.end(), std::back_inserter( ret ) );
     }
 
     return ret;
diff --git a/eeschema/lib_manager_adapter.cpp b/eeschema/lib_manager_adapter.cpp
index 2a01db656f..0f753bb6cc 100644
--- a/eeschema/lib_manager_adapter.cpp
+++ b/eeschema/lib_manager_adapter.cpp
@@ -54,8 +54,12 @@ bool LIB_MANAGER_ADAPTER::IsContainer( const wxDataViewItem& aItem ) const
 }
 
 
+#define PROGRESS_INTERVAL_MILLIS 50
+
 void LIB_MANAGER_ADAPTER::Sync( bool aForce, std::function<void(int, int, const wxString&)> aProgressCallback )
 {
+    wxLongLong nextUpdate = wxGetUTCTimeMillis() + PROGRESS_INTERVAL_MILLIS;
+
     int libMgrHash = m_libMgr->GetHash();
 
     if( !aForce && m_lastSyncHash == libMgrHash )
@@ -68,7 +72,12 @@ void LIB_MANAGER_ADAPTER::Sync( bool aForce, std::function<void(int, int, const
     for( auto it = m_tree.Children.begin(); it != m_tree.Children.end(); /* iteration inside */ )
     {
         const wxString& name = it->get()->Name;
-        aProgressCallback( i++, max, name );
+
+        if( wxGetUTCTimeMillis() > nextUpdate )
+        {
+            aProgressCallback( i++, max, name );
+            nextUpdate = wxGetUTCTimeMillis() + PROGRESS_INTERVAL_MILLIS;
+        }
 
         if( !m_libMgr->LibraryExists( name ) )
         {
@@ -88,7 +97,12 @@ void LIB_MANAGER_ADAPTER::Sync( bool aForce, std::function<void(int, int, const
     {
         if( m_libHashes.count( libName ) == 0 )
         {
-            aProgressCallback( i++, max, libName );
+            if( wxGetUTCTimeMillis() > nextUpdate )
+            {
+                aProgressCallback( i++, max, libName );
+                nextUpdate = wxGetUTCTimeMillis() + PROGRESS_INTERVAL_MILLIS;
+            }
+
             auto& lib_node = m_tree.AddLib( libName );
             updateLibrary( lib_node );
             m_tree.AssignIntrinsicRanks();
diff --git a/eeschema/lib_pin.cpp b/eeschema/lib_pin.cpp
index 8af8d385d6..52ef34c826 100644
--- a/eeschema/lib_pin.cpp
+++ b/eeschema/lib_pin.cpp
@@ -154,7 +154,6 @@ LIB_PIN::LIB_PIN( LIB_PART*      aParent ) :
     m_numTextSize = LIB_EDIT_FRAME::GetPinNumDefaultSize();
     m_nameTextSize = LIB_EDIT_FRAME::GetPinNameDefaultSize();
     m_width = 0;
-    m_typeName = _( "Pin" );
 }
 
 
diff --git a/eeschema/lib_pin.h b/eeschema/lib_pin.h
index d3dfda010a..79605da585 100644
--- a/eeschema/lib_pin.h
+++ b/eeschema/lib_pin.h
@@ -110,6 +110,11 @@ public:
         return wxT( "LIB_PIN" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "Pin" );
+    }
+
 #if defined(DEBUG)
     void Show( int nestLevel, std::ostream& os ) const override;
 #endif
diff --git a/eeschema/lib_polyline.cpp b/eeschema/lib_polyline.cpp
index 28b61daa18..761688a54d 100644
--- a/eeschema/lib_polyline.cpp
+++ b/eeschema/lib_polyline.cpp
@@ -48,7 +48,6 @@ LIB_POLYLINE::LIB_POLYLINE( LIB_PART*      aParent ) :
     m_Fill  = NO_FILL;
     m_Width = 0;
     m_isFillable = true;
-    m_typeName   = _( "PolyLine" );
     m_ModifyIndex = 0;
 }
 
diff --git a/eeschema/lib_polyline.h b/eeschema/lib_polyline.h
index f7ae53950d..2a6bba10be 100644
--- a/eeschema/lib_polyline.h
+++ b/eeschema/lib_polyline.h
@@ -57,6 +57,10 @@ public:
         return wxT( "LIB_POLYLINE" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "PolyLine" );
+    }
 
     void AddPoint( const wxPoint& aPoint );
 
diff --git a/eeschema/lib_rectangle.cpp b/eeschema/lib_rectangle.cpp
index 1be9cfa561..e1a92fbd29 100644
--- a/eeschema/lib_rectangle.cpp
+++ b/eeschema/lib_rectangle.cpp
@@ -48,7 +48,6 @@ LIB_RECTANGLE::LIB_RECTANGLE( LIB_PART*      aParent ) :
     m_Width                = 0;
     m_Fill                 = NO_FILL;
     m_isFillable           = true;
-    m_typeName             = _( "Rectangle" );
     m_isHeightLocked       = false;
     m_isWidthLocked        = false;
     m_isStartPointSelected = false;
diff --git a/eeschema/lib_rectangle.h b/eeschema/lib_rectangle.h
index 3909c41027..5c85b7931b 100644
--- a/eeschema/lib_rectangle.h
+++ b/eeschema/lib_rectangle.h
@@ -59,6 +59,11 @@ public:
         return wxT( "LIB_RECTANGLE" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "Rectangle" );
+    }
+
     void SetEndPosition( const wxPoint& aPosition ) { m_End = aPosition; }
 
     bool HitTest( const wxPoint& aPosition ) const override;
diff --git a/eeschema/lib_text.cpp b/eeschema/lib_text.cpp
index 671f21e188..ba29349124 100644
--- a/eeschema/lib_text.cpp
+++ b/eeschema/lib_text.cpp
@@ -48,7 +48,6 @@ LIB_TEXT::LIB_TEXT( LIB_PART * aParent ) :
     EDA_TEXT()
 {
     SetTextSize( wxSize( 50, 50 ) );
-    m_typeName   = _( "Text" );
     m_rotate     = false;
     m_updateText = false;
 }
diff --git a/eeschema/lib_text.h b/eeschema/lib_text.h
index 42096ab041..bad432168d 100644
--- a/eeschema/lib_text.h
+++ b/eeschema/lib_text.h
@@ -65,6 +65,11 @@ public:
         return wxT( "LIB_TEXT" );
     }
 
+    wxString GetTypeName() override
+    {
+        return _( "Text" );
+    }
+
     /**
      * Sets the text item string to \a aText.
      *
diff --git a/eeschema/template_fieldnames.cpp b/eeschema/template_fieldnames.cpp
index 01fbaec352..1505fd7100 100644
--- a/eeschema/template_fieldnames.cpp
+++ b/eeschema/template_fieldnames.cpp
@@ -26,33 +26,54 @@
 #include <dsnlexer.h>
 #include <fctsys.h>
 #include <macros.h>
+#include <pgm_base.h>
 
 using namespace TFIELD_T;
 
+
 const wxString TEMPLATE_FIELDNAME::GetDefaultFieldName( int aFieldNdx )
 {
+    static void* locale = nullptr;
+    static wxString referenceDefault;
+    static wxString valueDefault;
+    static wxString footprintDefault;
+    static wxString datasheetDefault;
+    static wxString fieldDefault;
+
+    // Fetching translations can take a surprising amount of time when loading libraries,
+    // so only do it when necessary.
+    if( Pgm().GetLocale() != locale )
+    {
+        referenceDefault = _( "Reference" );
+        valueDefault     = _( "Value" );
+        footprintDefault = _( "Footprint" );
+        datasheetDefault = _( "Datasheet" );
+        fieldDefault     = _( "Field" );
+        locale = Pgm().GetLocale();
+    }
+
     // Fixed values for the first few default fields used by EESCHEMA
     // (mandatory fields)
     switch( aFieldNdx )
     {
     case  REFERENCE:
-        return _( "Reference" );   // The component reference, R1, C1, etc.
+        return referenceDefault;   // The component reference, R1, C1, etc.
 
     case  VALUE:
-        return _( "Value" );       // The component value + name
+        return valueDefault;       // The component value + name
 
     case  FOOTPRINT:
-        return _( "Footprint" );   // The footprint for use with Pcbnew
+        return footprintDefault;   // The footprint for use with Pcbnew
 
     case  DATASHEET:
-        return _( "Datasheet" );   // Link to a datasheet for component
+        return datasheetDefault;   // Link to a datasheet for component
 
     default:
         break;
     }
 
     // Other fields are use fields, give a default name:
-    wxString fieldName = _( "Field" );
+    wxString fieldName = fieldDefault;
     fieldName << aFieldNdx;
     return fieldName;
 }