]> BookStack Code Mirror - bookstack/blobdiff - resources/lang/format.php
Update maintenance.php
[bookstack] / resources / lang / format.php
index 8698d1bb7d1639e69362432b335a3f295ff97a4e..45d0b4842005ba4cd80cc7d9ea87a5ee6906e8e6 100755 (executable)
 /**
  * Format a language file in the same way as the EN equivalent.
  * Matches the line numbers of translated content.
+ * Potentially destructive, Ensure you have a backup of your translation content before running.
  */
 
 $args = array_slice($argv, 1);
 
 if (count($args) < 2) {
-    errorOut("Please provide a language code as the first argument and a translation file name as the second (./format.php fr activities)");
+    errorOut("Please provide a language code as the first argument and a translation file name, or '--all', as the second (./format.php fr activities)");
 }
 
-$lang = formatLang($args[0]);
+$lang = formatLocale($args[0]);
 $fileName = explode('.', $args[1])[0];
+$fileNames = [$fileName];
+if ($fileName === '--all') {
+    $fileNames = getTranslationFileNames();
+}
 
-$enLines = loadLangFileLines('en', $fileName);
-$langContent = loadLang($lang, $fileName);
-$enContent = loadLang('en', $fileName);
+foreach ($fileNames as $fileName) {
+    $formatted = formatFileContents($lang, $fileName);
+    writeLangFile($lang, $fileName, $formatted);
+}
 
-// Calculate the longest top-level key length
-$longestKeyLength = longestKey($enContent);
 
-// Start formatted content
-$formatted = [];
-$mode = 'header';
-$arrayKeys = [];
+/**
+ * Format the contents of a single translation file in the given language.
+ * @param string $lang
+ * @param string $fileName
+ * @return string
+ */
+function formatFileContents(string $lang, string $fileName) : string {
+    $enLines = loadLangFileLines('en', $fileName);
+    $langContent = loadLang($lang, $fileName);
+    $enContent = loadLang('en', $fileName);
+
+    // Calculate the longest top-level key length
+    $longestKeyLength = calculateKeyPadding($enContent);
+
+    // Start formatted content
+    $formatted = [];
+    $mode = 'header';
+    $skipArray = false;
+    $arrayKeys = [];
+
+    foreach ($enLines as $index => $line) {
+        $trimLine = trim($line);
+        if ($mode === 'header') {
+            $formatted[$index] = $line;
+            if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body';
+        }
 
-foreach($enLines as $index => $line) {
-    $trimLine = trim($line);
-    if ($mode === 'header') {
-        $formatted[$index] = $line;
-        if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body';
-    }
+        if ($mode === 'body') {
+            $matches = [];
+            $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine);
 
-    if ($mode === 'body') {
-        $matches = [];
+            if ($skipArray) {
+                if ($arrayEndMatch) $skipArray = false;
+                continue;
+            }
 
-        // Comment
-        if (strpos($trimLine, '//') === 0) {
-            $formatted[$index] = "\t" . $trimLine;
-            continue;
-        }
+            // Comment to ignore
+            if (strpos($trimLine, '//!') === 0) {
+                $formatted[$index] = "";
+                continue;
+            }
 
-        // Arrays
-        $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches);
-        $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine);
-        $indent = count($arrayKeys) + 1;
-        if ($arrayStartMatch === 1) {
-            $arrayKeys[] = $matches[1];
-            $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> [";
-            if ($arrayEndMatch !== 1) continue;
-        }
-        if ($arrayEndMatch === 1) {
-            unsetArrayByKeys($langContent, $arrayKeys);
-            $key = array_pop($arrayKeys);
-            if (isset($formatted[$index])) {
-                $formatted[$index] .= '],';
-            } else {
-                $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],";
+            // Comment
+            if (strpos($trimLine, '//') === 0) {
+                $formatted[$index] = "\t" . $trimLine;
+                continue;
             }
-            continue;
-        }
 
-        // Translation
-        $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\'(.*)?\'.+?$/', $trimLine, $matches);
-        if ($translationMatch === 1) {
-            $key = $matches[1];
-            $keys = array_merge($arrayKeys, [$key]);
-            $langVal = getTranslationByKeys($langContent, $keys);
-            if (empty($langVal)) continue;
-
-            $keyPad = $longestKeyLength;
-            if (count($arrayKeys) === 0) {
-                unset($langContent[$key]);
-            } else {
-                $keyPad = longestKey(getTranslationByKeys($enContent, $arrayKeys));
+            // Arrays
+            $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches);
+
+            $indent = count($arrayKeys) + 1;
+            if ($arrayStartMatch === 1) {
+                if ($fileName === 'settings' && $matches[1] === 'language_select') {
+                    $skipArray = true;
+                    continue;
+                }
+                $arrayKeys[] = $matches[1];
+                $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> [";
+                if ($arrayEndMatch !== 1) continue;
+            }
+            if ($arrayEndMatch === 1) {
+                unsetArrayByKeys($langContent, $arrayKeys);
+                array_pop($arrayKeys);
+                if (isset($formatted[$index])) {
+                    $formatted[$index] .= '],';
+                } else {
+                    $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],";
+                }
+                continue;
             }
 
-            $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad);
-            continue;
+            // Translation
+            $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?[\'"](.*)?[\'"].+?$/', $trimLine, $matches);
+            if ($translationMatch === 1) {
+                $key = $matches[1];
+                $keys = array_merge($arrayKeys, [$key]);
+                $langVal = getTranslationByKeys($langContent, $keys);
+                if (empty($langVal)) continue;
+
+                $keyPad = $longestKeyLength;
+                if (count($arrayKeys) === 0) {
+                    unset($langContent[$key]);
+                } else {
+                    $keyPad = calculateKeyPadding(getTranslationByKeys($enContent, $arrayKeys));
+                }
+
+                $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad);
+                continue;
+            }
         }
-    }
 
-}
+    }
 
-// Fill missing lines
-$arraySize = max(array_keys($formatted));
-$formatted = array_replace(array_fill(0, $arraySize, ''), $formatted);
+    // Fill missing lines
+    $arraySize = max(array_keys($formatted));
+    $formatted = array_replace(array_fill(0, $arraySize, ''), $formatted);
+
+    // Add remaining translations
+    $langContent = array_filter($langContent, function($item) {
+        return !is_null($item) && !empty($item);
+    });
+    if (count($langContent) > 0) {
+        $formatted[] = '';
+        $formatted[] = "\t// Unmatched";
+    }
+    foreach ($langContent as $key => $value) {
+        if (is_array($value)) {
+            $formatted[] = formatTranslationArray($key, $value);
+        } else {
+            $formatted[] = formatTranslationLine($key, $value);
+        }
+    }
 
-// Add remaining translations
-$langContent = array_filter($langContent, function($item) {
-    return !is_null($item) && !empty($item);
-});
-if (count($langContent) > 0) {
-    $formatted[] = '';
-    $formatted[] = "\t// Unmatched";
+    // Add end line
+    $formatted[] = '];';
+    return implode("\n", $formatted);
 }
-foreach ($langContent as $key => $value) {
-    if (is_array($value)) {
-        $formatted[] = formatTranslationArray($key, $value);
+
+/**
+ * Format a translation line.
+ * @param string $key
+ * @param string $value
+ * @param int $indent
+ * @param int $keyPad
+ * @return string
+ */
+function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) : string {
+    $start = str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ');
+    if (strpos($value, "\n") !== false) {
+        $escapedValue = '"' .  str_replace("\n", '\n', $value)  . '"';
+        $escapedValue = '"' .  str_replace('"', '\"', $escapedValue)  . '"';
     } else {
-        $formatted[] = formatTranslationLine($key, $value);
+        $escapedValue = "'" . str_replace("'", "\\'", $value) . "'";
     }
+    return "{$start} => {$escapedValue},";
 }
 
-// Add end line
-$formatted[] = '];';
-$formatted = implode("\n", $formatted);
-
-writeLangFile($lang, $fileName, $formatted);
-
-function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) {
-    $escapedValue = str_replace("'", "\\'", $value);
-    return str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ') ."=> '{$escapedValue}',";
-}
-
-function longestKey(array $array) {
+/**
+ * Find the longest key in the array and provide the length
+ * for all keys to be used when printed.
+ * @param array $array
+ * @return int
+ */
+function calculateKeyPadding(array $array) : int {
     $top = 0;
     foreach ($array as $key => $value) {
         $keyLen = strlen($key);
         $top = max($top, $keyLen);
     }
-    return $top + 3;
+    return min(35, $top + 2);
 }
 
-function formatTranslationArray(string $key, array $array) {
+/**
+ * Format an translation array with the given key.
+ * Simply prints as an old-school php array.
+ * Used as a last-resort backup to save unused translations.
+ * @param string $key
+ * @param array $array
+ * @return string
+ */
+function formatTranslationArray(string $key, array $array) : string {
     $arrayPHP = var_export($array, true);
     return "    '{$key}' => {$arrayPHP},";
 }
 
-function getTranslationByKeys(array $translations, array $keys) {
+/**
+ * Find a string translation value within a multi-dimensional array
+ * by traversing the given array of keys.
+ * @param array $translations
+ * @param array $keys
+ * @return string|array
+ */
+function getTranslationByKeys(array $translations, array $keys)  {
     $val = $translations;
     foreach ($keys as $key) {
         $val = $val[$key] ?? '';
@@ -139,6 +208,12 @@ function getTranslationByKeys(array $translations, array $keys) {
     return $val;
 }
 
+/**
+ * Unset an inner item of a multi-dimensional array by
+ * traversing the given array of keys.
+ * @param array $input
+ * @param array $keys
+ */
 function unsetArrayByKeys(array &$input, array $keys) {
     $val = &$input;
     $lastIndex = count($keys) - 1;
@@ -151,6 +226,12 @@ function unsetArrayByKeys(array &$input, array $keys) {
     }
 }
 
+/**
+ * Write the given content to a translation file.
+ * @param string $lang
+ * @param string $fileName
+ * @param string $content
+ */
 function writeLangFile(string $lang, string $fileName, string $content) {
     $path = __DIR__ . "/{$lang}/{$fileName}.php";
     if (!file_exists($path)) {
@@ -159,7 +240,13 @@ function writeLangFile(string $lang, string $fileName, string $content) {
     file_put_contents($path, $content);
 }
 
-function loadLangFileLines(string $lang, string $fileName) {
+/**
+ * Load the contents of a language file as an array of text lines.
+ * @param string $lang
+ * @param string $fileName
+ * @return array
+ */
+function loadLangFileLines(string $lang, string $fileName) : array {
     $path = __DIR__ . "/{$lang}/{$fileName}.php";
     if (!file_exists($path)) {
         errorOut("Expected translation file '{$path}' does not exist");
@@ -170,7 +257,13 @@ function loadLangFileLines(string $lang, string $fileName) {
     }, $lines);
 }
 
-function loadLang(string $lang, string $fileName) {
+/**
+ * Load the contents of a language file
+ * @param string $lang
+ * @param string $fileName
+ * @return array
+ */
+function loadLang(string $lang, string $fileName) : array {
     $path = __DIR__ . "/{$lang}/{$fileName}.php";
     if (!file_exists($path)) {
         errorOut("Expected translation file '{$path}' does not exist");
@@ -180,21 +273,57 @@ function loadLang(string $lang, string $fileName) {
     return $fileData;
 }
 
-function formatLang($lang) {
+/**
+ * Fetch an array containing the names of all translation files without the extension.
+ * @return array
+ */
+function getTranslationFileNames() : array {
+    $dir = __DIR__ . "/en";
+    if (!file_exists($dir)) {
+        errorOut("Expected directory '{$dir}' does not exist");
+    }
+    $files = scandir($dir);
+    $fileNames = [];
+    foreach ($files as $file) {
+        if (substr($file, -4) === '.php') {
+            $fileNames[] = substr($file, 0, strlen($file) - 4);
+        }
+    }
+    return $fileNames;
+}
+
+/**
+ * Format a locale to follow the lowercase_UPERCASE standard
+ * @param string $lang
+ * @return string
+ */
+function formatLocale(string $lang) : string {
     $langParts = explode('_', strtoupper($lang));
     $langParts[0] = strtolower($langParts[0]);
     return implode('_', $langParts);
 }
 
+/**
+ * Dump a variable then die.
+ * @param $content
+ */
 function dd($content) {
     print_r($content);
     exit(1);
 }
 
+/**
+ * Log out some information text in blue
+ * @param $text
+ */
 function info($text) {
     echo "\e[34m" . $text . "\e[0m\n";
 }
 
+/**
+ * Log out an error in red and exit.
+ * @param $text
+ */
 function errorOut($text) {
     echo "\e[31m" . $text . "\e[0m\n";
     exit(1);