<?php /** * File: gettext * Scans the installation and creates .pot files for all translated strings. */ header("Content-Type: text/html; charset=UTF-8"); define('DEBUG', true); define('CHYRP_VERSION', "2025.01"); define('CHYRP_CODENAME', "Boreal"); define('CHYRP_IDENTITY', "Chyrp/".CHYRP_VERSION." (".CHYRP_CODENAME.")"); define('MAIN', false); define('ADMIN', false); define('AJAX', false); define('UPGRADING', false); define('INSTALLING', false); define('COOKIE_LIFETIME', 2592000); define('PASSWORD_RESET_TOKEN_LIFETIME', 3600); define('MAX_TIME_LIMIT', 600); define('MAX_MEMORY_LIMIT', "100M"); define('SQL_DATETIME_ZERO', "1000-01-01 00:00:00"); define('SQL_DATETIME_ZERO_VARIANTS', array( "0000-00-00 00:00:00", "0001-01-01 00:00:00", "1000-01-01 00:00:00" ) ); define('BOT_UA', false); define('DIR', DIRECTORY_SEPARATOR); define('MAIN_DIR', dirname(__FILE__)); define('INCLUDES_DIR', MAIN_DIR.DIR."includes"); define('CACHES_DIR', INCLUDES_DIR.DIR."caches"); define('MODULES_DIR', MAIN_DIR.DIR."modules"); define('FEATHERS_DIR', MAIN_DIR.DIR."feathers"); define('THEMES_DIR', MAIN_DIR.DIR."themes"); define('UPDATE_XML', null); define('UPDATE_INTERVAL', null); define('UPDATE_PAGE', null); define('SESSION_DENY_BOT', true); define('SLUG_STRICT', true); define('GET_REMOTE_UNSAFE', false); define('USE_GETTEXT_SHIM', stripos(PHP_OS, "Win") === 0); define('USE_OB', true); define('HTTP_ACCEPT_ZSTD', false); define('HTTP_ACCEPT_DEFLATE', false); define('HTTP_ACCEPT_GZIP', false); define('CAN_USE_ZSTD', false); define('CAN_USE_ZLIB', false); define('USE_COMPRESSION', false); define('PREVIEWING', false); define('THEME_DIR', null); define('THEME_URL', null); ob_start(); define('OB_BASE_LEVEL', ob_get_level()); # File: error # Functions for handling and reporting errors. require_once INCLUDES_DIR.DIR."error.php"; # File: helpers # Various functions used throughout the codebase. require_once INCLUDES_DIR.DIR."helpers.php"; # Array: $domains # An associative array of domains and their paths. $domains = array( "chyrp" => MAIN_DIR, "admin" => MAIN_DIR.DIR."admin" ); # Array: $exclude # Paths to be excluded from directory recursion. $exclude = array( MAIN_DIR.DIR."admin", MAIN_DIR.DIR."modules", MAIN_DIR.DIR."feathers", MAIN_DIR.DIR."themes", MAIN_DIR.DIR."tools", MAIN_DIR.DIR."uploads", MAIN_DIR.DIR."includes".DIR."caches", MAIN_DIR.DIR."includes".DIR."lib".DIR."Twig", MAIN_DIR.DIR."includes".DIR."lib".DIR."IXR", MAIN_DIR.DIR."includes".DIR."lib".DIR."cebe" ); # Array: $strings # Contains the translations for each gettext domain. $strings = array(); # String: $str_reg # Regular expression representing a string. $str_reg = '(\"[^\"]+\"|\'[^\']+\')'; /** * Function: find_domains * Find domains for installed extensions. */ function find_domains() { global $domains; global $strings; $modules_dir = new DirectoryIterator(MODULES_DIR); foreach ($modules_dir as $item) { if ($item->isDir() and !$item->isDot()) $domains[$item->getFilename()] = $item->getPathname(); } $feathers_dir = new DirectoryIterator(FEATHERS_DIR); foreach ($feathers_dir as $item) { if ($item->isDir() and !$item->isDot()) $domains[$item->getFilename()] = $item->getPathname(); } $themes_dir = new DirectoryIterator(THEMES_DIR); foreach ($themes_dir as $item) { if ($item->isDir() and !$item->isDot()) $domains[$item->getFilename()] = $item->getPathname(); } foreach ($domains as $filename => $pathname) $strings[$filename] = array(); } /** * Function: scan_dir * Scans a directory in search of files or subdirectories. */ function scan_dir( $domain, $pathname ) { global $exclude; $dir = new DirectoryIterator($pathname); foreach ($dir as $item) { if (!$item->isDot()) { $item_path = $item->getPathname(); $extension = $item->getExtension(); switch ($item->getType()) { case "file": scan_file($domain, $item_path, $extension); break; case "dir": if (!in_array($item_path, $exclude)) scan_dir($domain, $item_path); break; } } } } /** * Function: scan_file * Scans a file in search of translation strings. */ function scan_file( $domain, $pathname, $extension ) { if ($extension != "php" and $extension != "twig") return; $file = fopen($pathname, "r"); $line = 1; if ($file === false) return; while (!feof($file)) { $text = fgets($file); switch ($extension) { case "php": scan__($domain, $pathname, $line, $text); scan_f($domain, $pathname, $line, $text); scan_p($domain, $pathname, $line, $text); break; case "twig": scan_translate($domain, $pathname, $line, $text); scan_translate_format($domain, $pathname, $line, $text); scan_translate_plural($domain, $pathname, $line, $text); break; } $line++; } fclose($file); } /** * Function: make_place * Makes a string detailing the place a translation was found. */ function make_place( $pathname, $line ) { return str_replace( array(MAIN_DIR.DIR, DIR), array("", "/"), $pathname ).":".$line; } /** * Function: is_theme * Checks if a pathname is part of a theme. */ function is_theme( $pathname ) { return ( strpos($pathname, THEMES_DIR) === 0 or strpos($pathname, MAIN_DIR.DIR."admin") === 0 ); } /** * Function: scan__ * Scans text for occurrences of the __() function. */ function scan__( $domain, $pathname, $line, $text ) { global $domains; global $strings; global $str_reg; $escaped = preg_quote($domain, "/"); $dom_reg = ($domain == "chyrp") ? '' : ',\s*(\"'.$escaped.'\"|\''.$escaped.'\')' ; if ( preg_match_all( "/__\($str_reg$dom_reg\)/", $text, $matches, PREG_SET_ORDER ) ) { foreach ($matches as $match) { $string = trim($match[1], "'\""); if (isset($strings[$domain][$string])) $strings[$domain][$string]["places"][] = make_place( $pathname, $line ); else $strings[$domain][$string] = array( "places" => array(make_place($pathname, $line)), "filter" => false, "plural" => false ); } } } /** * Function: scan_f * Scans text for occurrences of the _f() function. */ function scan_f( $domain, $pathname, $line, $text ) { global $domains; global $strings; global $str_reg; $escaped = preg_quote($domain, "/"); $dom_reg = ($domain == "chyrp") ? '.+?' : '.+?,\s*(\"'.$escaped.'\"|\''.$escaped.'\')' ; if ( preg_match_all( "/_f\($str_reg$dom_reg\)/", $text, $matches, PREG_SET_ORDER ) ) { foreach ($matches as $match) { $string = trim($match[1], "'\""); if (isset($strings[$domain][$string])) $strings[$domain][$string]["places"][] = make_place( $pathname, $line ); else $strings[$domain][$string] = array( "places" => array(make_place($pathname, $line)), "filter" => true, "plural" => false ); } } } /** * Function: scan_p * Scans text for occurrences of the _p() function. */ function scan_p( $domain, $pathname, $line, $text ) { global $domains; global $strings; global $str_reg; $escaped = preg_quote($domain, "/"); $dom_reg = ($domain == "chyrp") ? '.+?' : '.+?,\s*(\"'.$escaped.'\"|\''.$escaped.'\')' ; if ( preg_match_all( "/_p\($str_reg,\s*$str_reg$dom_reg\)/", $text, $matches, PREG_SET_ORDER ) ) { foreach ($matches as $match) { $string = trim($match[1], "'\""); $plural = trim($match[2], "'\""); if (isset($strings[$domain][$string])) $strings[$domain][$string]["places"][] = make_place( $pathname, $line ); else $strings[$domain][$string] = array( "places" => array(make_place($pathname, $line)), "filter" => true, "plural" => $plural ); } } } /** * Function: scan_translate * Scans text for occurrences of the translate() filter. */ function scan_translate( $domain, $pathname, $line, $text ) { global $domains; global $strings; global $str_reg; $escaped = preg_quote($domain, "/"); $dom_reg = '(\(\s*(\"'.$escaped.'\"|\''.$escaped.'\')\s*\))'; if (is_theme($pathname)) $dom_reg.= '?'; if ( preg_match_all( "/$str_reg\s*\|\s*translate(?!_plural)$dom_reg(?!\s*\|\s*format)/", $text, $matches, PREG_SET_ORDER ) ) { foreach ($matches as $match) { $string = trim($match[1], "'\""); if (isset($strings[$domain][$string])) $strings[$domain][$string]["places"][] = make_place( $pathname, $line ); else $strings[$domain][$string] = array( "places" => array(make_place($pathname, $line)), "filter" => false, "plural" => false ); } } } /** * Function: scan_translate_format * Scans text for occurrences of the translate() | format() filter combination. */ function scan_translate_format( $domain, $pathname, $line, $text ) { global $domains; global $strings; global $str_reg; $escaped = preg_quote($domain, "/"); $dom_reg = '(\(\s*(\"'.$escaped.'\"|\''.$escaped.'\')\s*\))'; if (is_theme($pathname)) $dom_reg.= '?'; if ( preg_match_all( "/$str_reg\s*\|\s*translate$dom_reg\s*\|\s*format/", $text, $matches, PREG_SET_ORDER ) ) { foreach ($matches as $match) { $string = trim($match[1], "'\""); if (isset($strings[$domain][$string])) $strings[$domain][$string]["places"][] = make_place( $pathname, $line ); else $strings[$domain][$string] = array( "places" => array(make_place($pathname, $line)), "filter" => true, "plural" => false ); } } } /** * Function: scan_translate_plural * Scans text for occurrences of the translate_plural() filter. */ function scan_translate_plural( $domain, $pathname, $line, $text ) { global $domains; global $strings; global $str_reg; $escaped = preg_quote($domain, "/"); $dom_reg = '(,\s*(\"'.$escaped.'\"|\''.$escaped.'\'))'; if (is_theme($pathname)) $dom_reg.= '?'; if ( preg_match_all( "/$str_reg\s*\|\s*translate_plural\(\s*$str_reg\s*,.+?$dom_reg\s*\)\s*\|\s*format/", $text, $matches, PREG_SET_ORDER ) ) { foreach ($matches as $match) { $string = trim($match[1], "'\""); $plural = trim($match[2], "'\""); if (isset($strings[$domain][$string])) $strings[$domain][$string]["places"][] = make_place( $pathname, $line ); else $strings[$domain][$string] = array( "places" => array(make_place($pathname, $line)), "filter" => true, "plural" => $plural ); } } } /** * Function: create_files * Generates the .pot files and writes them to disk. */ function create_files() { global $domains; global $strings; foreach ($domains as $filename => $pathname) { $contents = "#. This file is distributed under the". " same license as the Chyrp Lite package.\n\n"; foreach ($strings[$filename] as $string => $attributes) { foreach ($attributes["places"] as $place) $contents.= "#: ".$place."\n"; if ($attributes["filter"] === true) $contents.= "#, php-format\n"; $contents.= "msgid \"".$string."\"\n"; if ($attributes["plural"] !== false) { $contents.= "msgid_plural \"".$attributes["plural"]."\"\n"; $contents.= "msgstr[0] \"\"\n"; $contents.= "msgstr[1] \"\"\n"; } else { $contents.= "msgstr \"\"\n"; } $contents.= "\n"; } $pot_file = $pathname.DIR. (($filename == "chyrp") ? "includes".DIR : ""). "locale".DIR."en_US".DIR."LC_MESSAGES".DIR. $filename.".pot"; $result = @file_put_contents($pot_file, $contents); echo $filename.".pot: ".( ($result === false) ? '<strong>Boo!</strong>' : 'Yay!' )."\n"; } } #--------------------------------------------- # Output Starts #--------------------------------------------- ?> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=640"> <title><?php echo "Gettext"; ?></title> <style type="text/css"> @font-face { font-family: 'Open Sans webfont'; src: url('../fonts/OpenSans-Regular.woff') format('woff'); font-weight: normal; font-style: normal; } @font-face { font-family: 'Open Sans webfont'; src: url('../fonts/OpenSans-SemiBold.woff') format('woff'); font-weight: 600; font-style: normal; } @font-face { font-family: 'Open Sans webfont'; src: url('../fonts/OpenSans-Bold.woff') format('woff'); font-weight: bold; font-style: normal; } @font-face { font-family: 'Open Sans webfont'; src: url('../fonts/OpenSans-Italic.woff') format('woff'); font-weight: normal; font-style: italic; } @font-face { font-family: 'Open Sans webfont'; src: url('../fonts/OpenSans-SemiBoldItalic.woff') format('woff'); font-weight: 600; font-style: italic; } @font-face { font-family: 'Open Sans webfont'; src: url('../fonts/OpenSans-BoldItalic.woff') format('woff'); font-weight: bold; font-style: italic; } @font-face { font-family: 'Cousine webfont'; src: url('../fonts/Cousine-Regular.woff') format('woff'); font-weight: normal; font-style: normal; } @font-face { font-family: 'Cousine webfont'; src: url('../fonts/Cousine-Bold.woff') format('woff'); font-weight: bold; font-style: normal; } @font-face { font-family: 'Cousine webfont'; src: url('../fonts/Cousine-Italic.woff') format('woff'); font-weight: normal; font-style: italic; } @font-face { font-family: 'Cousine webfont'; src: url('../fonts/Cousine-BoldItalic.woff') format('woff'); font-weight: bold; font-style: italic; } :root { color-scheme: light dark; --chyrp-pure-white: #ffffff; --chyrp-pure-black: #000000; --chyrp-inky-black: #1f1f23; --chyrp-summer-grey: #fbfbfb; --chyrp-english-grey: #efefef; --chyrp-welsh-grey: #dfdfdf; --chyrp-irish-grey: #cfcfcf; --chyrp-scottish-grey: #afafaf; --chyrp-winter-grey: #656565; --chyrp-strong-yellow: #ffdd00; --chyrp-strong-orange: #ff7f00; --chyrp-strong-red: #c11600; --chyrp-strong-green: #108600; --chyrp-strong-blue: #1e57ba; --chyrp-strong-purple: #ba1eba; --chyrp-light-yellow: #fffde6; --chyrp-light-red: #faebe4; --chyrp-light-green: #ebfae4; --chyrp-light-blue: #f2fbff; --chyrp-light-purple: #fae4fa; --chyrp-medium-yellow: #fffbcc; --chyrp-medium-red: #fcddcf; --chyrp-medium-green: #daf1d0; --chyrp-medium-blue: #e1f2fa; --chyrp-medium-purple: #f6d5f6; --chyrp-border-yellow: #e5d7a1; --chyrp-border-red: #d6bdb5; --chyrp-border-green: #bdd6b5; --chyrp-border-blue: #b8cdd9; --chyrp-border-purple: #d6b5d6; } *::selection { color: var(--chyrp-inky-black); background-color: var(--chyrp-strong-yellow); } html, body, div, dl, dt, dd, ul, ol, li, p, h1, h2, h3, h4, h5, h6, img, pre, code, form, fieldset, input, select, svg, textarea, table, tbody, tr, th, td, legend, caption, blockquote, aside, figure, figcaption { margin: 0em; padding: 0em; border: 0em; } html { font-size: 16px; } body { font-size: 1rem; font-family: "Open Sans webfont", sans-serif; line-height: 1.5; color: var(--chyrp-inky-black); tab-size: 4; background: var(--chyrp-english-grey); margin: 2rem; } h1 { font-size: 2em; text-align: center; font-weight: bold; margin: 1rem 0rem; } h2 { font-size: 1.5em; text-align: center; font-weight: bold; margin: 1rem 0rem; } h3 { font-size: 1em; font-weight: 600; margin: 1rem 0rem; border-bottom: 1px solid var(--chyrp-irish-grey); } p { margin-bottom: 1rem; } strong { font: inherit; font-weight: bold; color: var(--chyrp-strong-red); } em, dfn, cite, var { font: inherit; font-style: italic; } ul, ol { margin-bottom: 1rem; margin-inline-start: 2rem; list-style-position: outside; } pre { font-family: "Cousine webfont", monospace; font-size: 0.85em; background-color: var(--chyrp-english-grey); margin: 1rem 0rem; padding: 1rem; overflow-x: auto; white-space: pre; } code { font-family: "Cousine webfont", monospace; font-size: 0.85em; background-color: var(--chyrp-english-grey); padding: 2px 4px 0px 4px; border: 1px solid var(--chyrp-irish-grey); vertical-align: bottom; white-space: break-spaces; } pre > code { font-size: 0.85rem; display: block; border: none; padding: 0px; white-space: inherit; } a:link, a:visited { color: var(--chyrp-inky-black); text-decoration: underline; text-underline-offset: 0.125em; } a:focus { outline: var(--chyrp-strong-orange) dashed 2px; outline-offset: 0px; } a:hover, a:focus, a:active { color: var(--chyrp-strong-blue); text-decoration: underline; text-underline-offset: 0.125em; } a.big, button { box-sizing: border-box; display: block; font: inherit; font-size: 1.25em; text-align: center; color: var(--chyrp-inky-black); text-decoration: none; margin: 1rem 0rem; padding: 0.5rem 1rem; background-color: var(--chyrp-light-blue); border: 2px solid var(--chyrp-border-blue); border-radius: 0.25em; cursor: pointer; } button { width: 100%; } a.big:hover, button:hover, a.big:focus, button:focus, a.big:active, button:active { border-color: var(--chyrp-strong-blue); outline: none; } hr { border: none; clear: both; border-top: 1px solid var(--chyrp-irish-grey); margin: 2rem 0rem; } aside { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid var(--chyrp-border-yellow); border-radius: 0.25em; background-color: var(--chyrp-light-yellow); } .window { width: 30rem; background: var(--chyrp-pure-white); padding: 2rem; margin: 0rem auto 0rem auto; border-radius: 2rem; } .window > *:first-child { margin-top: 0rem; } .window > *:last-child { margin-bottom: 0rem; } @media (prefers-color-scheme: dark) { body { color: var(--chyrp-pure-white); background-color: var(--chyrp-inky-black); } .window { color: var(--chyrp-inky-black); background-color: var(--chyrp-english-grey); } h3 { border-color: var(--chyrp-scottish-grey); } hr { border-color: var(--chyrp-scottish-grey); } aside { border-color: var(--chyrp-scottish-grey); } pre { background-color: var(--chyrp-welsh-grey); } code { background-color: var(--chyrp-welsh-grey); border-color: var(--chyrp-scottish-grey); } } </style> </head> <body> <div class="window"> <pre role="status" class="pane"><?php #--------------------------------------------- # Processing Starts #--------------------------------------------- find_domains(); foreach ($domains as $filename => $pathname) scan_dir($filename, $pathname); create_files(); #--------------------------------------------- # Processing Ends #--------------------------------------------- ?></pre> </div> </body> </html>