diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php b/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php
index 196f4ccaacf10504f668d1d019e7f4d9ddb7611f..06a75ee70d60cbbd521ab53df18b7bd57cd43f5c 100644
--- a/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php
+++ b/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php
@@ -27,6 +27,7 @@
  */
 namespace VuFindConsole\Controller;
 use File_MARC, File_MARCXML, VuFind\Sitemap, Zend\Console\Console;
+use VuFind\Log\Logger;
 use VuFindSearch\Backend\Solr\Document\UpdateDocument;
 use VuFindSearch\Backend\Solr\Record\SerializableRecord;
 
@@ -433,6 +434,152 @@ class UtilController extends AbstractBase
         return $this->getSuccessResponse();
     }
 
+    /**
+     * Tool to synchronize language files
+     *
+     * @return \Zend\Console\Response
+     */
+    public function synclangfilesAction()
+    {
+        $this->checkLocalSetting();
+
+        $this->consoleOpts->addRules(
+            [
+                'h|help' => 'Get help',
+                'enable-comments' => 'Write output as ini comments',
+                'enable-fallback' => 'Enable fallback languages',
+                'ignore-local-dir' => 'ignore VUFIND_LOCAL_DIR',
+                'write-keys' => 'Write keys of language files as translation',
+                'write-to-ini' => 'Write missing lines directly to .ini files',
+            ]
+        );
+
+        if ($this->consoleOpts->getOption('h')
+            || $this->consoleOpts->getOption('help')
+        ) {
+            Console::writeLine('Synchronize language files.');
+            Console::writeLine('');
+            Console::writeLine(
+                'Options:'
+            );
+            Console::writeLine(
+                '--help             this output.'
+            );
+            Console::writeLine(
+                '--enable-comments  write output as ini comments (disabled by default).'
+            );
+            Console::writeLine(
+                '--enable-fallback  fallback languages are enabled (by default, fallback logic is overriden).'
+            );
+            Console::writeLine(
+                '--ignore-local-dir ignore VUFIND_LOCAL_DIR if set and only sync language files in VUFIND_HOME/languages.'
+            );
+            Console::writeLine(
+                '--write-keys       write language file keys as translation (write translation by default).'
+            );
+            Console::writeLine(
+                '--write-to-ini     write missing lines directly into .ini files (writes to stdout by default).'
+            );
+            return $this->getFailureResponse();
+        }
+
+        $config = $this->getServiceLocator()->get('VuFind\Config')->get('config');
+
+        $pathStack = [
+            APPLICATION_PATH  . '/languages'
+        ];
+        if (!$this->consoleOpts->getOption('ignore-local-dir') && LOCAL_OVERRIDE_DIR != '')
+            $pathStack[] = LOCAL_OVERRIDE_DIR . '/languages';
+
+        $fallbackLocales = $config->Site->language == 'en'
+            ? 'en'
+            : [$config->Site->language, 'en'];
+
+        $languageFile = new \VuFind\I18n\Translator\Loader\ExtendedIni(
+            $pathStack,
+            ($this->consoleOpts->getOption('enable-fallback')
+                ? $fallbackLocales : [])
+        );
+
+        $languages = $config->Languages->toArray();
+
+        $diffArray = [];
+
+        foreach ($languages as $lang => $value) {
+            if (is_array($fallbackLocales)) {
+                foreach ($fallbackLocales as $fallback) {
+                    if ($fallback != $lang) {
+                        $dataLang = $languageFile->load($lang, null);
+                        $dataFallback = $languageFile->load($fallback, null);
+                        $diffArray[$fallback][$lang] = array_diff_key($dataFallback->getArrayCopy(), $dataLang->getArrayCopy());
+                    }
+                }
+            } else {
+                if ($fallbackLocales != $lang) {
+                    $dataLang = $languageFile->load($lang, null);
+                    $dataFallback = $languageFile->load($fallbackLocales, null);
+                    $diffArray[$fallbackLocales][$lang] = array_diff_key($dataFallback->getArrayCopy(), $dataLang->getArrayCopy());
+                }
+            }
+        }
+
+        if (!empty ($diffArray)) {
+            foreach ($diffArray as $referenceLang => $targetLang) {
+                foreach ($targetLang as $lang => $messages) {
+                    if (count($messages)) {
+                        $counter = 0;
+
+                        if ($this->consoleOpts->getOption('write-to-ini')) {
+                            if ($this->consoleOpts->getOption('ignore-local-dir') &&
+                                file_exists($pathStack[0] . "/" . $lang . ".ini")) {
+                                $writerPath = "file://" . $pathStack[0] . "/" . $lang . ".ini";
+                            } elseif (!$this->consoleOpts->getOption('ignore-local-dir') &&
+                                file_exists($pathStack[1] . "/" . $lang . ".ini")) {
+                                $writerPath = "file://" . $pathStack[1] . "/" . $lang . ".ini";
+                            } else {
+                                $writerPath = "php://stdout";
+                            }
+                        } else {
+                            $writerPath = "php://stdout";
+                        }
+                        $writer = new \Zend\Log\Writer\Stream($writerPath);
+                        $writer->setFormatter(
+                            new \Zend\Log\Formatter\Simple('%message%')
+                        );
+
+                        $logger = new Logger();
+                        $logger->addWriter($writer);
+                        $logger->info(";------");
+                        $logger->info(";missing lines from " . $referenceLang . ".ini (compiled from files in " . implode(', ', $pathStack) . ")"
+                            . ($this->consoleOpts->getOption('write-to-ini') ? '' : ' in ' . $lang . '.ini'));
+                        $logger->info(";------");
+
+                        foreach($messages as $key => $message) {
+                            if ($key != "@parent_ini") {
+                                $counter++;
+                                if ($this->consoleOpts->getOption('write-keys')) {
+                                    $logger->info(
+                                        ($this->consoleOpts->getOption('enable-comments') ? ";" : "") .
+                                        $key. " = " . $key);
+                                } else {
+                                    $logger->info(
+                                        ($this->consoleOpts->getOption('enable-comments') ? ";" : "") .
+                                        $key. " = " . $message);
+                                }
+                            }
+                        }
+
+                        if ($this->consoleOpts->getOption('write-to-ini')) {
+                            Console::writeLine('Added ' . $counter . ' lines from ' . $referenceLang . '.ini (compiled from files in ' . implode(', ', $pathStack) . ') to ' . $writerPath);
+                        }
+                        unset($logger);
+                        unset($writer);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Tool to auto-fill hierarchy cache.
      *
diff --git a/util/synclangfiles.php b/util/synclangfiles.php
new file mode 100644
index 0000000000000000000000000000000000000000..01486fe67da8f6fc87fd9b292bde1331a343116f
--- /dev/null
+++ b/util/synclangfiles.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Create all the hierarchy files which are used for looking up hierarchichal trees.
+ * This script will search the Solr index and create the files needed so they don't
+ * need to be built at runtime. If this script is run after every index, the caching
+ * time for hierarchy trees can be set to -1 so that trees are always assumed to be
+ * up to date.
+ *
+ * -!!!!-This script is specifically for trees built for JSTree from Solr.-!!!!-
+ *
+ * PHP version 5
+ *
+ * Copyright (C) National Library of Ireland 2012.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind2
+ * @package  Utilities
+ * @author   Lutz Biedinger <lutz.biedinger@gmail.com>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki Wiki
+ */
+
+// Load the Zend framework -- this will automatically trigger the appropriate
+// controller action based on directory and file names
+define('CLI_DIR', __DIR__);     // save directory name of current script
+require_once __DIR__ . '/../public/index.php';