'${root_directory_path}/locales', 'default_locale' => null, 'extract_messages_excluded_paths' => ['.*/vendor/*'], ) ); self :: $root_path = App::get( 'i18n.root_directory', null, 'string'); $default_locale = App::get('i18n.default_locale', null, 'string'); if (!class_exists('Locale')) { Log :: error('Locale PHP class does not exist. May be php-intl is not installed?'); return; } $available_langs = self :: get_available_langs(); if (php_sapi_name() != "cli") { if (isset($_REQUEST['lang']) && in_array($_REQUEST['lang'], $available_langs)) { $lang = $_REQUEST['lang']; Log :: trace("Select lang from request parameter: '$lang'"); } elseif ( isset($_SESSION['lang']) && in_array($_SESSION['lang'], $available_langs) && !isset($_REQUEST['reset_lang']) ) { $lang = $_SESSION['lang']; Log :: trace("Restore lang from session: '$lang'"); } else { $lang = Locale::lookup( self :: get_available_langs(), Locale::acceptFromHttp( isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])? $_SERVER['HTTP_ACCEPT_LANGUAGE']:null ), true, Locale::getPrimaryLanguage(self :: $default_locale) ); Log :: trace("Best lang found is '$lang'"); } } else { $lang = null; $sys_current = getenv('LC_ALL'); if (!$sys_current) $sys_current = getenv('LANG'); if ($sys_current) $lang = Locale::getPrimaryLanguage($sys_current); if (is_null($lang)) { Log :: trace('No configured lang detected from CLI env, use default.'); $lang = Locale::getPrimaryLanguage(self :: $default_locale); } else Log :: trace("Lang detected from CLI env : '$lang'"); } // Keep selected lang in session $_SESSION['lang'] = $lang; $locale = self :: lang2locale($lang); Log :: trace("Matching locale found with language '$lang' is '$locale'"); // Gettext firstly look the LANGUAGE env variable, so set it if (!putenv("LANGUAGE=$locale")) Log :: error("Fail to set LANGUAGE variable in environment to '$locale'"); // Set the locale if (setlocale(LC_ALL, $locale) === false) Log :: error("An error occurred setting locale to '$locale'"); // Configure and set the text domain $fullpath = bindtextdomain(self :: CORE_TEXT_DOMAIN, self :: $core_root_path); Log :: trace("Core text domain %s fullpath is '%s'.", self :: CORE_TEXT_DOMAIN, $fullpath); Log :: trace("Test: ".self::_('Hello world !')); $fullpath = bindtextdomain(self :: TEXT_DOMAIN, self :: $root_path); Log :: trace("Default text domain %s fullpath is '%s'.", self :: TEXT_DOMAIN, $fullpath); Log :: trace("Default text domain is '".textdomain(self :: TEXT_DOMAIN)."'."); // JS translation file if ( php_sapi_name() != "cli" && Tpl :: initialized() ) { Tpl :: register_static_directory(self :: $root_path, null, 'locales/'); Tpl :: add_js_file("lib/babel.js", "js/translation.js"); Tpl :: add_js_file("locales/", "$locale.js"); } if (php_sapi_name() == 'cli') { Cli :: add_command( 'extract_messages', array('\\EesyPHP\\I18n', 'cli_extract_messages'), ___("Extract messages that need to be translated"), null, ___("This command could be used to generate/update locales/messages.pot file.") ); Cli :: add_command( 'update_messages', array('\\EesyPHP\\I18n', 'cli_update_messages'), ___("Update messages in translation PO lang files"), null, ___("This command could be used to init/update PO files in locales/*/LC_MESSAGES directories.") ); Cli :: add_command( 'compile_messages', array('\\EesyPHP\\I18n', 'cli_compile_messages'), ___( "Compile messages from existing translation PO lang files to ". "corresponding MO files and JS catalogs" ), null, ___( "This command could be used to compile PO files in locales/*/LC_MESSAGES ". "directories to MO files and as JS catalogs in locales directory." ) ); Cli :: add_command( 'init_locale', array('\\EesyPHP\\I18n', 'cli_init_locale'), ___("Initialize a new locale for translation."), "[locale]", ___("This command could be used to initialize a new locale for translation.") ); } } /** * List available translation languages * * @param $as_locales boolean If true, locale names will be return instead * of primary languages (optional, default: false) * * @return array Array of available translation languages (or locales) */ public static function get_available_langs($as_locales=false) { if (!is_dir(self :: $root_path)) Log :: fatal("Root land directory not found (%s)", self :: $root_path); $langs = array(($as_locales?'en_US.UTF8':'en')); if ($dh = opendir(self :: $root_path)) { while (($file = readdir($dh)) !== false) { if (!is_dir(self :: $root_path . '/' . $file) || in_array($file, array('.', '..'))) continue; if ($as_locales) { $langs[] = $file; continue; } $lang = Locale::getPrimaryLanguage($file); if (!in_array($lang, $langs)) $langs[] = $lang; } closedir($dh); } $langs = array_unique($langs); Log :: trace('Available '.($as_locales?'locales':'languages').': '.implode(', ', $langs)); return $langs; } /** * Get locale name corresponding to specified translation language * * @param $lang string The translation language * @param $default string|null Default locale name to return if any available translation * locales matched with the specified language * (optional, default: self :: $default_locale) * @return string Corresponding locale */ public static function lang2locale($lang, $default=null) { if (is_null($default)) $default = self :: $default_locale; foreach (self :: get_available_langs(true) as $locale) { if (strpos($locale, $lang) === false) continue; return $locale; } return $default; } /* ******************************************************************** * Translations helpers * ******************************************************************** */ /** * Translate a message using gettext * @param string $message The string to translate * @param array $extra_args Extra arguments used to compute the message using sprintf * @return string The translated and computed message */ public static function _(string $message, ...$extra_args) { $return = dgettext(self :: CORE_TEXT_DOMAIN, $message); // If extra arguments passed, format message using sprintf if ($extra_args) { $return = call_user_func_array( 'sprintf', array_merge(array($return), $extra_args) ); } return $return; } /** * Translate a message with singular and plural forms using gettext * @param string $singular The singular message to translate * @param string $plural The plural message to translate * @param int $count The item count to determine if the singular or the plural forms must to be * used * @param array $extra_args Extra arguments used to compute the message using sprintf * @return string The translated and computed message */ public static function ngettext(string $singular, string $plural, int $count, ...$extra_args) { $return = dngettext(self :: CORE_TEXT_DOMAIN, $singular, $plural, $count); // If extra arguments passed, format message using sprintf if ($extra_args) { $return = call_user_func_array( 'sprintf', array_merge(array($return), $extra_args) ); } return $return; } /** * Normalize a locale name * @param string $name The locale name to normalize * @param boolean $encoding_lower Set to true to put encoding in lowercase (optional, default: false) * @return string|false The normalized locale of false in case of invalid provide values */ public static function normalize_locale_name($name, $encoding_lower=False) { if ( !preg_match( "/^(?P[a-z]{2})_(?P[a-z]{2})((@(?P[a-z]+))|(\.(?P[a-z0-9\-]+)))?$/i", $name, $m ) ) return false; $locale = strtolower($m['lang0'])."_".strtoupper($m['lang1']); if ($m['at']) $locale .= ".".strtolower($m['at']); if ($m['encoding']) { $m['encoding'] = strtoupper($m['encoding']); if ($m['encoding'] == "UTF-8") $m['encoding'] = "UTF8"; if ($encoding_lower) $m['encoding'] = strtolower($m['encoding']); $locale .= ".".$m['encoding']; } return $locale; } /* ******************************************************************** * Translations CLI commands * ******************************************************************** */ /** * Convert PO file to JSON file * * @param string $locale The locale of the input PO file * @param string $po_path The path of the main PO input file * @param string|null $domain The domain specified in the output JSON file * (optional, default: self :: CORE_TEXT_DOMAIN) * @param array|null $other_po_paths Optional list of other PO files: if specified, * their translated messages will be loaded and * included in output JSON file. * * @return string JSON encoded file content */ public static function po2json($locale, $po_path, $domain=null, $other_po_paths=null) { Log :: trace('Load PO file %s', $po_path); $fileHandler = new \Sepia\PoParser\SourceHandler\FileSystem($po_path); $poparser = new \Sepia\PoParser\Parser($fileHandler); $catalog = $poparser->parse(); $headers = $catalog->getHeader(); $messages = array(); foreach ($catalog->getEntries() as $entry) { // msg id json format $msg = $entry->getMsgStr(); if ($entry->isPlural()) $msg = array($msg, $entry->getMsgIdPlural()); $messages[$entry->getMsgId()] = $msg; } foreach(ensure_is_array($other_po_paths) as $path) { Log :: trace('Load PO file %s (for its translated messages only)', $path); $fileHandler = new \Sepia\PoParser\SourceHandler\FileSystem($path); $poparser = new \Sepia\PoParser\Parser($fileHandler); $catalog = $poparser->parse(); foreach ($catalog->getEntries() as $entry) { // msg id json format $msg = $entry->getMsgStr(); if ($entry->isPlural()) $msg = array($msg, $entry->getMsgIdPlural()); $messages[$entry->getMsgId()] = $msg; } } return json_encode(array( 'messages' => $messages, 'locale' => $locale, 'domain' => $domain?$domain:self :: CORE_TEXT_DOMAIN, 'plural_expr' => '(n > 1)', )); } /** * Command to initialize a new locale. * * @param array $command_args The command arguments * @return void */ public static function cli_init_locale($command_args) { $root_path = Cli::core_mode()?self::$core_root_path:self::$root_path; if (!is_dir($root_path)) Log :: fatal(self::_("Root locales directory does not exists ($root_path).")); $domain = Cli::core_mode()?self::CORE_TEXT_DOMAIN:self::TEXT_DOMAIN; if (count($command_args) != 1) Cli::usage(self::_("You must provide the locale to initialize as unique and first argument.")); $locale = self::normalize_locale_name($command_args[0], true); if (!$locale) Log :: fatal(self::_("Invalid locale %s.", $command_args[0])); // Validate locale $result = run_external_command(["locale", "-a"]); if (!is_array($result) || $result[0] != 0) Log :: fatal(self::_("Fail to list valid locales.")); if (!in_array($locale, explode("\n", $result[1]))) Log :: fatal(self::_("Invalid locale %s.", $locale)); // Continue with locale with encoding as uppercase $locale = self::normalize_locale_name($locale); // Check and create locale directory $locale_dir = "$root_path/$locale"; if (!is_dir($locale_dir)) { if (mkdir($locale_dir)) Log::info(I18n::_("Locale %s directory created (%s)."), $locale, $locale_dir); else Log::fatal(I18n::_("Fail to create locale %s directory (%s)."), $locale, $locale_dir); } else Log::debug(I18n::_("Locale %s directory already exist (%s)."), $locale, $locale_dir); // Check and create locale LC_MESSAGES directory $locale_lc_dir = "$locale_dir/LC_MESSAGES"; if (!is_dir($locale_lc_dir)) { if (mkdir($locale_lc_dir)) Log::info( I18n::_("Locale %s LC_MESSAGES directory created (%s)."), $locale, $locale_lc_dir ); else Log::fatal( I18n::_("Fail to create locale %s LC_MESSAGES directory (%s)."),$locale, $locale_lc_dir ); } else Log::debug(I18n::_("Locale %s LC_MESSAGES directory already exist (%s)."), $locale, $locale_dir); $po_file = "$locale_lc_dir/$domain.po"; if (!is_file($po_file)) { $fd = fopen($po_file, 'w'); $lines = fwrite( $fd, implode( "\n", array( 'msgid ""', 'msgstr ""', '"POT-Creation-Date: '.date('Y-m-d H:iO').'\n"', '"PO-Revision-Date: '.date('Y-m-d H:iO').'\n"', '"Language: '.substr($locale, 0, 2).'\n"', '"MIME-Version: 1.0\n"', '"Content-Type: text/plain; charset=utf-8\n"', '"Content-Transfer-Encoding: 8bit\n"', '"Plural-Forms: nplurals=2; plural=(n > 1);\n"', ) ) ); fclose($fd); Log::info( I18n::_("Locale %s PO file created (%s)."), $locale, $po_file ); } else Log::debug(I18n::_("Locale %s PO file already exist (%s)."), $locale, $po_file); // Extract messages self :: cli_extract_messages([]); self :: cli_update_messages([]); self :: cli_compile_messages([]); } /** * Command to extract messages from PHP/JS & template files and * generate the lang/messages.pot file. * * @param array $command_args The command arguments * @return void */ public static function cli_extract_messages($command_args) { $root_path = Cli::core_mode()?self::$core_root_path:self::$root_path; // Store list of generated POT files $pot_files = array(); $root_directory_path = App::root_directory_path(); $excluded_paths = App::get('i18n.extract_messages_excluded_paths', null, 'array'); if (Cli::core_mode()) { // List EesyPHP PHP files to parse $cmd = array( 'find', '-name', "'*.php'", '-type', 'f', // Looking for PHP files "-not", "-name", "'*.tpl.php'", // Exclude Smarty cache template files ); if ($excluded_paths) foreach($excluded_paths as $path) array_push($cmd, "-not", "-path", "'$path'"); $eesyphp_php_files = run_external_command( $cmd, null, // no STDIN data false, // do not escape command args (already done) "$root_directory_path/src" // Run from EesyPHP src directory ); if (!is_array($eesyphp_php_files) || $eesyphp_php_files[0] != 0) Log :: fatal(self::_("Fail to list EesyPHP PHP files.")); // Extract messages from EesyPHP PHP files using xgettext $pot_file = "$root_path/php-messages.pot"; $result = run_external_command( array( "xgettext", "--from-code utf-8", "--language=PHP", "-o", $pot_file, // Output "--omit-header", // No POT header "--keyword=___", // Handle custom ___() translation function "--files=-", // Read files to parse from STDIN "--force-po", // Write PO file even if empty ), $eesyphp_php_files[1], // Pass PHP files list via STDIN true, // Escape parameters "$root_directory_path/src" // Run from EesyPHP src directory ); if (!is_array($result) || $result[0] != 0) Log :: fatal(self::_("Fail to extract messages from EesyPHP PHP files using xgettext.")); if (is_file($pot_file)) $pot_files[] = $pot_file; } else { // List application PHP files to parse $cmd = array( 'find', '-name', "'*.php'", '-type', 'f', // Looking for PHP files "-not", "-name", "'*.tpl.php'", // Exclude Smarty cache template files ); foreach($excluded_paths as $path) array_push($cmd, "-not", "-path", "'$path'"); $php_files = run_external_command( $cmd, null, // no STDIN data false, // do not escape command args (already done) $root_directory_path // Run from application root directory ); if (!is_array($php_files) || $php_files[0] != 0) Log :: fatal(self::_("Fail to list application PHP files.")); // Extract messages from PHP files using xgettext $pot_file = "$root_path/php-messages.pot"; $result = run_external_command( array( "xgettext", "--from-code utf-8", "--language=PHP", "-o", $pot_file, // Output "--omit-header", // No POT header "--keyword=___", // Handle custom ___() translation function "--files=-", // Read files to parse from STDIN "--force-po", // Write PO file even if empty ), $php_files[1], // Pass PHP files list via STDIN true, // Escape parameters $root_directory_path // Run from application root directory ); if (!is_array($result) || $result[0] != 0) Log :: fatal(self::_("Fail to extract messages from PHP files using xgettext.")); $pot_files[] = "$root_path/php-messages.pot"; } // Extract messages from JS files using xgettext in each registered static directories foreach(Tpl::static_directories() as $idx => $static_directory) { if (Cli::core_mode() && $static_directory != Tpl::$core_static_directory) continue; if (!Cli::core_mode() && $static_directory == Tpl::$core_static_directory) continue; // Make path relative to application root directory $relative_static_directory = App::relative_path($static_directory); if (!$relative_static_directory) { Log::debug("Static directory '%s' does not exist, ignore it.", $static_directory); continue; } // List JS files to parse $cmd = array( 'find', escapeshellarg($relative_static_directory), '-name', "'*.tpl'", '-type', 'f' ); foreach($excluded_paths as $path) array_push($cmd, "-not", "-path", "'$path'"); $result = run_external_command( $cmd, null, // no STDIN data false, // do not escape command args (already done) $root_directory_path // Run from application root directory ); if (!is_array($result) || $result[0] != 0) Log :: fatal( self::_("Fail to list JS files in the directory of static files '%s'."), $static_directory); // Extract messages from JS files using xgettext $pot_file = "$root_path/js-$idx-messages.pot"; $result = run_external_command( array( "xgettext", "--from-code utf-8", "--language=JavaScript", "-o", $pot_file, // Output "--omit-header", // No POT header "--keyword=___", // Handle custom ___() translation function "--files=-", // Read files to parse from STDIN "--force-po", // Write PO file even if empty ), $result[1], // Pass JS files list via STDIN true, // Escape arguments $root_directory_path // Run from application root directory ); if (!is_array($result) || $result[0] != 0) Log :: fatal( self::_("Fail to extract messages from JS files in the directory of static files '%s' using xgettext."), $static_directory); if (is_file($pot_file)) $pot_files[] = $pot_file; } if (Tpl :: initialized()) { foreach (Tpl :: templates_directories() as $idx => $templates_directory) { if (Cli::core_mode() && $templates_directory != Tpl::$core_templates_directory) continue; if (!Cli::core_mode() && $templates_directory == Tpl::$core_templates_directory) continue; // Make path relative to application root directory $relative_templates_directory = App::relative_path($templates_directory); if (!$relative_templates_directory) { Log::debug("Templates directory '%s' does not exist, ignore it.", $templates_directory); continue; } // List templates files to parse $cmd = array( 'find', escapeshellarg($relative_templates_directory), '-name', "'*.tpl'", '-type', 'f' ); foreach($excluded_paths as $path) array_push($cmd, "-not", "-path", "'$path'"); $templates_files = run_external_command( $cmd, null, // no STDIN data false, // do not escape command args (already done) dirname($templates_directory) // Run from parent directory ); if (!is_array($templates_files) || $templates_files[0] != 0) Log :: fatal( self::_("Fail to list templates files in directory %s."), $templates_directory ); // Compute template files file from find command output $templates_files = explode("\n", $templates_files[1]); // Extract messages from templates files using tsmarty2c.php $cmd = array( PHP_BINARY.' '.App :: root_directory_path(). "/vendor/smarty-gettext/smarty-gettext/tsmarty2c.php", ); array_push($cmd, ...$templates_files); $result = run_external_command( $cmd, null, // Pass nothing on STDIN true, // Escape arguments $root_directory_path // Run from application root directory ); if (!is_array($result) || $result[0] != 0) Log :: fatal( self::_("Fail to extract messages from templates directory '%s' using tsmarty2c.php script."), $templates_directory ); if (!$result[1]) continue; $pot_file = "$root_path/templates-$idx-messages.pot"; $fd = fopen($pot_file, 'w'); fwrite($fd, $result[1]); fclose($fd); $pot_files[] = $pot_file; } } $fd = fopen("$root_path/headers.pot", 'w'); $headers = array( 'msgid ""', 'msgstr ""', '"POT-Creation-Date: '.date('Y-m-d H:iO').'\n"', '"PO-Revision-Date: '.date('Y-m-d H:iO').'\n"', '"MIME-Version: 1.0\n"', '"Content-Type: text/plain; charset=utf-8\n"', '"Content-Transfer-Encoding: 8bit\n"', ); fwrite($fd, implode("\n", $headers)); fclose($fd); // Merge previous results in messages.pot file using msgcat $result = run_external_command(array_merge( array( 'msgcat', "-t", "utf-8", "--use-first", "--force-po", "-o", "$root_path/messages.pot", "$root_path/headers.pot", ), $pot_files )); if (!is_array($result) || $result[0] != 0) Log :: fatal(self::_("Fail to merge messages using msgcat.")); } /** * Command to update messages from lang/messages.pot file to * all PO file in lang/[lang]/LC_MESSAGES. * * @param array $command_args The command arguments * @return bool */ public static function cli_update_messages($command_args) { $compendium_args = array(); foreach ($command_args as $arg) { if (!file_exists($arg)) Log :: fatal(self::_("Compendium file %s not found."), $arg); $compendium_args[] = '-C'; $compendium_args[] = $arg; } $domain = Cli::core_mode()?self::CORE_TEXT_DOMAIN:self::TEXT_DOMAIN; $root_path = Cli::core_mode()?self::$core_root_path:self::$root_path; $pot_file = "$root_path/messages.pot"; if (!is_file($pot_file)) Log :: fatal(self::_("POT file not found (%s). Please run extract_messages first."), $pot_file); if ($dh = opendir($root_path)) { $error = False; while (($file = readdir($dh)) !== false) { if ( !is_dir("$root_path/$file") || in_array($file, array('.', '..')) || is_link("$root_path/$file") ) continue; Log :: debug(self::_("Lang directory '%s' found"), $file); // Check LC_MESSAGES directory exists $lang = $file; $lang_dir = "$root_path/$file/LC_MESSAGES" ; if (!is_dir($lang_dir)) { Log :: debug(self::_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."), $lang); continue; } $po_file = "$lang_dir/$domain.po"; $created = false; if (!is_file($po_file)) { // Init PO file from POT file using msginit $result = run_external_command( array("msginit", "-i", "$pot_file", "-l", "$lang", "-o", $po_file) ); if (is_array($result) && $result[0] == 0) { $created = true; } else { Log :: error(self::_("Fail to init messages in %s PO file using msginit (%s)."), $lang, $po_file); $error = True; } } // Update messages in PO file from POT file using msgmerge // Note: msginit does not accept compendium files, so we also run // msgmerge on creation with compendium file(s). if (is_file($po_file) && (!$created || $compendium_args)) { $result = run_external_command( array_merge( array("msgmerge", "-q", "-U"), $compendium_args, array($po_file, $pot_file) ) ); if (!is_array($result) || $result[0] != 0) { Log :: error(self::_("Fail to update messages in %s PO file using msgmerge (%s)."), $lang, $po_file); $error = True; } } elseif (!$created) { Log :: debug(self::_("PO file not found in lang '%s' directory, ignore it."), $lang); } } closedir($dh); return !$error; } Log :: fatal(self::_("Fail to open root lang directory (%s)."), App :: root_directory_path()); } /** * Command to compile messages from existing translation PO lang files * to corresponding MO files and as JS catalog (for translation in JS). * * @param array $command_args The command arguments * @return bool */ public static function cli_compile_messages($command_args) { $domain = Cli::core_mode()?self::CORE_TEXT_DOMAIN:self::TEXT_DOMAIN; $root_path = Cli::core_mode()?self::$core_root_path:self::$root_path; if ($dh = opendir($root_path)) { $error = False; while (($file = readdir($dh)) !== false) { if ( !is_dir("$root_path/$file") || in_array($file, array('.', '..')) ) continue; if (is_link("$root_path/$file")) { $real_lang_dir = readlink("$root_path/$file"); if (dirname($real_lang_dir) != '.' || !is_dir("$root_path/$real_lang_dir")) continue; $lang = $file; Log :: debug(self::_("Lang alias symlink found: %s -> %s"), $lang, $real_lang_dir); // Create JS catalog symlink (if not exists) $js_link = "$root_path/$lang.js"; $link_target = "$real_lang_dir.js"; if (!file_exists($js_link)) { if (symlink($link_target, $js_link)) { Log :: info(self::_("JS catalog symlink for %s -> %s created (%s)"), $lang, $real_lang_dir, $js_link); } else { Log :: error(self::_("Fail to create JS catalog symlink for %s -> %s (%s)"), $lang, $real_lang_dir, $js_link); $error = True; } } elseif (readlink($js_link) == $link_target) { Log :: debug(self::_("JS catalog symlink for %s -> %s already exist (%s)"), $lang, $real_lang_dir, $js_link); } else { Log :: warning( self::_("JS catalog file for %s already exist, but it's not a symlink to %s (%s)"), $lang, $real_lang_dir, $js_link ); $error = True; } continue; } Log :: debug(self::_("Lang directory '%s' found"), $file); // Check LC_MESSAGES directory exists $lang = $file; $lang_dir = "$root_path/$file/LC_MESSAGES" ; if (!is_dir($lang_dir)) { Log :: debug(self::_("LC_MESSAGES directory not found in lang '%s' directory, ignore it."), $lang); continue; } // Test .PO file is present $po_file = "$lang_dir/$domain.po"; if (!is_file($po_file)) { Log :: debug(self::_("PO file not found in lang '%s' directory, ignore it."), $lang); continue; } $mo_file = preg_replace('/\.po$/', '.mo', $po_file); // Compile messages from PO file to MO file using msgfmt $result = run_external_command( array("msgfmt", "-o", $mo_file, $po_file) ); if (!is_array($result) || $result[0] != 0) { Log :: error( self::_("Fail to compile messages from %s PO file as MO file using msgfmt (%s)."), $lang, $po_file ); $error = True; } // Compile messages from PO file to JS catalog file $other_po_paths = array(); if ($domain != self::CORE_TEXT_DOMAIN) { $core_po_file = self::$core_root_path."/$lang/LC_MESSAGES/".self::CORE_TEXT_DOMAIN.".po"; if (is_file($core_po_file)) { Log :: debug( self::_('Include core translated messages from %s PO file'), $core_po_file ); $other_po_paths[] = $core_po_file; } else Log :: warning( self::_('Core PO file %s not found: can not include its translated messages in '. 'resulting JSON catalog.'), $core_po_file); } $js_catalog = self :: po2json($lang, $po_file, $domain, $other_po_paths); $js_file = "$root_path/$lang.js"; if(!$fd = fopen($js_file, 'w')) { Log :: error(self::_("Fail to open %s JS catalog file in write mode (%s)."), $lang, $js_file); $error = True; } elseif (fwrite($fd, sprintf("translations_data = %s;", $js_catalog)) === false) { Log :: error(self::_("Fail to write %s JS catalog in file (%s)."), $lang, $js_file); $error = True; } else { Log :: info(self::_("%s JS catalog writed (%s)."), $lang, $js_file); } } closedir($dh); return !$error; } Log :: fatal(self::_("Fail to open root lang directory (%s)."), App :: root_directory_path()); } } # vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab