5 * Format a language file in the same way as the EN equivalent.
6 * Matches the line numbers of translated content.
7 * Potentially destructive, Ensure you have a backup of your translation content before running.
10 $args = array_slice($argv, 1);
12 if (count($args) < 2) {
13 errorOut("Please provide a language code as the first argument and a translation file name, or '--all', as the second (./format.php fr activities)");
16 $lang = formatLocale($args[0]);
17 $fileName = explode('.', $args[1])[0];
18 $fileNames = [$fileName];
19 if ($fileName === '--all') {
20 $fileNames = getTranslationFileNames();
23 foreach ($fileNames as $fileName) {
24 $formatted = formatFileContents($lang, $fileName);
25 writeLangFile($lang, $fileName, $formatted);
30 * Format the contents of a single translation file in the given language.
32 * @param string $fileName
35 function formatFileContents(string $lang, string $fileName) : string {
36 $enLines = loadLangFileLines('en', $fileName);
37 $langContent = loadLang($lang, $fileName);
38 $enContent = loadLang('en', $fileName);
40 // Calculate the longest top-level key length
41 $longestKeyLength = calculateKeyPadding($enContent);
43 // Start formatted content
49 foreach ($enLines as $index => $line) {
50 $trimLine = trim($line);
51 if ($mode === 'header') {
52 $formatted[$index] = $line;
53 if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body';
56 if ($mode === 'body') {
58 $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine);
61 if ($arrayEndMatch) $skipArray = false;
66 if (strpos($trimLine, '//!') === 0) {
67 $formatted[$index] = "";
72 if (strpos($trimLine, '//') === 0) {
73 $formatted[$index] = "\t" . $trimLine;
78 $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches);
80 $indent = count($arrayKeys) + 1;
81 if ($arrayStartMatch === 1) {
82 if ($fileName === 'settings' && $matches[1] === 'language_select') {
86 $arrayKeys[] = $matches[1];
87 $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> [";
88 if ($arrayEndMatch !== 1) continue;
90 if ($arrayEndMatch === 1) {
91 unsetArrayByKeys($langContent, $arrayKeys);
92 array_pop($arrayKeys);
93 if (isset($formatted[$index])) {
94 $formatted[$index] .= '],';
96 $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],";
102 $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?[\'"](.*)?[\'"].+?$/', $trimLine, $matches);
103 if ($translationMatch === 1) {
105 $keys = array_merge($arrayKeys, [$key]);
106 $langVal = getTranslationByKeys($langContent, $keys);
107 if (empty($langVal)) continue;
109 $keyPad = $longestKeyLength;
110 if (count($arrayKeys) === 0) {
111 unset($langContent[$key]);
113 $keyPad = calculateKeyPadding(getTranslationByKeys($enContent, $arrayKeys));
116 $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad);
123 // Fill missing lines
124 $arraySize = max(array_keys($formatted));
125 $formatted = array_replace(array_fill(0, $arraySize, ''), $formatted);
127 // Add remaining translations
128 $langContent = array_filter($langContent, function($item) {
129 return !is_null($item) && !empty($item);
131 if (count($langContent) > 0) {
133 $formatted[] = "\t// Unmatched";
135 foreach ($langContent as $key => $value) {
136 if (is_array($value)) {
137 $formatted[] = formatTranslationArray($key, $value);
139 $formatted[] = formatTranslationLine($key, $value);
145 return implode("\n", $formatted);
149 * Format a translation line.
151 * @param string $value
156 function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) : string {
157 $start = str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ');
158 if (strpos($value, "\n") !== false) {
159 $escapedValue = '"' . str_replace("\n", '\n', $value) . '"';
160 $escapedValue = '"' . str_replace('"', '\"', $escapedValue) . '"';
162 $escapedValue = "'" . str_replace("'", "\\'", $value) . "'";
164 return "{$start} => {$escapedValue},";
168 * Find the longest key in the array and provide the length
169 * for all keys to be used when printed.
170 * @param array $array
173 function calculateKeyPadding(array $array) : int {
175 foreach ($array as $key => $value) {
176 $keyLen = strlen($key);
177 $top = max($top, $keyLen);
179 return min(35, $top + 2);
183 * Format an translation array with the given key.
184 * Simply prints as an old-school php array.
185 * Used as a last-resort backup to save unused translations.
187 * @param array $array
190 function formatTranslationArray(string $key, array $array) : string {
191 $arrayPHP = var_export($array, true);
192 return " '{$key}' => {$arrayPHP},";
196 * Find a string translation value within a multi-dimensional array
197 * by traversing the given array of keys.
198 * @param array $translations
200 * @return string|array
202 function getTranslationByKeys(array $translations, array $keys) {
203 $val = $translations;
204 foreach ($keys as $key) {
205 $val = $val[$key] ?? '';
206 if ($val === '') return '';
212 * Unset an inner item of a multi-dimensional array by
213 * traversing the given array of keys.
214 * @param array $input
217 function unsetArrayByKeys(array &$input, array $keys) {
219 $lastIndex = count($keys) - 1;
220 foreach ($keys as $index => &$key) {
221 if ($index === $lastIndex && is_array($val)) {
224 if (!is_array($val)) return;
225 $val = &$val[$key] ?? [];
230 * Write the given content to a translation file.
231 * @param string $lang
232 * @param string $fileName
233 * @param string $content
235 function writeLangFile(string $lang, string $fileName, string $content) {
236 $path = __DIR__ . "/{$lang}/{$fileName}.php";
237 if (!file_exists($path)) {
238 errorOut("Expected translation file '{$path}' does not exist");
240 file_put_contents($path, $content);
244 * Load the contents of a language file as an array of text lines.
245 * @param string $lang
246 * @param string $fileName
249 function loadLangFileLines(string $lang, string $fileName) : array {
250 $path = __DIR__ . "/{$lang}/{$fileName}.php";
251 if (!file_exists($path)) {
252 errorOut("Expected translation file '{$path}' does not exist");
254 $lines = explode("\n", file_get_contents($path));
255 return array_map(function($line) {
256 return trim($line, "\r");
261 * Load the contents of a language file
262 * @param string $lang
263 * @param string $fileName
266 function loadLang(string $lang, string $fileName) : array {
267 $path = __DIR__ . "/{$lang}/{$fileName}.php";
268 if (!file_exists($path)) {
269 errorOut("Expected translation file '{$path}' does not exist");
272 $fileData = include($path);
277 * Fetch an array containing the names of all translation files without the extension.
280 function getTranslationFileNames() : array {
281 $dir = __DIR__ . "/en";
282 if (!file_exists($dir)) {
283 errorOut("Expected directory '{$dir}' does not exist");
285 $files = scandir($dir);
287 foreach ($files as $file) {
288 if (substr($file, -4) === '.php') {
289 $fileNames[] = substr($file, 0, strlen($file) - 4);
296 * Format a locale to follow the lowercase_UPERCASE standard
297 * @param string $lang
300 function formatLocale(string $lang) : string {
301 $langParts = explode('_', strtoupper($lang));
302 $langParts[0] = strtolower($langParts[0]);
303 return implode('_', $langParts);
307 * Dump a variable then die.
310 function dd($content) {
316 * Log out some information text in blue
319 function info($text) {
320 echo "\e[34m" . $text . "\e[0m\n";
324 * Log out an error in red and exit.
327 function errorOut($text) {
328 echo "\e[31m" . $text . "\e[0m\n";