src/output/Buffer.php 0000666 00000006651 15214141737 0010636 0 ustar 00 output;
}
/**
* @return Loco_output_Buffer
*/
public static function start(){
$buffer = new Loco_output_Buffer;
return $buffer->open();
}
/**
* @internal
* Ensure buffers closed if something terminates before we close gracefully
*/
public function __destruct(){
$this->close();
}
/**
* @return Loco_output_Buffer
*/
public function open(){
self::check();
if( ! ob_start() ){
throw new Loco_error_Exception('Failed to start output buffering');
}
$this->ob_level = ob_get_level();
return $this;
}
/**
* @return Loco_output_Buffer
*/
public function close(){
if( is_int($this->ob_level) ){
// collect output from our nested buffers
$this->output = self::collect( $this->ob_level );
$this->ob_level = null;
}
return $this;
}
/**
* Trash all open buffers, logging any junk output collected
* @return void
*/
public function discard(){
$this->close();
if( '' !== $this->output ){
self::log_junk( $this->output );
$this->output = '';
}
}
/**
* Collect output buffered to a given level
* @param int highest buffer to flush, 0 being the root
* @return string
*/
public static function collect( $min ){
$last = 0;
$output = '';
while( $level = ob_get_level() ){
// @codeCoverageIgnoreStart
if( $level === $last ){
throw new Loco_error_Exception('Failed to close output buffer');
}
// @codeCoverageIgnoreEnd
if( $level < $min ){
break;
}
// output is appended inside out:
$output = ob_get_clean().$output;
$last = $level;
}
return $output;
}
/**
* Forcefully destroy all open buffers and log any bytes already buffered.
* @return void
*/
public static function clear(){
$junk = self::collect(0);
if( '' !== $junk ){
self::log_junk($junk);
}
}
/**
* Check output has not already been flushed.
* @throws Loco_error_Exception
*/
public static function check(){
if( headers_sent($file,$line) && 'cli' !== PHP_SAPI ){
$file = str_replace( trailingslashit( loco_constant('ABSPATH') ), '', $file );
throw new Loco_error_Exception( sprintf( __('Loco interrupted by output from %s:%u','loco-translate'), $file, $line ) );
}
}
/**
* Debug collection of junk output
* @param string
*/
private static function log_junk( $junk ){
$bytes = strlen($junk);
$message = sprintf("Cleared %s of buffered output", Loco_mvc_FileParams::renderBytes($bytes) );
Loco_error_AdminNotices::debug( $message );
do_action( 'loco_buffer_cleared', $junk );
}
}
src/output/DiffRenderer.php 0000666 00000003654 15214141737 0011764 0 ustar 00 true,
'leading_context_lines' => 1,
'trailing_context_lines' => 1,
) );
}
/**
* Render diff of two files, presumed to be PO or POT
* @return string HTML table
*/
public function renderFiles( Loco_fs_File $lhs, Loco_fs_File $rhs ){
loco_require_lib('compiled/gettext.php');
// attempt to raise memory limit to WP_MAX_MEMORY_LIMIT
if( function_exists('wp_raise_memory_limit') ){
wp_raise_memory_limit('loco');
}
// like wp_text_diff but avoiding whitespace normalization
// uses deprecated signature for 'auto' in case of old WordPress
return $this->render( new Text_Diff (
preg_split( '/(?:\\n|\\r\\n?)/', Loco_gettext_Data::ensureUtf8( $lhs->getContents() ) ),
preg_split( '/(?:\\n|\\r\\n?)/', Loco_gettext_Data::ensureUtf8( $rhs->getContents() ) )
) );
}
/**
* {@inheritdoc}
*/
public function _startDiff() {
return "
\n";
}
/**
* {@inheritdoc}
*/
public function _endDiff() {
return "
\n";
}
/**
* {@inheritdoc}
*/
public function _startBlock( $header ) {
return '\n";
}
/**
* {@inheritdoc}
*/
public function _endBlock() {
return "\n";
}
}
src/package/Plugin.php 0000666 00000020416 15214141737 0010711 0 ustar 00 'get_plugins',
'WPMU_PLUGIN_DIR' => 'get_mu_plugins',
);
foreach( $search as $const => $getter ){
if( $list = call_user_func($getter) ){
$base = loco_constant($const);
foreach( $list as $handle => $data ){
if( isset($cached[$handle]) ){
Loco_error_AdminNotices::debug( sprintf('Plugin conflict on %s', $handle) );
continue;
}
// WordPress 4.6 introduced TextDomain header fallback @37562 see https://core.trac.wordpress.org/changeset/37562/
// if we don't force the original text domain header we can't know if a bundle is misconfigured. This leads to silent errors.
// this has a performance overhead, and also results in "unconfigured" messages that users may not have had in previous releases.
/*/ TODO perhaps implement a plugin setting that forces original headers
$file = new Loco_fs_File($base.'/'.$handle);
if( $file->exists() ){
$map = array( 'TextDomain' => 'Text Domain' );
$raw = get_file_data( $file->getPath(), $map, 'plugin' );
$data['TextDomain'] = $raw['TextDomain'];
}*/
// set resolved base directory before caching our copy of plugin data
$data['basedir'] = $base;
$cached[$handle] = $data;
}
}
}
$cached = apply_filters('loco_plugins_data', $cached );
uasort( $cached, '_sort_uname_callback' );
// Intended as in-memory cache so adding short expiry for object caching plugins that may persist it.
// All actions that invoke `wp_clean_plugins_cache` should purge this. See Loco_hooks_AdminHooks
wp_cache_set('plugins', $cached, 'loco', 3600 );
}
return $cached;
}
/**
* Get raw plugin data from WordPress registry, plus additional "basedir" field for resolving handle to actual file.
* @return array
*/
public static function get_plugin( $handle ){
$search = self::get_plugins();
// plugin must be registered with WordPress
if( isset($search[$handle]) ){
$data = $search[$handle];
}
// else plugin is not known to WordPress
else {
$data = apply_filters( 'loco_missing_plugin', array(), $handle );
}
// plugin not valid if name absent from raw data
if( empty($data['Name']) ){
return null;
}
// basedir is added by our get_plugins function, but filtered arrays could be broken
if( ! array_key_exists('basedir',$data) ){
Loco_error_AdminNotices::debug( sprintf('"basedir" property required to resolve %s',$handle) );
return null;
}
return $data;
}
/**
* {@inheritdoc}
*/
public function getHeaderInfo(){
$handle = $this->getHandle();
$data = self::get_plugin($handle);
if( ! is_array($data) ){
// permitting direct file access if file exists (tests)
$path = $this->getBootstrapPath();
if( $path && file_exists($path) ){
$data = get_plugin_data( $path, false, false );
}
else {
$data = array();
}
}
return new Loco_package_Header( $data );
}
/**
* {@inheritdoc}
*/
public function getMetaTranslatable(){
return array (
'Name' => 'Name of the plugin',
'Description' => 'Description of the plugin',
'PluginURI' => 'URI of the plugin',
'Author' => 'Author of the plugin',
'AuthorURI' => 'Author URI of the plugin',
// 'Tags' => 'Tags of the plugin',
);
}
/**
* {@inheritdoc}
*/
public function setHandle( $slug ){
// plugin handles are relative paths from plugin directory to bootstrap file
// so plugin is single file if its handle has no directory prefix
if( basename($slug) === $slug ){
$this->solo = true;
}
else {
$this->solo = false;
}
return parent::setHandle( $slug );
}
/**
* {@inheritdoc}
*/
public function setDirectoryPath( $path ){
parent::setDirectoryPath($path);
// plugin bootstrap file can be inferred from base directory + handle
// e.g. if base is "/path/to/foo" and handle is "foo/bar.php" we can derive "/path/to/foo/bar.php"
if( ! $this->getBootstrapPath() ){
$handle = $this->getHandle();
if( '' !== $handle ) {
$file = new Loco_fs_File( basename($handle) );
$file->normalize( $path );
$this->setBootstrapPath( $file->getPath() );
}
}
return $this;
}
/**
* Create plugin bundle definition from WordPress plugin data
*
* @param string plugin handle relative to plugin directory
* @return Loco_package_Plugin
*/
public static function create( $handle ){
// plugin must be registered with at least a name and "basedir"
$data = self::get_plugin($handle);
if( ! $data ){
throw new Loco_error_Exception( sprintf( __('Plugin not found: %s','loco-translate'),$handle) );
}
// lazy resolve of base directory from "basedir" property that we added
$file = new Loco_fs_File( $handle );
$file->normalize( $data['basedir'] );
$base = $file->dirname();
// handle and name is enough data to construct empty bundle
$bundle = new Loco_package_Plugin( $handle, $data['Name'] );
// check if listener heard the real text domain, but only use when none declared
// This will not longer happen since WP 4.6 header fallback, but we could warn about it
$listener = Loco_package_Listener::singleton();
if( $domain = $listener->getDomain($handle) ){
if( empty($data['TextDomain']) ){
$data['TextDomain'] = $domain;
if( empty($data['DomainPath']) ){
$data['DomainPath'] = $listener->getDomainPath($domain);
}
}
// ideally would only warn on certain pages, but unsure where to place this logic other than here
// TODO possibly allow bundle to hold errors/warnings as part of its config.
else if( $data['TextDomain'] !== $domain ){
Loco_error_AdminNotices::debug( sprintf("Plugin loaded text domain '%s' but WordPress knows it as '%s'",$domain, $data['TextDomain']) );
}
}
// do initial configuration of bundle from metadata
$bundle->configure( $base, $data );
return $bundle;
}
} src/package/Project.php 0000666 00000055670 15214141737 0011073 0 ustar 00 .pot"
* @var Loco_fs_File
*/
private $pot;
/**
* Whether POT file is protected from end-user update and sync operations.
* @var bool
*/
private $potlock;
/**
* Construct project from its domain and a descriptive name
* @param Loco_package_Bundle
* @param Loco_package_TextDomain
* @param string
*/
public function __construct( Loco_package_Bundle $bundle, Loco_package_TextDomain $domain, $name ){
$this->name = $name;
$this->bundle = $bundle;
$this->domain = $domain;
// take default slug from domain, avoiding wildcard
$slug = $domain->getName();
if( '*' === $slug ){
$slug = '';
}
$this->slug = $slug;
// sources
$this->sfiles = new Loco_fs_FileList;
$this->spaths = new Loco_fs_FileList;
$this->xspaths = new Loco_fs_FileList;
// targets
$this->dpaths = new Loco_fs_FileList;
$this->gpaths = new Loco_fs_FileList;
$this->xdpaths = new Loco_fs_FileList;
// global
$this->xgpaths = new Loco_fs_FileList;
}
/**
* Split project ID into domain and slug.
* null and "" are meaningfully different. "" means deliberately empty slug, whereas null means default
* @param string [.]
* @return string[] [ , ]
*/
public static function splitId( $id ){
$r = preg_split('/(?getSlug();
$domain = (string) $this->getDomain();
if( $slug === $domain ){
return $slug;
}
return addcslashes($domain,'.').'.'.addcslashes($slug,'.');
}
/**
* @return string
*/
public function __toString(){
return (string) $this->name;
}
/**
* Set friendly name of project
* @param string
* @return Loco_package_Project
*/
public function setName( $name ){
$this->name = (string) $name;
return $this;
}
/**
* Set short name of project
* @param string
* @return Loco_package_Project
*/
public function setSlug( $slug ){
$this->slug = (string) $slug;
return $this;
}
/**
* Get friendly name of project, e.g. "Network Admin"
* @return string
*/
public function getName(){
return $this->name;
}
/**
* Get short name of project, e.g. "admin"
* @return string
*/
public function getSlug(){
return $this->slug;
}
/**
* @return Loco_package_TextDomain
*/
public function getDomain(){
return $this->domain;
}
/**
* @return Loco_package_Bundle
*/
public function getBundle(){
return $this->bundle;
}
/**
* Whether project is the default for its domain.
* @return bool
*/
public function isDomainDefault(){
$slug = $this->getSlug();
$name = $this->getDomain()->getName();
// default if slug matches text domain.
// else special case for Core "default" domain which has empty slug
return $slug === $name || ( 'default' === $name && '' === $slug ) || 1 === count($this->bundle);
}
/**
* Add a root path where translation files may live
* @param string | Loco_fs_File
* @return Loco_package_Project
*/
public function addTargetDirectory( $location ){
$this->target = null;
$this->dpaths->add( new Loco_fs_Directory($location) );
return $this;
}
/**
* Add a global search path where translation files may live
* @param string | Loco_fs_Directory
* @return Loco_package_Project
*/
public function addSystemTargetDirectory( $location ){
$this->target = null;
$this->gpaths->add( new Loco_fs_Directory($location) );
return $this;
}
/**
* Get domain paths configured in project
* @return Loco_fs_FileList
*/
public function getConfiguredTargets(){
return $this->dpaths;
}
/**
* Get system paths added to project after configuration
* @return Loco_fs_FileList
*/
public function getSystemTargets(){
return $this->gpaths;
}
/**
* Get all target directory roots including global search paths
* @return Loco_fs_FileList
*/
public function getDomainTargets(){
return $this->getTargetFinder()->getRootDirectories();
}
/**
* Lazy create all searchable domain paths including global directories
* @return Loco_fs_FileFinder
*/
private function getTargetFinder(){
if( ! $this->target ){
$target = new Loco_fs_FileFinder;
$target->setRecursive(false)->group('pot','po','mo');
foreach( $this->dpaths as $path ){
// TODO search need not be recursive if it was the configured DomainPath
// currently no way to know at this point, so recursing by default.
$target->addRoot( (string) $path, true );
}
foreach( $this->gpaths as $path ){
$target->addRoot( (string) $path, false );
}
$this->excludeTargets( $target );
$this->target = $target;
}
return $this->target;
}
/**
* utility excludes current exclude paths from target finder
* @param Loco_fs_FileFinder
* @return Loco_fs_FileFinder
*/
private function excludeTargets( Loco_fs_FileFinder $finder ){
foreach( $this->xdpaths as $file ){
if( $path = realpath( (string) $file ) ){
$finder->exclude( $path );
}
}
foreach( $this->xgpaths as $file ){
if( $path = realpath( (string) $file ) ){
$finder->exclude( $path );
}
}
return $finder;
}
/**
* Check if target file or directory is excluded
* @param Loco_fs_File PO or POT file
* @return bool
*/
private function isTargetExcluded( Loco_fs_File $file ){
return $this->xgpaths->has($file) || $this->xdpaths->has($file);
}
/**
* Add a path for excluding in a recursive target file search
* @param string | Loco_fs_File
* @return Loco_package_Project
*/
public function excludeTargetPath( $path ){
$this->target = null;
$this->xdpaths->add( new Loco_fs_File($path) );
return $this;
}
/**
* Get all paths excluded when searching for targets
* @return Loco_fs_FileList
*/
public function getConfiguredTargetsExcluded(){
return $this->xdpaths;
}
/**
* Lazy create all searchable source paths
* @return Loco_fs_FileFinder
*/
private function getSourceFinder(){
if( ! $this->source ){
$source = new Loco_fs_FileFinder;
// .php extensions configured in plugin options
$conf = Loco_data_Settings::get();
$exts = $conf->php_alias or $exts = array('php');
// Only add .js extensions if enabled
$exts = array_merge( $exts, (array) $conf->jsx_alias );
$source->setRecursive(true)->groupBy($exts);
/* @var $file Loco_fs_File */
foreach( $this->spaths as $file ){
$path = realpath( (string) $file );
if( $path && is_dir($path) ){
$source->addRoot( $path, true );
}
}
$this->excludeSources( $source );
$this->source = $source;
}
return $this->source;
}
/**
* Utility excludes current exclude paths from target finder
* @param Loco_fs_FileFinder
* @return Loco_fs_FileFinder
*/
private function excludeSources( Loco_fs_FileFinder $finder ){
foreach( $this->xspaths as $file ){
if( $path = realpath( (string) $file ) ){
$finder->exclude( $path );
}
}
foreach( $this->xgpaths as $file ){
if( $path = realpath( (string) $file ) ){
$finder->exclude( $path );
}
}
return $finder;
}
/**
* Add a root path where source files may live under for this project
* @param string | Loco_fs_File
* @return Loco_package_Project
*/
public function addSourceDirectory( $location ){
$this->source = null;
$this->spaths->add( new Loco_fs_File($location) );
return $this;
}
/**
* Add Explicit source file to project config
* @param string | Loco_fs_File
* @return Loco_package_Project
*/
public function addSourceFile( $path ){
$this->source = null;
$this->sfiles->add( new Loco_fs_File($path) );
return $this;
}
/**
* Add a file or directory as a source location
* @param string | Loco_fs_File
* @return Loco_package_Project
*/
public function addSourceLocation( $path ){
$file = new Loco_fs_File( $path );
if( $file->isDirectory() ){
$this->addSourceDirectory( $file );
}
else {
$this->addSourceFile( $file );
}
return $this;
}
/**
* Get all source directories and files defined in project
* @return Loco_fs_FileList
*/
public function getConfiguredSources(){
$dynamic = $this->spaths->getArrayCopy();
$statics = $this->sfiles->getArrayCopy();
return new Loco_fs_FileList( array_merge( $dynamic, $statics ) );
}
/**
* Test if bundle has configured source files (even if they're excluded by other rules)
* @return bool
*/
public function hasSourceFiles(){
return count( $this->sfiles ) || count( $this->spaths );
}
/**
* Add a path for excluding in source file search
* @param string | Loco_fs_File
* @return Loco_package_Project
*/
public function excludeSourcePath( $path ){
$this->source = null;
$this->xspaths->add( new Loco_fs_File($path) );
return $this;
}
/**
* Get all paths excluded when searching for sources
* @return Loco_fs_FileList
*/
public function getConfiguredSourcesExcluded(){
return $this->xspaths;
}
/**
* Add a globally excluded location affecting sources and targets
* @param string | Loco_fs_File
* @return Loco_package_Project
*/
public function excludeLocation( $path ){
$this->source = null;
$this->target = null;
$this->xgpaths->add( new Loco_fs_File($path) );
return $this;
}
/**
* Check whether POT file is protected from end-user update and sync operations.
* @return bool
*/
public function isPotLocked(){
return (bool) $this->potlock;
}
/**
* Lock POT file to prevent end-user updates0
* @param bool
* @return Loco_package_Project
*/
public function setPotLock( $locked ){
$this->potlock = (bool) $locked;
return $this;
}
/**
* Get full path to template POT (file)
* @return Loco_fs_File
*/
public function getPot(){
if( ! $this->pot ){
$name = $this->getSlug().'.pot';
if( '.pot' !== $name ){
// find under configured domain paths
$targets = $this->getConfiguredTargets()->copy();
// always permit POT file in the bundle root (i.e. outside domain path)
if( $this->isDomainDefault() && $this->bundle->hasDirectoryPath() ){
$root = $this->bundle->getDirectoryPath();
$targets->add( new Loco_fs_Directory($root) );
// look in alternative language directories if only root is configured
if( 1 === count($targets) ){
foreach( array('languages','language','lang','l10n','i18n') as $d ) {
$alt = new Loco_fs_Directory($root.'/'.$d);
if( ! $this->isTargetExcluded($alt) ){
$targets->add($alt);
}
}
}
}
// pot check is for exact name and not recursive
foreach( $targets as $dir ){
$file = new Loco_fs_File($name);
$file->normalize( $dir->getPath() );
if( $file->exists() && ! $this->isTargetExcluded($file) ){
$this->pot = $file;
break;
}
}
}
}
return $this->pot;
}
/**
* Force the use of a known POT file. This could be a PO file if necessary
* @param Loco_fs_File template POT file
* @return Loco_package_Project
*/
public function setPot( Loco_fs_File $pot ){
$this->pot = $pot;
return $this;
}
/**
* Take a guess at most likely POT file under target locations
* @return Loco_fs_File|null
*/
public function guessPot(){
$slug = $this->getSlug();
if( ! is_string($slug) || '' === $slug ){
$slug = (string) $this->getDomain();
if( '' === $slug ){
$slug = 'default';
}
}
// search only inside bundle for template
$finder = new Loco_fs_FileFinder;
foreach( $this->dpaths as $path ){
$finder->addRoot( (string) $path, true );
}
$this->excludeTargets($finder);
$files = $finder->group('pot','po','mo')->exportGroups();
foreach( array('pot','po') as $ext ){
/* @var $pot Loco_fs_File */
foreach( $files[$ext] as $pot ){
$name = $pot->filename();
// use exact match on project slug if found
if( $slug === $name ){
return $pot;
}
// support unconventional -en_US.
foreach( array('-en_US'=>6, '-en'=>3 ) as $tail => $len ){
if( '-en_US' === substr($name,-$len) && $slug === substr($name,0,-$len) ){
return $pot;
}
}
}
}
// Failed to find correctly named POT file,
// but if a single POT file is found we'll use it.
if( 1 === count($files['pot']) ){
return $files['pot'][0];
}
// Either no POT files are found, or multiple are found.
// if the project is the default in its domain, we can try aliases which may be PO
if( $this->isDomainDefault() ){
$options = Loco_data_Settings::get();
if( $aliases = $options->pot_alias ){
$found = array();
/* @var $pot Loco_fs_File */
foreach( $finder as $pot ){
$priority = array_search( $pot->basename(), $aliases, true );
if( false !== $priority ){
$found[$priority] = $pot;
}
}
if( $found ){
ksort( $found );
return current($found);
}
}
}
// failed to guess POT file
return null;
}
/**
* Get all extractable PHP source files found under all source paths
* @return Loco_fs_FileList
*/
public function findSourceFiles(){
$source = $this->getSourceFinder();
// augment file list from directories unless already done so
if( ! $source->isCached() ){
$crawled = $source->exportGroups();
foreach( $crawled as $ext => $files ){
/* @var Loco_fs_File $file */
foreach( $files as $file ){
$name = $file->filename();
// skip "{name}.min.{ext}" but only if "{name}.{ext}" exists
if( '.min' === substr($name,-4) && file_exists( $file->dirname().'/'.substr($name,0,-4).'.'.$ext ) ){
continue;
}
$this->sfiles->add($file);
}
}
}
return $this->sfiles;
}
/**
* Get all translation files matching project prefix across target directories
* @param string file extension, usually "po" or "mo"
* @return Loco_fs_LocaleFileList
*/
public function findLocaleFiles( $ext ){
$finder = $this->getTargetFinder();
$list = new Loco_fs_LocaleFileList;
$files = $finder->exportGroups();
$prefix = $this->getSlug();
$domain = $this->domain->getName();
$default = $this->isDomainDefault();
/* @var $file Loco_fs_File */
foreach( $files[$ext] as $file ){
$file = new Loco_fs_LocaleFile( $file );
// add file if prefix matches and has a suffix. locale will be validated later
if( $file->getPrefix() === $prefix && $file->getSuffix() ){
$list->addLocalized( $file );
}
// else in some cases a suffix-only file like "el.po" can match
else if( $default && $file->hasSuffixOnly() ){
// theme files under their own directory
if( $file->underThemeDirectory() ){
$list->addLocalized( $file );
}
// check followed links if they were originally under theme dir
else if( ( $link = $finder->getFollowed($file) ) && $link->underThemeDirectory() ){
$list->addLocalized( $file );
}
// WordPress core "default" domain, default project
else if( 'default' === $domain ){
$list->addLocalized( $file );
}
}
}
return $list;
}
/**
* @param string file extension
* @return Loco_fs_FileList
*/
public function findNotLocaleFiles( $ext ){
$list = new Loco_fs_LocaleFileList;
$files = $this->getTargetFinder()->exportGroups();
/* @var $file Loco_fs_LocaleFile */
foreach( $files[$ext] as $file ){
$file = new Loco_fs_LocaleFile( $file );
// add file if it has no locale suffix and is inside the bundle
if( $file->hasPrefixOnly() && ! $file->underGlobalDirectory() ){
$list->add( $file );
}
}
return $list;
}
/**
* Initialize choice of PO file paths for a given locale
* @param Loco_Locale locale to initialize translation files for
* @return Loco_fs_FileList
*/
public function initLocaleFiles( Loco_Locale $locale ){
$slug = $this->getSlug();
$domain = $this->domain->getName();
$default = $this->isDomainDefault();
$suffix = sprintf( '%s.po', $locale );
$prefix = $slug ? sprintf('%s-',$slug) : '';
$choice = new Loco_fs_FileList;
/* @var $dir Loco_fs_Directory */
foreach( $this->getConfiguredTargets() as $dir ){
// theme files under their own directory normally have no file prefix
if( $default && $dir->underThemeDirectory() ){
$path = $dir->getPath().'/'.$suffix;
}
// plugin files are prefixed even in their own directory, so empty prefix here implies incorrect bundle configuration
//else if( $default && ! $prefix && $dir->underPluginDirectory() ){
// $path = $dir->getPath().'/'.$domain.'-'.$suffix;
//}
// all other paths use configured prefix, which may be empty
else {
$path = $dir->getPath().'/'.$prefix.$suffix;
}
$choice->add( new Loco_fs_LocaleFile($path) );
}
/* @var $dir Loco_fs_Directory */
foreach( $this->getSystemTargets() as $dir ){
$path = $dir->getPath();
// themes and plugins under global locations will be loaded by domain, regardless of prefix
if( '/themes' === substr($path,-7) || '/plugins' === substr($path,-8) ){
$path .= '/'.$domain.'-'.$suffix;
}
// all other paths (probably core) use configured prefix, which may be empty
else {
$path .= '/'.$prefix.$suffix;
}
$choice->add( new Loco_fs_LocaleFile($path) );
}
return $choice;
}
/**
* Get newest timestamp of all translation files (includes template, but exclude source files)
* @return int
*/
public function getLastUpdated(){
$t = 0;
$file = $this->getPot();
if( $file && $file->exists() ){
$t = $file->modified();
}
/* @var $file Loco_fs_File */
foreach( $this->findLocaleFiles('po') as $file ){
$t = max( $t, $file->modified() );
}
return $t;
}
} src/package/Inverter.php 0000666 00000015242 15214141737 0011252 0 ustar 00 getFileFinder();
/* @var $project Loco_package_Project */
foreach( $bundle as $project ){
if( $file = $project->getPot() ){
// excluding all extensions in case POT is actually a PO/MO pair
foreach( array('pot','po','mo') as $ext ){
$file = $file->cloneExtension($ext);
if( $path = realpath( $file->getPath() ) ){
$finder->exclude( $path );
}
}
}
foreach( $project->findLocaleFiles('po') as $file ){
if( $path = realpath( $file->getPath() ) ){
$finder->exclude( $path );
}
}
foreach( $project->findLocaleFiles('mo') as $file ){
if( $path = realpath( $file->getPath() ) ){
$finder->exclude( $path );
}
}
}
// Do a deep scan of all files that haven't been seen, or been excluded:
// This will include files in global directories and inside the bundle.
return $finder->setRecursive(true)->followLinks(false)->group('po','mo','pot')->exportGroups();
}
/**
* Compile anything found under bundle root that isn't configured in $known
* @return Loco_package_Bundle
*/
public static function compile( Loco_package_Bundle $bundle ){
$found = self::export($bundle);
// done with original bundle now
$bundle = clone $bundle;
$bundle->clear();
// first iteration groups found files into common locations that should hopefully indicate translation sets
$groups = array();
$templates = array();
$localised = array();
$root = $bundle->getDirectoryPath();
/* @var $list Loco_fs_FileList */
foreach( $found as $ext => $list ){
/* @var $file Loco_fs_LocaleFile */
foreach( $list as $file ){
// printf("Found: %s \n", $file );
// This file is NOT known to be part of a configured project
$dir = $file->getParent();
$key = $dir->getRelativePath( $root );
//
if( ! isset($groups[$key]) ){
$groups[$key] = $dir;
$templates[$key] = array();
$localised[$key] = array();
}
// template should define single set of translations unique by directory and file prefix
if( 'pot' === $ext ){
$slug = $file->filename();
$templates[$key][$slug] = true;
}
// else ideally PO/MO files will correspond to a template by common prefix
else {
$file = new Loco_fs_LocaleFile( $file );
$slug = $file->getPrefix();
if( $file->getLocale()->isValid() ){
$localised[$key][$slug] = true;
}
// else could be some kind of non-standard template
else {
$slug = $file->filename();
$templates[$key][$slug] = true;
}
}
}
}
unset($found);
// next iteration matches collected files together into likely project sets
$unique = array();
/* @var $list Loco_fs_Directory */
foreach( $groups as $key => $dir ){
// pair up all projects that match templates neatly to prefixed files
foreach( $templates[$key] as $slug => $bool ){
if( isset($localised[$key][$slug]) ){
//printf("Perfect match on domain '%s' in %s \n", $slug, $key );
$unique[$key][$slug] = $dir;
// done with this prefectly matched set
$templates[$key][$slug] = null;
$localised[$key][$slug] = null;
}
}
// pair up any unprefixed localised files
if( isset($localised[$key]['']) ){
$slug = 'unknown';
// Match to first (hopefully only) template to establish a slug
foreach( $templates[$key] as $_slug => $bool ){
if( $bool ){
$slug = $_slug;
$templates[$key][$slug] = null;
break; // <- not possible to know how multiple POTs might be paired up
}
}
//printf("Pairing unprefixed files in %s to '%s' \n", $key, $slug );
$unique[$key][$slug] = $dir;
// done with unprefixed localised files in this directory
$localised[$key][''] = null;
}
// add any orphaned translations (those with no template matched)
foreach( $localised[$key] as $slug => $bool ){
if( $bool ){
// printf("Picked up orphoned locales in %s as '%s' \n", $key, $slug );
$unique[$key][$slug] = $dir;
}
}
// add any orphaned templates (those with no localised files matched)
foreach( $templates[$key] as $slug => $bool ){
if( $bool ){
//printf("Picked up orphoned template in %s as '%s' \n", $key, $slug );
$unique[$key][$slug] = $dir;
}
}
}
unset( $groups, $localised, $templates );
// final iteration adds unique projects to bundle
foreach( $unique as $key => $sets ){
foreach( $sets as $slug => $dir ){
$name = ucfirst( strtr( $slug, '-_', ' ' ) );
$domain = new Loco_package_TextDomain( $slug );
$project = $domain->createProject( $bundle, $name );
$project->addTargetDirectory($dir);
$bundle->addProject($project);
}
// TODO how to prevent overlapping sets by adding each other's files to exclude lists
}
return $bundle;
}
} src/package/TextDomain.php 0000666 00000002744 15214141737 0011533 0 ustar 00 name = $name;
}
/**
* @internal
*/
public function __toString(){
return (string) $this->name;
}
/**
* Get name of Text Domain, e.g. "twentyfifteen"
* @return string
*/
public function getName(){
return $this->name;
}
/**
* Create a named project in a given bundle for this Text Domain
* @return Loco_package_Project
*/
public function createProject( Loco_package_Bundle $bundle, $name ){
$proj = new Loco_package_Project( $bundle, $this, $name );
$this[] = $proj;
return $proj;
}
/**
* @return Loco_package_TextDomain
*/
public function setCanonical( $bool ){
$this->canonical = (bool) $bool;
return $this;
}
/**
* @return bool
*/
public function isCanonical(){
return $this->canonical;
}
}
src/package/Theme.php 0000666 00000007051 15214141737 0010515 0 ustar 00 getDirectoryPath() );
$theme = new WP_Theme( $this->getSlug(), $root );
return new Loco_package_Header( $theme );
}
/**
* {@inheritdoc}
*/
public function getMetaTranslatable(){
return array (
'Name' => 'Name of the theme',
'Description' => 'Description of the theme',
'ThemeURI' => 'URI of the theme',
'Author' => 'Author of the theme',
'AuthorURI' => 'Author URI of the theme',
// 'Tags' => 'Tags of the theme',
);
}
/**
* Get parent bundle if theme is a child
* @return Loco_package_Theme
*/
public function getParent(){
return $this->parent;
}
/**
* Create theme bundle definition from WordPress theme handle
*
* @param string short name of theme, e.g. "twentyfifteen"
* @return Loco_package_Plugin
*/
public static function create( $slug, $root = null ){
return self::createFromTheme( wp_get_theme( $slug, $root ) );
}
/**
* Create theme bundle definition from WordPress theme data
*/
public static function createFromTheme( WP_Theme $theme ){
$slug = $theme->get_stylesheet();
$base = $theme->get_stylesheet_directory();
$name = $theme->get('Name') or $name = $slug;
if( ! $theme->exists() ){
throw new Loco_error_Exception('Theme not found: '.$name );
}
$bundle = new Loco_package_Theme( $slug, $name );
// ideally theme has declared its TextDomain
$domain = $theme->get('TextDomain') or
// if not, we can see if the Domain listener has picked it up
$domain = Loco_package_Listener::singleton()->getDomain($slug);
// otherwise we won't try to guess as it results in silent problems when guess is wrong
// ideally theme has declared its DomainPath
$target = $theme->get('DomainPath') or
// if not, we can see if the Domain listener has picked it up
$target = Loco_package_Listener::singleton()->getDomainPath($domain);
// otherwise project will use theme root by default
$bundle->configure( $base, array (
'Name' => $name,
'TextDomain' => $domain,
'DomainPath' => $target,
) );
// parent theme inheritance:
if( $parent = $theme->parent() ){
try {
$bundle->parent = self::createFromTheme($parent);
$bundle->inherit( $bundle->parent );
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add($e);
}
}
// TODO provide hook to modify bundle?
// do_action( 'loco_bundle_configured', $bundle );
return $bundle;
}
} src/package/Listener.php 0000666 00000027506 15214141737 0011247 0 ustar 00 unhook();
self::$singleton = null;
}
}
/**
* Create a singleton listener that we can query from anywhere
* @return Loco_package_Listener
*/
public static function create(){
self::destroy();
self::$singleton = new Loco_package_Listener;
return self::$singleton->clear();
}
/**
* @return Loco_package_Listener
*/
public function clear(){
$this->buffer = array();
$this->themes = array();
$this->plugins = array();
$this->domains = array();
$this->domainPaths = array();
$this->pluginHandles = null;
$this->buffered = false;
$this->globalPaths = array();
foreach( array('WP_LANG_DIR') as $name ){
if( $value = loco_constant($name) ){
$this->globalPaths[$value] = strlen($value);
}
}
return $this;
}
/**
* Early hook listening for active bundles loading their own text domains.
*/
public function on_load_textdomain( $domain, $mofile ){
// echo '
';
}
$this->errors = array();
echo implode("\n", $htmls),"\n";
}
}
/**
* admin_notices action handler.
*/
public function on_admin_notices(){
if( ! $this->inline ){
$this->flush();
}
}
/**
* loco_admin_notices callback.
* Unlike WordPress "admin_notices" this fires from within template layout at the point we want them, hence they are marked as "inline"
*/
public function on_loco_admin_notices(){
$this->inline = true;
$this->flush();
}
/**
* loco_admin_init callback
* When we know a Loco admin controller will render the page we will control the point at which notices are printed
*/
public function on_loco_admin_init(){
$this->inline = true;
}
/**
* @internal
* Make sure we always see notices if hooks didn't fire
*/
public function __destruct(){
$this->inline = false;
if( ! loco_doing_ajax() ){
$this->flush();
}
}
}
src/error/Warning.php 0000666 00000000701 15214141740 0010603 0 ustar 00 context = array( 'themes', $domain, $locale );
unset( $this->lock[$domain] );
return $locale;
}
/**
* `plugin_locale` filter callback.
* Signals the beginning of a "load_plugin_textdomain" process
* @param string
* @param string
* @return string
*/
public function filter_plugin_locale( $locale, $domain = '' ){
$this->context = array( 'plugins', $domain, $locale );
unset( $this->lock[$domain] );
return $locale;
}
/**
* `unload_textdomain` action callback.
* Lets us release lock so that custom file may be loaded again (hopefully for another locale)
* @param string
* @return void
*/
public function on_unload_textdomain( $domain ){
unset( $this->lock[$domain] );
}
/**
* `load_textdomain` action callback.
* Lets us load our custom translations before WordPress loads what it was going to anyway.
* We're deliberately not stopping WordPress loading $mopath, if it exists it will be merged on top of our custom strings.
* @param string
* @param string
* @return void
*/
public function on_load_textdomain( $domain, $mopath ){
$key = '';
// domains may be split into multiple files
$name = pathinfo( $mopath, PATHINFO_FILENAME );
if( $lpos = strrpos( $name, '-') ){
$slug = substr( $name, 0, $lpos );
if( $slug !== $domain ){
$key = $slug;
}
}
// avoid recursion when we've already handled this domain/slug
if( isset($this->lock[$domain][$key]) ){
return;
}
// language roots
$wp_lang_dir = trailingslashit( loco_constant('WP_LANG_DIR') );
$lc_lang_dir = trailingslashit( loco_constant('LOCO_LANG_DIR') );
// if context is set, then a theme or plugin initialized the loading process properly
if( is_array($this->context) ){
list( $subdir, $_domain, $locale ) = $this->context;
$this->context = null;
// It shouldn't be possible to catch a different domain after setting context, but we'd better bail just in case
if( $_domain !== $domain ){
return;
}
$mopath = $lc_lang_dir.$subdir.'/'.$domain.'-'.$locale.'.mo';
}
// else load_textdomain must have been called directly to bypass locale filters
else {
$snip = strlen($wp_lang_dir);
// direct file loads must be under WP_LANG_DIR if we are to map them
if( substr( dirname($mopath).'/', 0, $snip ) === $wp_lang_dir ){
$mopath = substr_replace( $mopath, $lc_lang_dir, 0, $snip );
}
// else no way to map files from WP_LANG_DIR to LOCO_LANG_DIR
else {
return;
}
}
// Load our custom translations avoiding recursion back into this hook
$this->lock[$domain][$key] = true;
load_textdomain( $domain, $mopath );
}
/**
* `load_textdomain_mofile` filter callback
* @param string
* @param string
* @return string
*/
public function filter_load_textdomain_mofile( $mopath, $domain ){
// 2.0.14 changed text domain from "loco" to "loco-translate"
// so if file doesn't exist, there's no harm in trying the legacy file name
if( 'loco-translate' === $domain && ! file_exists($mopath) ){
$mopath = str_replace('/loco-translate-','/loco-',$mopath);
}
return $mopath;
}
} src/hooks/AdminHooks.php 0000666 00000013011 15214141740 0011222 0 ustar 00
Error: Loco Translate failed to start up
';
}
/**
* {@inheritdoc}
*/
public function __construct(){
// renders failure notice if plugin failed to start up admin hooks.
add_action( 'admin_notices', array(__CLASS__,'print_hook_failure') );
// initialize hooks
parent::__construct();
// Ajax router will be called directly in tests
// @codeCoverageIgnoreStart
if( loco_doing_ajax() ){
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
// initialize Ajax router before hook fired so we can handle output buffering
if( 'loco_' === substr($action,0,5) && isset($_REQUEST['route']) ){
$this->router = new Loco_mvc_AjaxRouter;
Loco_package_Listener::create();
}
}
// @codeCoverageIgnoreEnd
// page router required on all pages as it hooks in the menu
else {
$this->router = new Loco_mvc_AdminRouter;
// we don't know we will render a page yet, but we need to listen for text domain hooks as early as possible
if( isset($_GET['page']) && 'loco' === substr($_GET['page'],0,4) ){
Loco_package_Listener::create();
// trigger post-upgrade process if required
$opts = Loco_data_Settings::get();
$opts->migrate();
}
}
}
/**
* "admin_init" callback.
*/
public function on_admin_init(){
// currently no better hook than `admin_init` for adding privacy statement.
// could use "load-tools.php" action, but WordPress could change that in future.
// this should fire just before WP_Privacy_Policy_Content::privacy_policy_guide is called
if( function_exists('wp_add_privacy_policy_content') ) {
$url = apply_filters('loco_external','https://localise.biz/wordpress/plugin/privacy');
wp_add_privacy_policy_content(
__('Loco Translate','loco-translate'),
esc_html( __("This plugin doesn't collect any data from public website visitors.",'loco-translate') ).' '.
sprintf( __('Administrators and auditors may wish to review Loco\'s plugin privacy notice.','loco-translate'), esc_url($url) )
);
}
}
/**
* "admin_menu" callback.
*/
public function on_admin_menu(){
// This earliest we need translations, and admin user locale should be set by now
if( $this->router ){
$domainPath = dirname( loco_plugin_self() ).'/languages';
load_plugin_textdomain( 'loco-translate', false, $domainPath );
}
// Unhook failure notice that would fire if this hook was not successful
remove_action( 'admin_notices', array(__CLASS__,'print_hook_failure') );
}
/**
* plugin_action_links action callback
* @param string[]
* @param string
* @return string[]
*/
public function on_plugin_action_links( $links, $plugin = '' ){
try {
if( $plugin && current_user_can('loco_admin') && Loco_package_Plugin::get_plugin($plugin) ){
// coerce links to array
if( ! is_array($links) ){
$links = $links && is_string($links) ? (array) $links : array();
}
// ok to add "translate" link into meta row
$href = Loco_mvc_AdminRouter::generate('plugin-view', array( 'bundle' => $plugin) );
$links[] = ''.esc_html__('Translate','loco-translate').'';
}
}
catch( Exception $e ){
// $links[] = esc_html( 'Debug: '.$e->getMessage() );
}
return $links;
}
/**
* Purge in-memory caches that may be persisted by object caching plugins
*/
private function purge_wp_cache(){
global $wp_object_cache;
if( function_exists('wp_cache_delete') && method_exists($wp_object_cache,'delete') ){
wp_cache_delete('plugins','loco');
}
}
/**
* pre_update_option_{$option} filter callback for $option = "active_plugins"
* @param array active plugins
* @return array
*/
public function filter_pre_update_option_active_plugins( $value = null ){
$this->purge_wp_cache();
return $value;
}
/**
* pre_update_site_option_{$option} filter callback for $option = "active_sitewide_plugins"
* @param array active sitewide plugins
* @return array
*/
public function filter_pre_update_site_option_active_sitewide_plugins( $value = null ){
$this->purge_wp_cache();
return $value;
}
/**
* deactivate_plugin action callback
*
public function on_deactivate_plugin( $plugin, $network = false ){
if( loco_plugin_self() === $plugin ){
// TODO flush all our transient cache entries
// "DELETE FROM ___ WHERE `option_name` LIKE '_transient_loco_%' OR `option_name` LIKE '_transient_timeout_loco_%'";
}
}*/
/*public function filter_all( $hook ){
error_log( $hook, 0 );
}*/
}
src/hooks/TranslateBuffer.php 0000666 00000004404 15214141740 0012263 0 ustar 00 buffer[$domain][$msgid] = null;
return $msgstr;
}
/**
* `gettext_with_context` filter callback
*/
public function filter_gettext_with_context( $msgstr, $msgid, $msgctxt, $domain ){
$this->buffer[$domain][$msgctxt."\x04".$msgid] = null;
return $msgstr;
}
/**
* `ngettext` filter callback
*/
public function filter_ngettext( $msgstr, $msgid, $msgid_plural, $number, $domain ){
$this->buffer[$domain][$msgid] = null;
return $msgstr;
}
/**
* `ngettext_with_context` filter callback
*/
function filter_ngettext_with_context( $msgstr, $msgid, $msgid_plural, $number, $msgctxt, $domain ){
$this->buffer[$domain][$msgctxt."\x04".$msgid] = null;
return $msgstr;
}
/**
* Export all captured translations in a raw form and reset buffer
* @param string the specific domain listened for
* @return array
*/
public function flush( $domain ){
$export = array();
if( isset($this->buffer[$domain]) ){
// what we captures was just a unique namespace
$captured = $this->buffer[$domain];
unset($this->buffer[$domain]);
// process raw data for all that actually exist
// this survives on WordPress internals not changing :-/
$loaded = get_translations_for_domain($domain);
if( $loaded instanceof Translations && is_array($loaded->entries) ){
$entries = array_intersect_key( $loaded->entries, $captured );
/* @var $entry Translation_Entry */
foreach( $entries as $key => $entry ){
$export[$key] = $entry->translations;
}
}
}
return $export;
}
}
src/hooks/Hookable.php 0000666 00000004541 15214141740 0010722 0 ustar 00 getMethods( ReflectionMethod::IS_PUBLIC ) as $method ){
$func = $method->name;
// support filter_{filter_hook} methods
if( 0 === strpos($func,'filter_' ) ) {
$hook = substr( $func, 7 );
}
// support on_{action_hook} methods
else if( 0 === strpos($func,'on_' ) ){
$hook = substr( $func, 3 );
}
else {
continue;
}
// this goes to 11 so we run after system defaults
$priority = 11;
// support @priority tag in comment block (uncomment if needed)
/*if( ( $docblock = $method->getDocComment() ) && ( $offset = strpos($docblock,'@priority ') ) ){
preg_match( '/^\d+/', substr($docblock,$offset+10), $r ) and
$priority = (int) $r[0];
}*/
// call add_action or add_filter with required arguments and hook is registered
// add_action actually calls add_filter, although unsure how long that's been the case.
$num_args = $method->getNumberOfParameters();
add_filter( $hook, array( $this, $func ), $priority, $num_args );
// register hook for destruction so object can be removed from memory
$reg[] = array( $hook, $func, $priority );
}
$this->hooks = $reg;
}
/**
* Deregister active hooks.
* We can't use __destruct because instances persist in WordPress hook registry
*/
public function unhook(){
if( is_array($this->hooks) ){
foreach( $this->hooks as $r ){
remove_filter( $r[0], array($this,$r[1]), $r[2] );
}
}
$this->hooks = null;
}
} readme.txt 0000666 00000027474 15214141740 0006563 0 ustar 00 === Plugin Name ===
Contributors: timwhitlock
Tags: translation, translators, localization, localisation, l10n, i18n, Gettext, PO, MO, productivity, multilingual, internationalization
Requires at least: 4.1
Requires PHP: 5.2.4
Tested up to: 5.4
Stable tag: 2.3.3
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Translate WordPress plugins and themes directly in your browser
== Description ==
Loco Translate provides in-browser editing of WordPress translation files.
It also provides localization tools for developers, such as extracting strings and generating templates.
Features include:
* Built-in translation editor within WordPress admin
* Create and update language files directly in your theme or plugin
* Extraction of translatable strings from your source code
* Native MO file compilation without the need for Gettext on your system
* Support for PO features including comments, references and plural forms
* PO source view with clickable source code references
* Protected language directory for saving custom translations
* Configurable PO file backups with diff and restore capability
* Built-in WordPress locale codes
Official [Loco](https://localise.biz/) WordPress plugin by Tim Whitlock.
For more information please visit our [plugin page](https://localise.biz/wordpress/plugin).
== Installation ==
= Basic usage: =
Translators: To translate a theme into your language, follow these steps:
1. Create the protected languages directory at `wp-content/languages/loco/themes`
2. Ensure this directory writeable by the web server
3. Find your theme in the list at *Loco Translate > Themes*
4. Click `+ New language` and follow the on-screen prompts.
Developers: To translate your own theme or plugin for distribution, follow these steps:
1. Create a `languages` subdirectory in your bundle’s root directory
2. Ensure this directory writeable by the web server
3. Find the bundle at either *Loco Translate > Themes* or *Loco Translate > Plugins*
4. Click `+ Create template` and follow the on-screen prompts to extract your strings.
5. Click `+ New language` and follow the on-screen prompts to add your own translations.
= Installing manually: =
1. Unzip all files to the `wp-content/plugins/loco-translate` directory
2. Log into WordPress admin and activate the 'Loco Translate' plugin through the 'Plugins' menu
3. Go to *Loco Translate > Home* in the left-hand menu to start translating
More information on using the plugin is [available here](https://localise.biz/wordpress/plugin).
== Frequently Asked Questions ==
= How do I use it? =
Try our [Guides and Tutorials](https://localise.biz/wordpress/plugin#guides).
= How do I get help? =
If you have a problem using Loco Translate, please try our [help pages](https://localise.biz/wordpress/plugin).
There's a lot of information there to help you understand how it works and the most common pitfalls to avoid.
To report a bug please start a new topic in the [support forum](https://wordpress.org/support/plugin/loco-translate),
but please check the [FAQs](https://localise.biz/wordpress/plugin/faqs) for similar issues first.
If you decide to submit a bug report please post enough [relevant detail](https://localise.biz/wordpress/plugin/faqs/debug-info) for us to reproduce your issue.
= Is my data protected? =
We don't collect your data or snoop on you. See the [plugin privacy notice](https://localise.biz/wordpress/plugin/privacy).
== Screenshots ==
1. Translating strings in the browser with the Loco PO Editor
2. Showing translation progress for theme language files
3. PO source view with text filter and clickable file references
4. Restore tab showing PO diff view with revert function
5. Showing access to translations by installed language
== Changelog ==
= 2.3.3 =
* Fixed fatal error when class not found
= 2.3.2 =
* Removed login/email from default Last-Translator credit
* Bumped WP compatibility to 5.4
* Fixed PHP 7.4 deprecations
= 2.3.1 =
* Default POT getter now looks in "lang" directory
* Not calling deprecated magic quotes functions under PHP 7.4
* Fixed issue with conflicting page hooks
* Ajax file uploads now enabled by default
* Removed legacy option migrations from 1.x branch
* Bumped WP compatibility to 5.2.4
= 2.3.0 =
* Added experimental support for multipart uploads
* Added relocation tab for moving translation sets
* Creation of missing directories when writing new files
* Fixed duplicate file addition when iterating over symlink
* Bumped WP compatibility to 5.2.1
= 2.2.2 =
* Security fixes for reading sensitive files
* Fixed old PHP version error in data files
* Bumped WP compatibility to 5.1.1
= 2.2.1 =
* Fixed bug where plural tabs not displaying RTL
* Various improvements to PO parser incl. better charset handling
* Excluding node_modules and vendor directories by default
* Transients now have maximum lifespan of 10 days, refreshed after 24h
* Symlink fix for followed theme paths detected outside theme
* Deprecated config repository lookup
* Bumped WP compatibility to 5.1
= 2.2.0 =
* Fix for empty language code when getting plural rules
* Added X-Loco-Version header to generated Gettext files
* Added sanity check for mbstring.func_overload madness
* Added "Assign template" link on missing template page
* Added JavaScript string extraction (experimental)
* Editor supports sprintf-js when javascript-format tag present
* Fix for duplicate comments when end punctuation differs
* Marking msgctxt more clearly in editor views
* Added `loco_admin_shutdown` action hook
* Bumped WP compatibility to 5.0 (beta)
= 2.1.5 =
* Updated locale data
* Minor fix to file reference resolution
* Fixed windows paths with trailing backslash
* Fixed ssh-keys toggling issue
* Rejigged buffer handling during Ajax
* Bumped WP compatibility to 4.9.8
= 2.1.4 =
* Bumped WP compatibility to 4.9.6
* Hooked in privacy policy suggestion
= 2.1.3 =
* Added loco_locale_name filter and updated locale data
* Fixed editor column sorting to update as values change
* Supporting RTL text in editor preview rows
* Minor refactor of debug mode routing check
* Minor PO parser improvements
* Bumped WP compatibility to 4.9.5
= 2.1.2 =
* Fixed undeclared property in admin hook
* Fixed incompatibility with older WordPress
* Fixed incorrect millisecond reporting in footer
* Removed locale progress column for en_US locale
* Tweaks to debugging and error logging
= 2.1.1 =
* Setting `Project-Id-Version` on new POT files
* Added source view to quick links in file tables
* Supporting only WordPress style locale codes
* Editor screen tolerates missing PO headers
* Ajax debugging improvements for issue reporting
* Added loco_parse_locale action callback
= 2.1.0 =
* Add `fs_protect` setting to avoid overwriting system files
* Fixed bug in connect dialogue where errors not redisplayed
* Minor improvements to inline notices
* Removed downgrade notice under version tab
* Fixed extraction bug where file header confused with comment
* Resolved some inconsistencies between PHP and JS utilities
* Added Restore tab with diff display
* Added `loco_settings` hook
* Prevented editor from changing PO document order
* Added default string sorting to extracted strings
* Added "Languages" section for grouping files by locale
* Fixed bug where translations loaded before user profile language set
* Added loco_locale_plurals filter for customising plural rules
* Allowing PO files to enforce their own Plural-Forms rules
* Added `loco_allow_remote` filter for debugging remote problems
* Updated plural forms from Unicode CLDR
* PHP extractor avoids repeated comments
* Bumped WP compatibility to 4.9.4
= 2.0.17 =
* Unofficial languages showing in “Installed” dropdown
* Fixed extraction bug where comment confused with file header
* Fixed issue where src attributes requested from server during HTML strip
* Added loco_admin_init hook into ajax router for consistency
* Added warning on file info page when file is managed by WordPress
* Minor help link and layout tweaks
* Bumped WP compatibility to 4.9.1
= 2.0.16 =
* File writer observes wp_is_file_mod_allowed
* Fixed progress bug in editor for locales with nplurals=1
* Made plural form categories translatable for editor UI
* Sync-from-source raises warning when files are skipped
* Added hack for extracting from .twig as per .php
* Added warning when child themes declare parent text domain
* Added option to control PO line wrapping
* Bumped WP compatibility to 4.8.2
= 2.0.15 =
* Permanently removed legacy version 1.x
* Fixed bug where editor code view was not redrawn on resize
* Fixed bug where fuzzy flag caused format flag to be ignored
* Fixed bug where autoloader responded to very long class names
* Purging WP object cache when active plugin list changes
* Added experimental source word count into POT info tab
* Bumped WP compatibility to 4.8.1
= 2.0.14 =
* Editor improvements inc. column sorting
* Added warnings that legacy version will be removed
* Added PO source view text filtering
* Added _fs_nonce for 4.7.5 compatibility
* Migrated to canonical text domain
* Removed wp class autoloading
= 2.0.13 =
* CSS conflict fixes
* Added option for UTF-8 byte order mark
* Printf highlighting observes no-php-format flag
* Fixed issue with translator role losing “read” permission
= 2.0.12 =
* Minor fix for root path configs
* Added alternative PHP extensions setting
* Bumped WP version to 4.7.3
* LoadHelper fix for core files
* Allow revoking of permissions from translator role
* Allow network admins to deny access to site admins
= 2.0.11 =
* Extra debug logging and error diagnostics
* Forcefully clear output buffers before Ajax flush
* Bumped WordPress version to 4.7
* Experimental wildcard text domain support
= 2.0.10 =
* Allows missing domain argument in plugin_locale filter
* Reverted editor changes that disabled readonly text
* Added invisibles and coding editor switches
* Added table filtering via text query
* Added Last-Translator user preference
= 2.0.9 =
* Bumped minimum WordPress version to 4.1
* Some optimisation of transient caching
* Fixed hash table settings bug
= 2.0.8 =
* Source refs fix for files in unknown subsets
* Downgrades PO formatting exceptions to PHP warnings
* Renamed function prefixes to avoid PHP 7 warnings
* Better support for php-format and no-php-format flag
* PO source and editor UI tweaks
* Localised strings and implemented in js
= 2.0.7 =
* Fixed prototype.js conflict
* More Windows file path fixes
* Added loco_current_translator filter
* Fixed false positive in extra files test
= 2.0.6 =
* PO wrapping bugfix
* Downgraded source code bugfix
* Tolerating headerless POT files
* Core bundle metadata tweaks
= 2.0.5 =
* Deferred missing tokenizer warning
* Allows editing of files in unconfigured sets
* Added maximum PHP file size for string extraction
* Display of PHP fatal errors during Ajax
= 2.0.4 =
* Reduced session failures to debug notices
* Added wp_roles support for WP < 4.3
* Fixed domain listener bugs
= 2.0.3 =
* Added support for Windows servers
* Removed incomplete config warning on bundle overview
= 2.0.2 =
* Fixed bug when absolute path used to get plugins
* Added loco_plugins_data filter
* Added theme Template Name header extraction
* Minor copy amends
= 2.0.1 =
* Added help link in settings page
* Fixed opendir warnings in legacy code
* Catching session errors during init
* Removing meta row link when plugin not found
= 2.0.0 =
* First release of completely rebuilt version 2
== Upgrade Notice ==
= 2.3.3 =
* Various bug fixes and improvements
== Keyboard shortcuts ==
The PO file editor supports the following keyboard shortcuts for faster translating:
* Done and Next: `Ctrl ↵`
* Next string: `Ctrl ↓`
* Previous string: `Ctrl ↑`
* Next untranslated: `Shift Ctrl ↓`
* Previous untranslated: `Shift Ctrl ↑`
* Copy from source text: `Ctrl B`
* Clear translation: `Ctrl K`
* Toggle Fuzzy: `Ctrl U`
* Save PO / compile MO: `Ctrl S`
* Toggle invisibles: `Shift Ctrl I`
Mac users can use ⌘ Cmd instead of Ctrl.
lib/compiled/gettext.php 0000666 00000142705 15214141740 0011317 0 ustar 00 map = array_combine( array_map( 'strtolower', $keys ), $keys ); parent::__construct($raw); } } public function normalize( $key ){ $k = strtolower($key); return isset($this->map[$k]) ? $this->map[$k] : null; } public function add($key, $val ){ $this->offsetSet( $key, $val ); return $this; } public function __toString(){ $pairs = array(); foreach( $this as $key => $val ){ $pairs[] = $key.': '.$val; } return implode("\n", $pairs ); } public function trimmed($prop ){ return trim( $this->__get($prop) ); } public function has($key ){ $k = strtolower($key); return isset($this->map[$k]); } public function __get($key ){ return $this->offsetGet( $key ); } public function __set($key, $val ){ $this->offsetSet( $key, $val ); } public function offsetExists($k ){ return ! is_null( $this->normalize($k) ); } public function offsetGet($k ){ $k = $this->normalize($k); if( is_null($k) ){ return ''; } return parent::offsetGet($k); } public function offsetSet($key, $v ){ $k = strtolower($key); if( isset($this->map[$k]) && $key !== $this->map[$k] ){ parent::offsetUnset( $this->map[$k] ); } $this->map[$k] = $key; parent::offsetSet( $key, $v ); } public function offsetUnset($key ){ $k = strtolower($key); if( isset($this->map[$k]) ){ parent::offsetUnset( $this->map[$k] ); unset( $this->map[$k] ); } } public function jsonSerialize(){ return $this->getArrayCopy(); } public function keys(){ trigger_error('Is this required?', E_USER_NOTICE); return array_values( $this->map ); } }
function loco_normalize_charset( $cs ){ if( preg_match('/^UTF-?8$/i',$cs) ){ return 'UTF-8'; } $aliases = @mb_encoding_aliases($cs); if( false === $aliases ){ throw new InvalidArgumentException('Unsupported character encoding: '.$cs ); } if( $r = preg_grep('/^ISO[-_]\\d+[-_]\\d+$/i',$aliases) ){ $cs = current($aliases); $cs = strtr( strtoupper($cs), '_', '-' ); } else if( in_array('US-ASCII',$aliases,true) ){ $cs = 'US-ASCII'; } return $cs; }
class LocoPoHeaders extends LocoHeaders { private $cs; public function getCharset(){ $cs = $this->cs; if( is_null($cs) ){ $cs = ''; $raw = $this->offsetGet('content-type'); if( $raw && preg_match('!\\bcharset[= ]+([-\\w]+)!',$raw,$r) ){ try { $cs = loco_normalize_charset($r[1]); } catch( InvalidArgumentException $e ){ $cs = null; } catch( Throwable $e ){ trigger_error( $e->getMessage(), E_USER_NOTICE ); $cs = null; } } $this->cs = $cs; } return $cs; } public function setCharset( $to ){ $to = loco_normalize_charset($to); $from = $this->getCharset(); $this->cs = $to; $this['Content-Type'] = 'text/plain; charset='.$to; if( $from && $from !== $to ){ foreach( $this as $key => $val ){ $this[$key] = mb_convert_encoding($val,$to,$from); } } return $to; } public static function fromMsgstr( $str ){ $headers = new LocoPoHeaders; $key = ''; foreach( preg_split('/[\\r\\n]+/',$str) as $line ){ $i = strpos($line,':'); if( is_int($i) ){ $key = trim( substr($line,0,$i), " \t" ); $headers->offsetSet( $key, ltrim( substr($line,++$i)," \t" ) ); } else if( '' !== $key ){ $headers->offsetSet( $key, $headers->offsetGet($key)."\n".$line ); } } $cs = $headers->getCharset(); if( $cs && 'UTF-8' !== $cs && 'UTF-8' !== mb_detect_encoding($str,array('UTF-8',$cs),true) ){ foreach( $headers as $key => $val ){ $headers[$key] = mb_convert_encoding($val,'UTF-8',array($cs)); } } return $headers; } public static function fromSource( $raw ){ $po = new LocoPoParser($raw); $po->parse(0); return $po->getHeader(); } }
function loco_convert_utf8( $str, $enc ){ if( '' === $enc ){ if( false === preg_match('//u',$str) ){ $str = mb_convert_encoding( $str, 'UTF-8', 'cp1252' ); } } else if( 'UTF-8' === $enc || 'US-ASCII' === $enc ){ if( false === preg_match('//u',$str) ){ throw new Loco_error_ParseException('Bad '.$enc.' encoding'); } } else if( 'ISO-8859-1' === $enc ) { $str = mb_convert_encoding( $str, 'UTF-8', 'cp1252' ); } else { $str = mb_convert_encoding( $str, 'UTF-8', $enc ); } return $str; }
abstract class LocoGettextParser { private $head; private $cs; abstract public function parse( $limit = -1 ); protected function setHeader( LocoPoHeaders $head ){ $this->head = $head; if( $cs = $head->getCharset() ){ if( is_null($this->cs) ){ $this->setCharset($cs); } } return $head; } public function getHeader(){ return $this->head; } protected function setCharset( $cs ){ $this->cs = $cs; } protected function getCharset(){ return $this->cs; } protected function str( $str ){ if( '' !== $str ){ $enc = (string) $this->cs; $str = loco_convert_utf8( $str, $enc ); } return $str; } }
function loco_remove_bom( $s, &$c ){ $bom = substr($s,0,2); if( "\xFF\xFE" === $bom ){ $c = 'UTF-16LE'; return substr($s,2); } if( "\xFE\xFF" === $bom ){ $c = 'UTF-16BE'; return substr($s,2); } if( "\xEF\xBB" === $bom && "\xBF" === $s[2] ){ $c = 'UTF-8'; return substr($s,3); } $c = ''; return $s; }
function loco_parse_reference_id( $refs, &$_id ){ if( false === ( $n = strpos($refs,'loco:') ) ){ $_id = ''; return $refs; } $_id = substr($refs, $n+5, 24 ); $refs = substr_replace( $refs, '', $n, 29 ); return trim( $refs ); }
class LocoPoParser extends LocoGettextParser implements Iterator { private $lines = array(); private $i; private $k; private $m; public function __construct( $src ){ if( '' !== $src ){ $src = loco_remove_bom($src,$cs); if( $cs && 'UTF-8' !== $cs ){ $src = mb_convert_encoding( $src, 'UTF-8', array($cs) ); } if( 'UTF-8' === mb_detect_encoding($src,array('UTF-8','ISO-8859-1'),true) ){ $this->setCharset('UTF-8'); } $this->lines = preg_split('/(\\r\\n?|\\n)/', $src ); } } public function rewind(){ $this->i = -1; $this->k = -1; $this->next(); } public function valid(){ return is_int($this->i); } public function key(){ return $this->k; } public function current(){ return $this->m; } public function next(){ $valid = false; $entry = array( '#' => array(), 'id' => array(null), 'str' => array(null) ); $i = $this->i; while( array_key_exists(++$i,$this->lines) ){ $line = $this->lines[$i]; try { if( '' === $line ){ if( $valid ){ break; } continue; } $c = $line[0]; if( '#' === $c ){ if( $valid ){ $i--; break; } if( '#' === $line ){ continue; } $f = $line[1]; $entry['#'][$f][] = trim( substr( $line, 1+strlen($f) ), "/ \n\r\t" ); } else if( preg_match('/^msg(id(?:_plural)?|ctxt|str(?:\\[(\\d+)])?)[ \\t]*/', $line, $r ) ){ if( isset($r[2]) ){ $key = 'str'; $idx = (int) $r[2]; } else { $key = $r[1]; $idx = 0; } if( $valid && 'str' !== $key && null !== $entry['str'][0] ){ $i--; break; } $snip = strlen($r[0]); if( '"' !== substr($line,$snip,1) ){ throw new Exception('Expected " to follow msg'.$key); } $val = ''; $line = substr($line,$snip); while( true ){ if( '"' === $line || '"' !== substr($line,-1) ){ throw new Exception('Unterminated msg'.$key ); } $val .= substr( $line, 1, -1 ); $j = $i + 1; if( array_key_exists($j,$this->lines) && ( $line = $this->lines[$j] ) && '"' === $line[0] ){ $i = $j; } else { break; } } if( ! $valid ){ $valid = true; } if( 'id_plural' === $key ){ $key = 'id'; $idx = 1; } $entry[$key][$idx] = stripcslashes($val); } else if( preg_match('/^[ \\t]+$/',$line) ){ if( $valid ) { break; } } else if( '"' === $c ){ throw new Exception('String encountered without keyword'); } else { throw new Exception('Junk'); } } catch( Exception $e ){ } } if( $valid ){ ++$this->k; $this->i = $i; $this->m = $entry; } else { $this->i = null; $this->k = null; $this->m = null; } } public function parse( $limit = -1 ){ $this->rewind(); if( ! $this->valid() ){ throw new Loco_error_ParseException('Invalid PO file'); } $i = -1; $assets = array(); $entry = $this->current(); if( '' !== $entry['id'][0] || isset($entry['ctxt']) || is_null($entry['str'][0]) ){ $head = $this->setHeader( new LocoPoHeaders ); } else { $head = $this->setHeader( LocoPoHeaders::fromMsgstr($entry['str'][0]) ); } $lk = $head['X-Loco-Lookup']; while( $this->valid() ){ $entry = $this->current(); $this->next(); $msgid = $entry['id'][0]; if( is_null($msgid) ){ continue; } if( ++$i === $limit ){ return $assets; } $asset = array( 'source' => $this->str( $msgid ), 'target' => $this->str( (string) $entry['str'][0] ), 'context' => null, ); $prev_entry = null; if( isset($entry['ctxt']) ){ $asset['context'] = $this->str( $entry['ctxt'][0] ); } $cmt = $entry['#']; if( isset($cmt[' ']) ){ $asset['comment'] = $this->str( implode("\n", $cmt[' '] ) ); } if( isset($cmt['.']) ){ $asset['notes'] = $this->str( implode("\n", $cmt['.'] ) ); } if( isset($cmt[':']) ){ if( $refs = implode( ' ', $cmt[':'] ) ) { $refs = $this->str($refs); if( $refs = loco_parse_reference_id( $refs, $_id ) ){ $asset['refs'] = $refs; } if( $_id ){ $asset['_id'] = $_id; } } } if( isset($cmt[',']) ){ foreach( $cmt[','] as $flags ){ foreach( explode(',',$flags) as $flag ){ if( $flag = trim($flag," \t") ){ if( preg_match('/^((?:no-)?\w+)-format/', $flag, $r ) ){ $asset['format'] = $r[1]; } else if( 'fuzzy' === $flag ){ $asset['flag'] = 4; } } } } } if( isset($cmt['|']) ){ $p = new LocoPoParser(''); $p->lines = $cmt['|']; $p->setCharset( $this->getCharset() ); try { $prev_entry = $p->parse(); } catch( Loco_error_ParseException $e ){ } if( $prev_entry ){ $msgid = $prev_entry[0]['source']; if( $lk ){ $asset[$lk] = $asset['source']; $asset['source'] = $msgid; } else if( 'loco:' === substr($msgid,0,5) ){ $asset['_id'] = substr($msgid,5); } else { $asset['prev'] = $prev_entry; } } else { $prev_entry = null; } } $assets[] = $asset; if( isset($entry['id'][1]) ){ $idx = 0; $pidx = count($assets) - 1; $num = max( 2, count($entry['str']) ); while( ++$idx < $num ){ $plural = array( 'source' => '', 'target' => isset($entry['str'][$idx]) ? $this->str($entry['str'][$idx]) : '', 'plural' => $idx, 'parent' => $pidx, ); if( 1 === $idx ){ $plural['source'] = $this->str($entry['id'][1]); if( $lk && is_array($prev_entry) && isset($prev_entry[1]) ){ $plural[$lk] = $plural['source']; $plural['source'] = $prev_entry[1]['source']; } } if( isset($asset['flag']) ){ $plural['flag'] = $asset['flag']; } $assets[] = $plural; } } } if( -1 === $i ){ throw new Loco_error_ParseException('Invalid PO file'); } else if( 0 === $i && '' === $assets[0]['source'] && '' === $assets[0]['target'] ){ throw new Loco_error_ParseException('Invalid PO file' ); } return $assets; } }
class LocoMoParser extends LocoGettextParser { private $bin; private $be; private $n; private $o; private $t; private $v; public function __construct( $bin ){ $this->bin = $bin; } public function getAt( $idx ){ $offset = $this->targetOffset(); $offset += ( $idx * 8 ); $len = $this->integerAt( $offset ); $idx = $this->integerAt( $offset + 4 ); $txt = $this->bytes( $idx, $len ); if( false === strpos( $txt, "\0") ){ return $txt; } return explode( "\0", $txt ); } public function parse( $limit = -1 ){ $i = -1; $r = array(); $sourceOffset = $this->sourceOffset(); $targetOffset = $this->targetOffset(); $soffset = $sourceOffset; $toffset = $targetOffset; while( $soffset < $targetOffset ){ $len = $this->integerAt( $soffset ); $idx = $this->integerAt( $soffset + 4 ); $src = $this->bytes( $idx, $len ); $eot = strpos( $src, "\x04" ); if( false === $eot ){ $context = null; } else { $context = $this->str( substr($src, 0, $eot ) ); $src = substr( $src, $eot+1 ); } $sources = explode( "\0", $src, 2 ); $len = $this->integerAt( $toffset ); $idx = $this->integerAt( $toffset + 4 ); $targets = explode( "\0", $this->bytes( $idx, $len ) ); if( -1 === $i && '' === $sources[0] && is_null($context) ){ $this->setHeader( LocoPoHeaders::fromMsgstr($targets[0]) ); } if( ++$i === $limit ){ break; } $r[$i] = array( 'source' => $this->str( $sources[0] ), 'target' => $this->str( $targets[0] ), 'context' => $context, ); if( isset($sources[1]) ){ $p = count($r) - 1; $nforms = max( 2, count($targets) ); for( $i = 1; $i < $nforms; $i++ ){ $r[] = array( 'source' => isset($sources[$i]) ? $this->str( $sources[$i] ) : sprintf('%s (plural %u)',$r[$p]['source'],$i), 'target' => isset($targets[$i]) ? $this->str( $targets[$i] ) : '', 'parent' => $p, 'plural' => $i, ); } } $soffset += 8; $toffset += 8; } return $r; } public function isBigendian(){ if( is_null($this->be) ){ $str = $this->words( 0, 1 ); if( "\xDE\x12\x04\x95" === $str ){ $this->be = false; } else if( "\x95\x04\x12\xDE" === $str ){ $this->be = true; } else { throw new Loco_error_ParseException('Invalid MO format'); } } return $this->be; } public function version(){ if( is_null($this->v) ){ $this->v = $this->integerWord(1); } return $this->v; } public function count(){ if( is_null($this->n) ){ $this->n = $this->integerWord(2); } return $this->n; } public function sourceOffset(){ if( is_null($this->o) ){ $this->o = $this->integerWord(3); } return $this->o; } public function targetOffset(){ if( is_null($this->t) ){ $this->t = $this->integerWord(4); } return $this->t; } public function getHashTable(){ $s = $this->integerWord(5); $h = $this->integerWord(6); return $this->bytes( $h, $s * 4 ); } private function bytes( $offset, $length ){ $s = substr( $this->bin, $offset, $length ); if( strlen($s) !== $length ){ throw new Loco_error_ParseException('Failed to read '.$length.' bytes at ['.$offset.']' ); } return $s; } private function words( $offset, $length ){ return $this->bytes( $offset * 4, $length * 4 ); } private function integerWord( $offset ){ return $this->integerAt( $offset * 4 ); } private function integerAt( $offset ){ $str = $this->bytes( $offset, 4 ); $fmt = $this->isBigendian() ? 'N' : 'V'; $arr = unpack( $fmt, $str ); if( ! isset($arr[1]) || ! is_int($arr[1]) ){ throw new Loco_error_ParseException('Failed to read integer at byte '.$offset); } return $arr[1]; } }
abstract class LocoPo { public static function pair( $key, $text, $width = 79, $eol = "\n", $esc = '\\n' ){ if( '' === $text ){ return $key.' ""'; } $text = addcslashes( $text, "\t\x0B\x0C\x07\x08\\\"" ); if( $esc ) { $text = preg_replace('/(?:\\r\\n?|\\n)/', $esc.$eol, $text, -1, $nbr ); } else { $eol = "\n"; $text = preg_replace_callback('/(?:\\r\\n?|\\n)/',array(__CLASS__,'replace_br'), $text, -1, $nbr ); } if( $nbr ){ } else if( $width && $width < mb_strlen($text,'UTF-8') + strlen($key) + 3 ){ } else { return $key.' "'.$text.'"'; } $lines = array( $key.' "' ); if( $width ){ $width -= 2; $a = '/^.{0,'.($width-1).'}[-– \\.,:;\\?!\\)\\]\\}\\>]/u'; $b = '/^[^-– \\.,:;\\?!\\)\\]\\}\\>]+/u'; foreach( explode($eol,$text) as $unwrapped ){ $length = mb_strlen( $unwrapped, 'UTF-8' ); while( $length > $width ){ if( preg_match( $a, $unwrapped, $r ) ){ $line = $r[0]; } else if( preg_match( $b, $unwrapped, $r ) ){ $line = $r[0]; } else { throw new Exception('Wrapping error'); } $lines[] = $line; $trunc = mb_strlen($line,'UTF-8'); $length -= $trunc; $unwrapped = (string) substr( $unwrapped, strlen($line) ); if( ( '' === $unwrapped && 0 !== $length ) || ( 0 === $length && '' !== $unwrapped ) ){ throw new Exception('Truncation error'); } } if( 0 !== $length ){ $lines[] = $unwrapped; } } } else { foreach( explode($eol,$text) as $unwrapped ){ $lines[] = $unwrapped; } } return implode('"'.$eol.'"',$lines).'"'; } private static function replace_br( array $r ){ return addcslashes($r[0],"\r\n")."\n"; } public static function refs( $text, $width = 76, $eol = "\n" ){ $text = preg_replace('/\\s+/u', ' ', $text ); if( $width ){ $text = wordwrap( $text, $width, $eol.'#: ', false ); } return '#: '.$text; } public static function prefix( $text, $prefix, $eol = "\n" ){ $lines = preg_split('/\\R/u', $text, -1 ); return $prefix.implode( $eol.$prefix, $lines ); } }
class LocoPoIterator implements Iterator, Countable { private $po; private $headers; private $i; private $t; private $j; private $z; private $w = 79; public function __construct( $po ){ $this->po = $po; $this->t = count( $po ); if( ! isset($po[0]) ){ throw new InvalidArgumentException('Empty PO data'); } $h = $po[0]; if( '' === $h['source'] && empty($h['context']) ){ $this->z = 0; } else { $this->z = -1; } } public function count(){ return $this->t - ( $this->z + 1 ); } public function wrap( $width ){ $width = (int) $width; if( $width > 0 ){ $this->w = max( 15, $width ); } else { $this->w = 0; } return $this; } public function rewind(){ $this->i = $this->z; $this->j = -1; $this->next(); } public function key(){ return $this->j; } public function valid(){ return is_int($this->i); } public function next(){ $i = $this->i; while( ++$i < $this->t ){ $this->j++; $this->i = $i; return; } $this->i = null; $this->j = null; } public function current(){ $i = $this->i; $po = $this->po; $parent = new LocoPoMessage( $po[$i] ); $plurals = array(); while( isset($po[++$i]['parent']) ){ $this->i = $i; $plurals[] = new LocoPoMessage( $po[$i] ); } if( $plurals ){ $parent['plurals'] = $plurals; } return $parent; } public function getArrayCopy(){ $po = $this->po; if( 0 === $this->z ){ $po[0]['target'] = (string) $this->getHeaders(); } return $po; } public function getHeaders(){ if( ! $this->headers ){ $header = $this->po[0]; if( 0 === $this->z ){ $this->headers = LocoPoHeaders::fromMsgstr( $header['target'] ); } else { $this->headers = new LocoPoHeaders; } } return $this->headers; } public function initPo(){ if( 0 === $this->z ){ unset( $this->po[0]['flag'] ); } return $this; } public function initPot(){ if( 0 === $this->z ){ $this->po[0]['flag'] = 4; } return $this; } public function strip(){ $po = $this->po; $i = count($po); $z = $this->z; while( --$i > $z ){ $po[$i]['target'] = ''; } $this->po = $po; return $this; } public function __toString(){ try { return $this->render(); } catch( Exception $e ){ trigger_error( $e->getMessage(), E_USER_WARNING ); return ''; } } public function render( $sorter = null ){ $width = $this->w; $ref_width = max( 0, $width - 3 ); if( 0 === $this->z ){ $h = $this->po[0]; } else { $h = array( 'source' => '' ); } $h['target'] = (string) $this->getHeaders(); $msg = new LocoPoMessage( $h ); $s = $msg->render( $width, $ref_width ); if( $sorter ){ if( ! is_callable($sorter) ){ throw new InvalidArgumentException('Bad callback'); } $msgs = array(); foreach( $this as $msg ){ $msgs[] = $msg; } usort( $msgs, $sorter ); } else { $msgs = $this; } foreach( $msgs as $msg ){ $s .= "\n".$msg->render( $width, $ref_width ); } return $s; } public function exportRefs( $grep = '' ){ $a = array(); if( '' === $grep ) { $grep = '/(\\S+):\\d+/'; } else { $grep = '/(\\S*'.$grep.'):\\d+/'; } foreach( $this as $message ){ if( preg_match_all( $grep, (string) $message->refs, $r ) ){ foreach( $r[1] as $ref ) { $a[$ref][] = $message; } } } return $a; } public function splitRefs( array $map = null ){ $a = array(); foreach( $this as $message ){ $refs = ltrim( (string) $message->refs ); if( '' !== $refs ){ if( preg_match_all('/\\S+\\.([a-z]+):\\d+/', $refs, $r, PREG_SET_ORDER ) ){ $tmp = array(); foreach( $r as $rr ) { list( $ref, $ext ) = $rr; $tmp[$ext][$ref] = true; } foreach( $tmp as $ext => $refs ){ if( is_null($map) ){ } else if( isset($map[$ext]) ){ $ext = $map[$ext]; } else { continue; } $message = clone $message; $message['refs'] = implode(' ',array_keys($refs) ); $a[$ext][] = $message; } } } } return $a; } public function getHashes(){ $a = array(); foreach( $this as $msg ){ $a[] = $msg->getHash(); } sort( $a, SORT_STRING ); return $a; } public function equalSource( LocoPoIterator $that ){ $a = $this->getHashes(); $b = $that->getHashes(); if( count($a) !== count($b) ){ return false; } foreach( $a as $i => $hash ){ if( $hash !== $b[$i] ){ return false; } } return true; } public function sort( $func = null ){ $order = array(); foreach( $this as $msg ){ $order[] = $msg; } if( is_null($func) ){ $func = array( __CLASS__, 'compare' ); } else if( ! is_callable($func) ){ throw new InvalidArgumentException('Bad callback'); } usort( $order, $func ); $po = array(); if( 0 === $this->z ){ $po[] = $this->po[0]; } foreach( $order as $msg ){ $po[] = $msg->getArrayCopy(); if( is_array( $plurals = $msg->plurals ) ){ $index = count($po) - 1; unset( $po[$index]['plurals'] ); foreach( $plurals as $p ){ $a = $p->getArrayCopy(); $a['parent'] = $index; $po[] = $a; } } } $this->po = $po; return $this; } public static function compare( LocoPoMessage $a, LocoPoMessage $b ){ $h = $a->getHash(); $j = $b->getHash(); $n = strcasecmp( $h, $j ); if( 0 === $n ){ $n = strcmp( $h, $j ); if( 0 === $n ){ return 0; } } return $n > 0 ? 1 : -1; } public function createSorter(){ $index = array(); foreach( $this as $i => $msg ){ $index[ $msg->getHash() ] = $i; } $obj = new LocoPoIndex( $index ); return array( $obj, 'compare' ); } }
class LocoPoIndex extends ArrayIterator { public function compare( LocoPoMessage $a, LocoPoMessage $b ){ $h = $a->getHash(); if( ! isset($this[$h]) ){ return 1; } $j = $b->getHash(); if( ! isset($this[$j]) ){ return -1; } return $this[$h] > $this[$j] ? 1 : -1; } }
class LocoPoMessage extends ArrayObject { public function __construct( array $r ){ $r['key'] = $r['source']; parent::__construct($r); } public function __get( $prop ){ return isset($this[$prop]) ? $this[$prop] : null; } private function _getFlags(){ $flags = array(); $plurals = $this->__get('plurals'); if( 4 === $this->__get('flag') ){ $flags[] = 'fuzzy'; } else if( $plurals ){ foreach( $plurals as $child ){ if( 4 === $child->__get('flag') ){ $flags[] = 'fuzzy'; break; } } } if( $f = $this->__get('format') ){ $flags[] = $f.'-format'; } else if( isset($plurals[0]) && ( $f = $plurals[0]->format ) ){ $flags[] = $f.'-format'; } return $flags; } public function getHash(){ $hash = $this->getKey(); if( isset($this['plurals']) ){ foreach( $this['plurals'] as $p ){ $hash .= "\0".$p->getHash(); break; } } return $hash; } public function getKey(){ $msgid = (string) $this['source']; $msgctxt = (string) $this->__get('context'); if( '' !== $msgctxt ){ if( '' === $msgid ){ $msgid = '('.$msgctxt.')'; } $msgid = $msgctxt."\4".$msgid; } return $msgid; } public function getMsgstrs(){ $a = array( $this['target'] ); $plurals = $this->__get('plurals'); if( is_array($plurals) ){ foreach( $plurals as $i => $p ){ $a[] = $p['target']; } } return $a; } public function __toString(){ return $this->render( 79, 76 ); } public function render( $width, $ref_width ){ $s = ''; try { if( $text = $this->__get('comment') ) { $s .= LocoPo::prefix( $text, '# ')."\n"; } if( $text = $this->__get('notes') ) { $s .= LocoPo::prefix( $text, '#. ')."\n"; } if( $text = $this->__get('refs') ){ $s .= LocoPo::refs( $text, $ref_width )."\n"; } if( $texts = $this->_getFlags() ){ $s .= '#, '.implode(', ',$texts)."\n"; } $text = $this->__get('context'); if( is_string($text) && '' !== $text ){ $s .= LocoPo::pair('msgctxt', $text, $width )."\n"; } $s .= LocoPo::pair( 'msgid', $this['key'], $width )."\n"; $target = $this['target']; if( is_array( $plurals = $this->__get('plurals') ) ){ if( $plurals ){ foreach( $plurals as $i => $p ){ if( 0 === $i ){ $s .= LocoPo::pair('msgid_plural', $p['key'], $width )."\n"; $s .= LocoPo::pair('msgstr[0]', $target, $width )."\n"; } $s .= LocoPo::pair('msgstr['.(++$i).']', $p['target'], $width )."\n"; } } else if( isset($this['plural_key']) ){ $s .= LocoPo::pair('msgid_plural', $this['plural_key'], $width )."\n"; $s .= LocoPo::pair('msgstr[0]', $target, $width )."\n"; } else { trigger_error('Missing plural_key in zero plural export'); $s .= LocoPo::pair('msgstr', $target, $width )."\n"; } } else { $s .= LocoPo::pair('msgstr', $target, $width )."\n"; } } catch( Exception $e ){ trigger_error( $e->getMessage(), E_USER_WARNING ); } return $s; } }
class LocoMoTable { private $size = 0; private $bin = ''; private $map; public function __construct( $data = null ){ if( is_array($data) ){ $this->compile( $data ); } else if( $data ){ $this->parse( $data ); } } public function count(){ if( ! isset($this->size) ){ if( $this->bin ){ $this->size = (int) ( strlen( $this->bin ) / 4 ); } else if( is_array($this->map) ){ $this->size = count($this->map); } else { return 0; } if( ! self::is_prime($this->size) || $this->size < 3 ){ throw new Exception('Size expected to be prime number above 2, got '.$this->size); } } return $this->size; } public function bytes(){ return $this->count() * 4; } public function __toString(){ return $this->bin; } public function export(){ if( ! is_array($this->map) ){ $this->parse( $this->bin ); } return $this->map; } private function reset( $length ){ $this->size = max( 3, self::next_prime ( $length * 4 / 3 ) ); $this->bin = null; $this->map = array(); return $this->size; } public function compile( array $msgids ){ $hash_tab_size = $this->reset( count($msgids) ); $packed = array_fill( 0, $hash_tab_size, "\0\0\0\0" ); $j = 0; foreach( $msgids as $msgid ){ $hash_val = self::hashpjw( $msgid ); $idx = $hash_val % $hash_tab_size; if( array_key_exists($idx, $this->map) ){ $incr = 1 + ( $hash_val % ( $hash_tab_size - 2 ) ); do { $idx += $incr; if( $hash_val === $idx ){ throw new Exception('Unable to find empty slot in hash table'); } $idx %= $hash_tab_size; } while( array_key_exists($idx, $this->map ) ); } $this->map[$idx] = $j; $packed[$idx] = pack('V', ++$j ); } return $this->bin = implode('',$packed); } public function lookup( $msgid, array $msgids ){ $hash_val = self::hashpjw( $msgid ); $idx = $hash_val % $this->size; $incr = 1 + ( $hash_val % ( $this->size - 2 ) ); while( true ){ if( ! array_key_exists($idx, $this->map) ){ break; } $j = $this->map[$idx]; if( isset($msgids[$j]) && $msgid === $msgids[$j] ){ return $j; } $idx += $incr; if( $idx === $hash_val ){ break; } $idx %= $this->size; } return -1; } public function parse( $bin ){ $this->bin = (string) $bin; $this->size = null; $hash_tab_size = $this->count(); $this->map = array(); $idx = -1; $byte = 0; while( ++$idx < $hash_tab_size ){ $word = substr( $this->bin, $byte, 4 ); if( "\0\0\0\0" !== $word ){ list(,$j) = unpack('V', $word ); $this->map[$idx] = $j - 1; } $byte += 4; } return $this->map; } public static function hashpjw( $str ){ $i = -1; $hval = 0; $len = strlen($str); while( ++$i < $len ){ $ord = ord( substr($str,$i,1) ); $hval = ( $hval << 4 ) + $ord; $g = $hval & 0xf0000000; if( $g !== 0 ){ $hval ^= $g >> 24; $hval ^= $g; } } return $hval; } private static function next_prime( $seed ){ $seed |= 1; while ( ! self::is_prime($seed) ){ $seed += 2; } return $seed; } private static function is_prime( $num ) { if ($num === 1 ){ return false; } if( $num === 2 ){ return true; } if( $num % 2 == 0 ) { return false; } for( $i = 3; $i <= ceil(sqrt($num)); $i = $i + 2) { if($num % $i == 0 ){ return false; } } return true; } }
class LocoMo { private $bin; private $msgs; private $head; private $hash; private $use_fuzzy = false; private $cs; public function __construct( Iterator $export, LocoPoHeaders $head = null ){ if( $head ){ $this->head = $head; } else { $this->head = new LocoPoHeaders(array('Project-Id-Version' => 'Loco')); } $this->msgs = $export; $this->bin = ''; } public function setCharset( $cs ){ $cs = $this->head->setCharset($cs); $this->cs = 'UTF-8' === $cs ? null : $cs; } public function enableHash(){ return $this->hash = new LocoMoTable; } public function useFuzzy(){ $this->use_fuzzy = true; } public function setHeader( $key, $val ){ $this->head->add($key, $val); return $this; } private function str( $s ){ $s = (string) $s; if( $cs = $this->cs ){ $s = mb_convert_encoding($s,$cs,array('UTF-8')); } return $s; } public function compile(){ $table = array(''); $sources = array(''); $targets = array( (string) $this->head ); $fuzzy_flag = 4; $skip_fuzzy = ! $this->use_fuzzy; foreach( $this->msgs as $r ){ if( $skip_fuzzy && isset($r['flag']) && $fuzzy_flag === $r['flag'] ){ continue; } $msgid = $this->str( $r['key'] ); if( isset($r['context']) ){ $msgctxt = $this->str( $r['context'] ); if( '' !== $msgctxt ){ if( '' === $msgid ){ $msgid = '('.$msgctxt.')'; } $msgid = $msgctxt."\x04".$msgid; } } if( '' === $msgid ){ continue; } $msgstr = $this->str( $r['target'] ); if( '' === $msgstr ){ continue; } $table[] = $msgid; if( isset($r['plurals']) ){ foreach( $r['plurals'] as $i => $p ){ if( $i === 0 ){ $msgid .= "\0".$p['key']; } $msgstr .= "\0".$p['target']; } } $sources[] = $msgid; $targets[] = $msgstr; } asort( $sources, SORT_STRING ); $this->bin = "\xDE\x12\x04\x95\x00\x00\x00\x00"; $n = count($sources); $this->writeInteger( $n ); $offset = 28; $this->writeInteger( $offset ); $offset += $n * 8; $this->writeInteger( $offset ); if( $this->hash ){ sort( $table, SORT_STRING ); $this->hash->compile( $table ); $s = $this->hash->count(); } else { $s = 0; } $this->writeInteger( $s ); $offset += $n * 8; $this->writeInteger( $offset ); if( $s ){ $offset += $s * 4; } $source = ''; foreach( $sources as $i => $str ){ $source .= $str."\0"; $this->writeInteger( $strlen = strlen($str) ); $this->writeInteger( $offset ); $offset += $strlen + 1; } $target = ''; foreach( array_keys($sources) as $i ){ $str = $targets[$i]; $target .= $str."\0"; $this->writeInteger( $strlen = strlen($str) ); $this->writeInteger( $offset ); $offset += $strlen + 1; } if( $this->hash ){ $this->bin .= $this->hash->__toString(); } $this->bin .= $source; $this->bin .= $target; return $this->bin; } private function writeInteger( $num ){ $this->bin .= pack( 'V', $num ); return $this; } }
interface LocoTokensInterface extends Iterator { public function advance(); public function ignore( array $a ); }
class LocoTokenizer implements LocoTokensInterface { const T_LITERAL = 0; const T_UNKNOWN = -1; private $src; private $pos; private $line; private $col; private $max; private $rules = array(); private $skip = array(); private $tok; private $len; public function __construct( $src = '' ){ $this->init($src); } public function parse( $src ){ $tokens = array(); $this->init($src); while( $tok = $this->advance() ){ $tokens[] = $tok; } return $tokens; } public function init( $src ){ $this->src = $src; $this->rewind(); return $this; } public function define( $grep, $t = 0 ){ if('^' !== $grep[1] ){ throw new InvalidArgumentException('Expression '.$grep.' isn\'t anchored'); } if( ! is_int($t) && ! is_callable($t) ){ throw new InvalidArgumentException('Non-integer token must be valid callback'); } $sniff = $grep[2]; if( $sniff === preg_quote($sniff,$grep[0]) ){ $this->rules[$sniff][] = array( $grep, $t ); } else { $this->rules[''][] = array( $grep, $t ); } return $this; } public function ignore( array $a ){ foreach( $a as $t ){ $this->skip[$t] = true; } return $this; } public function current(){ return $this->tok; } public function advance(){ $tok = $this->current(); $this->next(); return $tok; } public function next(){ $tok = null; $offset = $this->pos; $column = $this->col; $line = $this->line; while( $offset <= $this->max ){ $t = null; $s = ''; $text = substr($this->src,$offset); foreach( array($text[0],'') as $k ){ if( isset($this->rules[$k]) ) { foreach( $this->rules[$k] as $rule) { if( preg_match($rule[0], $text, $match ) ) { $s = $match[0]; $t = $rule[1]; if( ! is_int($t) ) { $t = call_user_func( $t, $s, $match ); } break 2; } } } } if( is_null($t) ){ $n = preg_match('/^./u',$text,$match); if( false === $n ){ $s = $text[0]; $match = array( mb_convert_encoding($s,'UTF-8','cp1252') ); } $s = (string) $match[0]; $t = self::T_UNKNOWN; } $length = strlen($s); if( 0 === $length ){ throw new Loco_error_ParseException('Failed to match anything'); } $offset += $length; $lines = preg_split('/\\r?\\n/',$s); $nlines = count($lines); if( $nlines > 1 ){ $next_line = $line + ( $nlines - 1 ); $next_column = strlen( end($lines) ); } else { $next_line = $line; $next_column = $column + $length; } if( array_key_exists($t,$this->skip) ){ $line = $next_line; $column = $next_column; continue; } $tok = self::T_LITERAL === $t ? $s : array( $t, $s, $line, $column ); $line = $next_line; $column = $next_column; $this->len++; break; } $this->tok = $tok; $this->pos = $offset; $this->col = $column; $this->line = $line; } public function key(){ return $this->len ? $this->len-1 : null; } public function valid(){ return null !== $this->tok; } public function rewind(){ $this->len = 0; $this->pos = 0; $this->col = 0; $this->line = 1; $this->max = strlen($this->src) - 1; $this->next(); } }
function loco_utf8_chr( $u ){ if( $u < 0x80 ){ if( $u < 0 ){ throw new RangeException( sprintf('%d is out of Unicode range', $u ) ); } return chr($u); } if( $u < 0x800 ) { return chr( ($u>>6) & 0x1F | 0xC0 ).chr( $u & 0x3F | 0x80 ); } if( $u < 0x10000 ) { return chr( $u>>12 & 15 | 0xE0 ).chr( $u>>6 & 0x3F | 0x80 ).chr( $u & 0x3F | 0x80 ); } if( $u < 0x110000 ) { return chr( $u>>18 & 7 | 0xF0 ).chr( $u>>12 & 0x3F | 0x80 ).chr( $u>>6 & 0x3F | 0x80 ).chr( $u & 0x3F | 0x80 ); } throw new RangeException( sprintf('\\x%X is out of Unicode range', $u ) ); }
function loco_resolve_surrogates( $s ){ return preg_replace_callback('/\\xED([\\xA0-\\xAF])([\\x80-\\xBF])\\xED([\\xB0-\\xBF])([\\x80-\\xBF])/', '_loco_resolve_surrogates', $s ); }
function _loco_resolve_surrogates( array $r ){ return loco_utf8_chr ( ( ( ( ( 832 | ( ord($r[1]) & 0x3F ) ) << 6 ) | ( ord($r[2]) & 0x3F ) ) - 0xD800 ) * 0x400 + ( ( ( ( 832 | ( ord($r[3]) & 0x3F ) ) << 6 ) | ( ord($r[4]) & 0x3F ) ) - 0xDC00 ) + 0x10000 ); }
class LocoEscapeParser { private $map; private $grep; public function __construct( array $map = array() ){ $this->map = $map; $rules = array('\\\\'); if( $map ){ $rules[] = '['.implode(array_keys($map)).']'; } if( ! isset($map['U']) ) { $rules[] = 'U[0-9A-Fa-f]{5,8}'; } if( ! isset($map['u']) ) { $rules[] = 'u(?:\\{[0-9A-Fa-f]+\\}|[0-9A-Fa-f]{1,4})(?:\\\\u(?:\\{[0-9A-Fa-f]+\\}|[0-9A-Fa-f]{1,4}))*'; } $this->grep = '/\\\\('.implode('|',$rules).')/'; } public function unescape( $s ){ if( '' !== $s ) { return $this->stripSlashes( preg_replace_callback($this->grep, array($this, 'unescapeMatch'), $s) ); } return ''; } public function unescapeMatch( array $r ){ $s = $r[0]; $c = $s[1]; if( isset($this->map[$c]) ){ return $this->map[$c]; } if( 'u' === $c ){ $str = ''; $surrogates = false; foreach( explode('\\u',$s) as $i => $h ){ if( '' !== $h ){ $h = ltrim( trim($h,'{}'),'0'); $u = intval($h,16); $str.= loco_utf8_chr($u); if( ! $surrogates ){ $surrogates = $u >= 0xD800 && $u <= 0xDBFF; } } } if( $surrogates ){ $str = loco_resolve_surrogates($str); } return $str; } if( 'U' === $c ){ return loco_utf8_chr( intval(substr($s,2),16) ); } if( 'x' === $c ){ return chr( intval(substr($s,2),16) ); } if( ctype_digit($c) ){ return chr( intval(substr($s,1),8) ); } return $s; } protected function stripSlashes( $s ){ return stripcslashes($s); } }
class LocoJsTokens extends LocoTokenizer { const T_KWORD = 1; const T_REGEX = 2; private static $lex; protected static $words = array( 'true' => 1, 'false' => 1, 'null' => 1, 'break' => T_BREAK, 'else' => T_ELSE, 'new' => T_NEW, 'var' => 1, 'case' => T_CASE, 'finally' => T_FINALLY, 'return' => T_RETURN, 'void' => 1, 'catch' => T_CATCH, 'for' => T_FOR, 'switch' => T_SWITCH, 'while' => T_WHILE, 'continue' => T_CONTINUE, 'function' => T_FUNCTION, 'this' => T_STRING, 'with' => 1, 'default' => T_DEFAULT, 'if' => T_IF, 'throw' => T_THROW, 'delete' => 1, 'in' => 1, 'try' => T_TRY, 'do' => T_DO, 'instanceof' => 1, 'typeof' => 1, ); public static function decapse( $encapsed ){ $s = substr($encapsed,1,-1); $l = self::$lex; if( is_null($l) ){ $l = new LocoEscapeParser( array( 'U' => 'U', 'a' => 'a', ) ); self::$lex = $l; } return $l->unescape($s); } public function __construct( $src = '' ){ $this->define('/^(?:\\\\u[0-9A-F]{4,4}|[$_\\pL\\p{Nl}])(?:\\\\u[0-9A-F]{4}|[$_\\pL\\pN\\p{Mn}\\p{Mc}\\p{Pc}])*/ui', array($this,'matchWord') ); $this->define('/^\\s+/u', T_WHITESPACE ); $this->define('!^//.*!', T_COMMENT ); $this->define('!^/\\*.*\\*/!Us', array($this,'matchComment') ); $this->define('/^"(?:\\\\.|[^\\r\\n\\p{Zl}\\p{Zp}"\\\\])*"/u', T_CONSTANT_ENCAPSED_STRING ); $this->define('/^\'(?:\\\\.|[^\\r\\n\\p{Zl}\\p{Zp}\'\\\\])*\'/u', T_CONSTANT_ENCAPSED_STRING ); $this->define('/^[-+;,<>.=:|&^!?*%~(){}[\\]]/'); parent::__construct($src); } public function matchWord( $s ){ if( array_key_exists($s,self::$words) ){ return self::$words[$s]; } return T_STRING; } public function matchComment( $s ){ if( '/**' === substr($s,0,3) ){ return T_DOC_COMMENT; } return T_COMMENT; } }
class LocoExtracted implements Countable { private $exp = array(); private $reg = array(); private $dom = array(); private $dflt = ''; public function extractSource( LocoExtractor $ext, $src, $fileref = '' ){ $ext->extract( $this, $ext->tokenize($src), $fileref ); return $this; } public function export(){ return $this->exp; } public function count(){ return count( $this->exp ); } public function getDomainCounts(){ return $this->dom; } public function setDomain( $default ){ $this->dflt = (string) $default; return $this; } public function getDomain(){ return $this->dflt; } public function pushMeta( $source, $notes = '', $domain = null ){ if( ! $domain || '*' === $domain ){ $domain = $this->dflt; } $entry = array( 'id' => '', 'source' => $source, 'target' => '', 'notes' => $notes, ); if( $domain ){ $entry['domain'] = $domain; $key = $source."\1".$domain; } else { $key = $source; } return $this->pushMsgid( $key, $entry, $domain ); } public function pushMsgid( $key, array $entry, $domain ){ if( isset($this->reg[$key]) ){ $index = $this->reg[$key]; $clash = $this->exp[$index]; if( $value = $this->mergeField( $clash, $entry, 'refs', ' ') ){ $this->exp[$index]['refs'] = $value; } if( $value = $this->mergeField( $clash, $entry, 'notes', "\n") ){ $this->exp[$index]['notes'] = $value; } } else { $index = count($this->exp); $this->reg[$key] = $index; $this->exp[$index] = $entry; if( isset($this->dom[$domain]) ){ $this->dom[$domain]++; } else { $this->dom[$domain] = 1; } } return $index; } public function pushMsgidPlural( $key, array $entry ){ $key = $key."\2"; if( ! isset($this->reg[$key]) ){ $index = count($this->exp); $this->reg[$key] = $index; $this->exp[$index] = $entry; } } public function mergeField( array $old, array $new, $field, $glue ){ $prev = isset($old[$field]) ? $old[$field] : ''; if( isset($new[$field]) ){ $text = $new[$field]; if( '' !== $prev && $prev !== $text ){ if( 'notes' === $field && preg_match( '/^'.preg_quote( rtrim($text,'. '),'/').'[. ]*$/mu', $prev ) ) { $text = $prev; } else { $text = $prev.$glue.$text; } } return $text; } return $prev; } public function filter( $domain ){ $map = array(); $newOffset = 1; $matchAll = '*' === $domain; $raw = array( array( 'id' => '', 'source' => '', 'target' => '', 'domain' => $matchAll ? '' : $domain, ) ); foreach( $this->exp as $oldOffset => $r ){ if( isset($r['parent']) ){ if( isset($map[$r['parent']]) ){ $r['parent'] = $map[ $r['parent'] ]; $raw[ $newOffset++ ] = $r; } } else { if( $matchAll ){ $match = true; } else if( isset($r['domain']) ){ $match = $domain === $r['domain']; } else { $match = $domain === ''; } if( $match ){ $map[ $oldOffset ] = $newOffset; $raw[ $newOffset++ ] = $r; } } } return $raw; } }
abstract class LocoExtractor { private $rules; private $wp = array(); private $domain; abstract public function tokenize( $src ); abstract public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' ); abstract protected function fsniff( $str ); abstract protected function decapse( $raw ); abstract protected function comment( $comment ); public function __construct( array $rules ){ $this->rules = $rules; } public function setDomain( $default ){ $this->domain = $default; return $this; } public function headerize( array $tags, $domain = '' ){ if( isset($this->wp[$domain]) ){ $this->wp[$domain] += $tags; } else { $this->wp[$domain] = $tags; } return $this; } protected function getHeaders(){ return $this->wp; } final public function extractSource( $src, $fileref ){ $strings = new LocoExtracted; $this->extract( $strings, $this->tokenize($src), $fileref ); return $strings; } public function rule( $s ){ return isset($this->rules[$s]) ? $this->rules[$s] : ''; } protected function push( LocoExtracted $strings, $rule, array $args, $comment = '', $ref = '' ){ $s = strpos( $rule, 's'); $p = strpos( $rule, 'p'); $c = strpos( $rule, 'c'); $d = strpos( $rule, 'd'); if( false === $s || ! isset($args[$s]) ){ return null; } $key = $args[$s]; if( ! is_string($key) ){ return null; } $msgid = $key; $entry = array( 'id' => '', 'source' => $msgid, 'target' => '', ); if( is_int($c) && isset($args[$c]) ){ $entry['context'] = $context = $args[$c]; $key .= "\0". $context; } else if( '' === $msgid ){ return null; } else { $context = null; } if( $ref ){ $entry['refs'] = $ref; } if( is_int($d) && array_key_exists($d,$args) ){ $domain = $args[$d]; if( is_null($domain) ){ $domain = ''; } } else { $domain = $this->domain or $domain = $strings->getDomain(); } if( is_string($domain) && '' !== $domain ){ $entry['domain'] = $domain; $key .= "\1".$domain; } $parse_printf = true; if( $comment = $this->comment($comment) ){ if( preg_match('/^xgettext:\\s*([-a-z]+)-format\\s*/mi', $comment, $r, PREG_OFFSET_CAPTURE ) ){ $entry['format'] = $r[1][0]; $comment = trim( substr_replace( $comment,'', $r[0][1], strlen($r[0][0]) ) ); $parse_printf = ( 'no-' === substr($entry['format'],0,3) ) ? false : null; } if( preg_match('/^references?:( *.+:\\d+)*\\s*/mi', $comment, $r, PREG_OFFSET_CAPTURE ) ){ $entry['refs'] = trim($r[1][0],' '); $comment = trim( substr_replace( $comment, '', $r[0][1], strlen($r[0][0]) ) ); } $entry['notes'] = $comment; } if( $parse_printf && ( $format = $this->fsniff($msgid) ) ){ $entry['format'] = $format; } $index = $strings->pushMsgid( $key, $entry, $domain ); if( is_int($p) && isset($args[$p]) ){ $msgid_plural = $args[$p]; $entry = array( 'id' => '', 'source' => $msgid_plural, 'target' => '', 'plural' => 1, 'parent' => $index, ); if( false !== $parse_printf && ( $format = $this->fsniff($msgid_plural) ) ){ $entry['format'] = $format; } $strings->pushMsgidPlural( $key, $entry ); } return $index; } }
class LocoPHPTokens implements LocoTokensInterface, Countable { private $i; private $tokens; private $skip_tokens = array(); private $literal_tokens = array(); public function __construct( array $tokens ){ $this->tokens = $tokens; $this->rewind(); } public function literal( array $a ){ foreach( $a as $t ){ $this->literal_tokens[ $t ] = 1; } return $this; } public function ignore( array $a ){ foreach( $a as $t ){ $this->skip_tokens[$t] = true; } return $this; } public function export(){ $arr = array(); $this->rewind(); while( $tok = $this->advance() ){ $arr[] = $tok; } return $arr; } public function advance(){ $tok = $this->current(); $this->next(); return $tok; } public function rewind(){ $this->i = ( false === reset($this->tokens) ? null : key($this->tokens) ); } public function valid(){ while( isset($this->i) ){ $tok = $this->tokens[$this->i]; if( array_key_exists( is_array($tok) ? $tok[0] : $tok, $this->skip_tokens ) ){ $this->next(); } else { return true; } } return false; } public function key(){ return $this->i; } public function next(){ $this->i = ( false === next($this->tokens) ? null : key($this->tokens) ); } public function current(){ if( ! $this->valid() ){ return false; } $tok = $this->tokens[$this->i]; if( is_array($tok) && isset($this->literal_tokens[$tok[0]]) ){ return $tok[1]; } return $tok; } public function __toString(){ $s = array(); foreach( $this as $token ){ $s[] = is_array($token) ? $token[1] : $token; } return implode('',$s); } public function count(){ return count($this->tokens); } }
class LocoPHPEscapeParser extends LocoEscapeParser { public function __construct(){ parent::__construct( array( 'n' => "\n", 'r' => "\r", 't' => "\t", 'v' => "\x0B", 'f' => "\x0C", 'e' => "\x1B", '$' => '$', '\\' => '\\', '"' => '"', ) ); } protected function stripSlashes( $s ){ return preg_replace_callback('/\\\\(x[0-9A-Fa-f]{1,2}|[0-3]?[0-7]{1,2})/', array($this,'unescapeMatch'), $s, -1, $n ); } }
function loco_unescape_php_string( $s ){ static $l; if( is_null($l) ) { $l = new LocoPHPEscapeParser; } return $l->unescape($s); }
function loco_decapse_php_string( $s ){ if( ! $s ){ return (string) $s; } $q = $s[0]; if( "'" === $q ){ return str_replace( array('\\'.$q, '\\\\'), array($q, '\\'), substr( $s, 1, -1 ) ); } if( '"' !== $q ){ return $s; } return loco_unescape_php_string( substr($s,1,-1) ); }
function loco_parse_php_comment($comment){ $comment = trim( $comment,"/ \n\r\t" ); if( '' !== $comment && '*' === $comment[0] ){ $lines = array(); $junk = "\r\t/ *"; foreach( explode("\n",$comment) as $line ){ $line = trim($line,$junk); if( '' !== $line ){ $lines[] = $line; } } $comment = implode("\n", $lines); } return $comment; }
function loco_parse_wp_comment( $block ){ $header = array(); if( '*' === $block[1] ){ $junk = "\r\t/ *"; foreach( explode("\n", $block) as $line ){ if( false !== ( $i = strpos($line,':') ) ){ $key = substr($line,0,$i); $val = substr($line,++$i); $header[ trim($key,$junk) ] = trim($val,$junk); } } } return $header; }
function loco_sniff_printf( $s, $p, $limit = 0, $offset = 0 ){ $n = 0; while( is_string($s) && '' !== $s && false !== ( $i = strpos($s,'%',$offset) ) ){ if( 0 !== $i ){ $s = substr( $s, $i ); } if( preg_match( $p, $s, $r ) ){ $match = $r[0]; if( 0 < $n && isset($r[1]) && '' === $r[1] && '%' !== substr($match,-1) ){ return 0; } if( ++$n === $limit ){ break; } $offset = strlen($match); } else { return 0; } } return $n; }
function loco_sniff_php_printf( $s, $limit = 0 ){ return loco_sniff_printf( $s, '/^%(?:[1-9]\\d*\\$)?(?:\'.|[-+0 ])*\\d*(?:\\.\\d+)?[suxXbcdeEfFgGo%]/', $limit ); }
class LocoPHPExtractor extends LocoExtractor { private $defs = array(); public function tokenize( $src ){ return new LocoPHPTokens( token_get_all($src) ); } public function decapse( $raw ){ return loco_decapse_php_string( $raw ); } public function fsniff( $str ){ return loco_sniff_php_printf($str) ? 'php' : ''; } protected function comment( $comment ){ $comment = loco_parse_php_comment($comment); $comment = preg_replace('/^translators:\\s+/mi', '', $comment ); return $comment; } public function define( $name, $value ){ if( is_string($value) ){ $this->defs[$name] = $value; } return $this; } public function extract( LocoExtracted $strings, LocoTokensInterface $tokens, $fileref = '' ){ $tokens->ignore( array(T_WHITESPACE) ); $n = 0; $depth = 0; $comment = ''; $narg = 0; $args = array(); $ref = ''; $rule = ''; $wp = $this->getHeaders(); $tokens->rewind(); while( $tok = $tokens->advance() ){ if( is_string($tok) ){ $s = $tok; $t = null; } else { $t = $tok[0]; $s = $tok[1]; } if( $depth ){ if( ')' === $s || ']' === $s ){ if( 0 === --$depth ){ if( $this->push( $strings, $rule, $args, $comment, $ref ) ){ $n++; } $comment = ''; } } else if( '(' === $s || '[' === $s ){ $depth++; $args[$narg] = null; } else if( 1 === $depth ){ if( ',' === $s ){ $narg++; } else if( T_CONSTANT_ENCAPSED_STRING === $t ){ $args[$narg] = $this->decapse($s); } else if( T_STRING === $t && array_key_exists($s,$this->defs) ){ $args[$narg] = $this->defs[$s]; } else { $args[$narg] = null; } } } else if( T_COMMENT === $t || T_DOC_COMMENT === $t ){ $was_header = false; if( 0 === $n ){ if( false !== strpos($s,'* @package') ){ $was_header = true; } if( $wp && ( $header = loco_parse_wp_comment($s) ) ){ foreach( $wp as $domain => $tags ){ foreach( array_intersect_key($header,$tags) as $tag => $source ){ $strings->pushMeta( $source, $tags[$tag], $domain ); $was_header = true; } } } } if( ! $was_header ) { $comment = $s; } } else if( T_STRING === $t && '(' === $tokens->advance() && ( $rule = $this->rule($s) ) ){ $ref = $fileref ? $fileref.':'.$tok[2]: ''; $depth = 1; $args = array(); $narg = 0; } else if( $comment ){ if( false === stripos($comment, 'translators:') && false === strpos($comment, 'xgettext:') ){ $comment = ''; } } } return $this; } }
function loco_sniff_js_printf( $s, $limit = 0 ){ return loco_sniff_printf( $s, '/^%(?:[1-9]\\d*\\$)?\\+?(?:0|\'[^$])?-?\\d*(?:\\.\\d+)?[b-gijostTuvxX%]/', $limit ); }
class LocoJsExtractor extends LocoPHPExtractor { public function tokenize( $src ){ return new LocoJsTokens($src); } public function fsniff( $str ){ return loco_sniff_js_printf($str) ? 'javascript' : ''; } public function decapse( $encapsed ){ return LocoJsTokens::decapse($encapsed); } }
class LocoTwigExtractor extends LocoPHPExtractor { public function tokenize( $src ){ $src = ' 'sd', '_e' => 'sd', '_c' => 'sd', '_n' => 'sp_d', '_n_noop' => 'spd', '_nc' => 'sp_d', '__ngettext' => 'spd', '__ngettext_noop' => 'spd', '_x' => 'scd', '_ex' => 'scd', '_nx' => 'sp_cd', '_nx_noop' => 'spcd', 'esc_attr__' => 'sd', 'esc_html__' => 'sd', 'esc_attr_e' => 'sd', 'esc_html_e' => 'sd', 'esc_attr_x' => 'scd', 'esc_html_x' => 'scd', ); if( 'php' === $type ){ $extr = new LocoPHPExtractor($rules); } else if( 'twig' === $type ){ $extr = new LocoTwigExtractor($rules); } else if( 'js' === $type ){ $extr = new LocoJsExtractor($rules); } else { throw new InvalidArgumentException('No string extractor for '.$type); } return $extr; }
function loco_print_percent( $n, $t ){ $s = loco_string_percent( (int) $n, (int) $t ); echo $s,'%'; return ''; }
function loco_print_progress( $translated, $untranslated, $flagged ){ $total = $translated + $untranslated; $complete = loco_string_percent( $translated - $flagged, $total ); $class = 'progress'; if( ! $translated && ! $flagged ){ $class .= ' empty'; } else if( '100' === $complete ){ $class .= ' done'; } echo '
'; return ''; }
function loco_string_percent( $n, $t ){ if( ! $t || ! $n ){ $s = '0'; } else if( $t === $n ){ $s = '100'; } else { $dp = 0; $n = 100 * $n / $t; if( $n > 99 ){ $s = number_format( min( $n, 99.9 ), ++$dp ); } else if( $n < 0.5 ){ $n = max( $n, 0.0001 ); do { $s = number_format( $n, ++$dp ); } while( preg_match('/^0\\.0+$/',$s) && $dp < 4 ); $s = substr($s,1); } else { $s = number_format( $n, $dp ); } } return $s; }
defined('T_FINALLY') || define('T_FINALLY',500);
lib/compiled/phpunit.php 0000666 00000011517 15214141740 0011316 0 ustar 00 preserveWhitespace = true; $dom->formatOutput = false; $source = ''.$source.''; $used_errors = libxml_use_internal_errors(true); $opts = LIBXML_HTML_NODEFDTD; $parsed = $dom->loadHTML( $source, $opts ); $errors = libxml_get_errors(); $used_errors || libxml_use_internal_errors(false); libxml_clear_errors(); if( $errors || ! $parsed ){ $e = new Loco_error_ParseException('Unknown parse error'); foreach( $errors as $error ){ $e = new Loco_error_ParseException( trim($error->message) ); $e->setContext( $error->line, $error->column, $source ); if( LIBXML_ERR_FATAL === $error->level ){ throw $e; } } if( ! $parsed ){ throw $e; } } return $dom; } public function __construct( $value ){ if( $value instanceof DOMDocument ){ $value = array( $value->documentElement ); } else if( $value instanceof DOMNode ){ $value = array( $value ); } if( is_iterable($value) ){ $nodes = array(); foreach( $value as $node ){ $nodes[] = $node; } } else if( is_string($value) || method_exists($value,'__toString') ){ $value = self::parse( $value ); $nodes = array( $value->documentElement ); } else { $type = is_object($value) ? get_class($value) : gettype($value); throw new InvalidArgumentException('Cannot construct DOM from '.$type ); } parent::__construct( $nodes ); } public function eq( $index ){ $q = new LocoDomQuery(array()); if( $el = $this[$index] ){ $q[] = $el; } return $q; } public function find( $value ){ $q = new LocoDomQuery( array() ); $f = new _LocoDomQueryFilter($value); foreach( $this as $el ){ foreach( $f->filter($el) as $match ){ $q[] = $match; } } return $q; } public function children(){ $q = new LocoDomQuery(array()); foreach( $this as $el ){ if( $el instanceof DOMNode ){ foreach( $el->childNodes as $child ) { $q[] = $child; } } } return $q; } public function text(){ $s = ''; foreach( $this as $el ){ $s .= $el->textContent; } return $s; } public function html(){ $s = ''; foreach( $this as $outer ){ foreach( $outer->childNodes as $inner ){ $s .= $inner->ownerDocument->saveXML($inner); } break; } return $s; } public function attr( $name ){ foreach( $this as $el ){ return $el->getAttribute($name); } return null; } public function serialize(){ $pairs = array(); foreach( array('input','select','textarea','button') as $type ){ foreach( $this->find($type) as $field ){ $name = $field->getAttribute('name'); if( ! $name ){ continue; } if( $field->hasAttribute('type') ){ $type = $field->getAttribute('type'); } if( 'select' === $type ){ $value = null; $f = new _LocoDomQueryFilter('option'); foreach( $f->filter($field) as $option ){ if( $option->hasAttribute('value') ){ $_value = $option->getAttribute('value'); } else { $_value = $option->nodeValue; } if( $option->hasAttribute('selected') ){ $value = $_value; break; } else if( is_null($value) ){ $value = $_value; } } if( is_null($value) ){ $value = ''; } } else if( 'checkbox' === $type || 'radio' === $type ){ if( $field->hasAttribute('checked') ){ $value = $field->getAttribute('value'); } else { continue; } } else if( 'file' === $type ){ $value = ''; } else if( $field->hasAttribute('value') ){ $value = $field->getAttribute('value'); } else { $value = $field->textContent; } $pairs[] = sprintf('%s=%s', rawurlencode($name), rawurlencode($value) ); } } return implode('&',$pairs); } }
class _LocoDomQueryFilter { private $tag; private $attr = array(); public function __construct( $value ){ $id = '[-_a-z][-_a-z0-9]*'; if( ! preg_match('/^([a-z1-6]*)(#'.$id.')?(\.'.$id.')?(\[(\w+)="(.+)"\])?$/i', $value, $r ) ){ throw new InvalidArgumentException('Bad filter, '.$value ); } if( $r[1] ){ $this->tag = $r[1]; } if( ! empty($r[2]) ){ $this->attr['id'] = substr($r[2],1); } if( ! empty($r[3]) ){ $this->attr['class'] = substr($r[3],1); } if( ! empty($r[4]) ){ $this->attr[ $r[5] ] = $r[6]; } } public function filter( DOMElement $el ){ if( $this->tag ){ $list = $el->getElementsByTagName($this->tag); $recursive = false; } else { $list = $el->childNodes; $recursive = true; } if( $this->attr ){ $list = $this->reduce( $list, new ArrayIterator, $recursive )->getArrayCopy(); } return $list; } public function reduce( DOMNodeList $list, ArrayIterator $reduced, $recursive ){ foreach( $list as $node ){ if( $node instanceof DOMElement ){ $matched = false; foreach( $this->attr as $name => $value ){ if( ! $node->hasAttribute($name) ){ $matched = false; break; } $values = array_flip( explode(' ', $node->getAttribute($name) ) ); if( ! isset($values[$value]) ){ $matched = false; break; } $matched = true; } if( $matched ){ $reduced[] = $node; } if( $recursive && $node->hasChildNodes() ){ $this->reduce( $node->childNodes, $reduced, true ); } } } return $reduced; } }
lib/compiled/locales.php 0000666 00000001040 15214141740 0011237 0 ustar 00 strtolower( $tags[1] ), ); if( isset($tags[2]) && ( $subtag = $tags[2] ) ){ $data['region'] = strtoupper($tags[2]); } if( isset($tags[3]) && ( $subtag = $tags[3] ) ){ $data['variant'] = strtolower($tags[3]); } return $data; }
lib/compiled/README.md 0000666 00000000222 15214141740 0010364 0 ustar 00 # Compiled libraries
These files are built from the Loco core. Do not edit!
They've been converted down for PHP 5.2 compatibility in WordPress.
lib/data/regions.php 0000666 00000011727 15214141740 0010415 0 ustar 00 'Andorra','AE'=>'United Arab Emirates','AF'=>'Afghanistan','AG'=>'Antigua and Barbuda','AI'=>'Anguilla','AL'=>'Albania','AM'=>'Armenia','AO'=>'Angola','AQ'=>'Antarctica','AR'=>'Argentina','AS'=>'American Samoa','AT'=>'Austria','AU'=>'Australia','AW'=>'Aruba','AX'=>'Åland Islands','AZ'=>'Azerbaijan','BA'=>'Bosnia and Herzegovina','BB'=>'Barbados','BD'=>'Bangladesh','BE'=>'Belgium','BF'=>'Burkina Faso','BG'=>'Bulgaria','BH'=>'Bahrain','BI'=>'Burundi','BJ'=>'Benin','BL'=>'Saint Barthélemy','BM'=>'Bermuda','BN'=>'Brunei Darussalam','BO'=>'Bolivia','BQ'=>'Bonaire, Sint Eustatius and Saba','BR'=>'Brazil','BS'=>'Bahamas','BT'=>'Bhutan','BV'=>'Bouvet Island','BW'=>'Botswana','BY'=>'Belarus','BZ'=>'Belize','CA'=>'Canada','CC'=>'Cocos (Keeling) Islands','CD'=>'The Democratic Republic of the Congo','CF'=>'Central African Republic','CG'=>'Congo','CH'=>'Switzerland','CI'=>'Côte d\'Ivoire','CK'=>'Cook Islands','CL'=>'Chile','CM'=>'Cameroon','CN'=>'China','CO'=>'Colombia','CR'=>'Costa Rica','CU'=>'Cuba','CV'=>'Cabo Verde; Cape Verde','CW'=>'Curaçao','CX'=>'Christmas Island','CY'=>'Cyprus','CZ'=>'Czech Republic','DE'=>'Germany','DJ'=>'Djibouti','DK'=>'Denmark','DM'=>'Dominica','DO'=>'Dominican Republic','DZ'=>'Algeria','EC'=>'Ecuador','EE'=>'Estonia','EG'=>'Egypt','EH'=>'Western Sahara','ER'=>'Eritrea','ES'=>'Spain','ET'=>'Ethiopia','FI'=>'Finland','FJ'=>'Fiji','FK'=>'Falkland Islands (Malvinas)','FM'=>'Federated States of Micronesia','FO'=>'Faroe Islands','FR'=>'France','GA'=>'Gabon','GB'=>'United Kingdom','GD'=>'Grenada','GE'=>'Georgia','GF'=>'French Guiana','GG'=>'Guernsey','GH'=>'Ghana','GI'=>'Gibraltar','GL'=>'Greenland','GM'=>'Gambia','GN'=>'Guinea','GP'=>'Guadeloupe','GQ'=>'Equatorial Guinea','GR'=>'Greece','GS'=>'South Georgia and the South Sandwich Islands','GT'=>'Guatemala','GU'=>'Guam','GW'=>'Guinea-Bissau','GY'=>'Guyana','HK'=>'Hong Kong','HM'=>'Heard Island and McDonald Islands','HN'=>'Honduras','HR'=>'Croatia','HT'=>'Haiti','HU'=>'Hungary','ID'=>'Indonesia','IE'=>'Ireland','IL'=>'Israel','IM'=>'Isle of Man','IN'=>'India','IO'=>'British Indian Ocean Territory','IQ'=>'Iraq','IR'=>'Islamic Republic of Iran','IS'=>'Iceland','IT'=>'Italy','JE'=>'Jersey','JM'=>'Jamaica','JO'=>'Jordan','JP'=>'Japan','KE'=>'Kenya','KG'=>'Kyrgyzstan','KH'=>'Cambodia','KI'=>'Kiribati','KM'=>'Comoros','KN'=>'Saint Kitts and Nevis','KP'=>'Democratic People\'s Republic of Korea','KR'=>'Republic of Korea','KW'=>'Kuwait','KY'=>'Cayman Islands','KZ'=>'Kazakhstan','LA'=>'Lao People\'s Democratic Republic','LB'=>'Lebanon','LC'=>'Saint Lucia','LI'=>'Liechtenstein','LK'=>'Sri Lanka','LR'=>'Liberia','LS'=>'Lesotho','LT'=>'Lithuania','LU'=>'Luxembourg','LV'=>'Latvia','LY'=>'Libya','MA'=>'Morocco','MC'=>'Monaco','MD'=>'Moldova','ME'=>'Montenegro','MF'=>'Saint Martin (French part)','MG'=>'Madagascar','MH'=>'Marshall Islands','MK'=>'The Former Yugoslav Republic of Macedonia','ML'=>'Mali','MM'=>'Myanmar','MN'=>'Mongolia','MO'=>'Macao','MP'=>'Northern Mariana Islands','MQ'=>'Martinique','MR'=>'Mauritania','MS'=>'Montserrat','MT'=>'Malta','MU'=>'Mauritius','MV'=>'Maldives','MW'=>'Malawi','MX'=>'Mexico','MY'=>'Malaysia','MZ'=>'Mozambique','NA'=>'Namibia','NC'=>'New Caledonia','NE'=>'Niger','NF'=>'Norfolk Island','NG'=>'Nigeria','NI'=>'Nicaragua','NL'=>'Netherlands','NO'=>'Norway','NP'=>'Nepal','NR'=>'Nauru','NU'=>'Niue','NZ'=>'New Zealand','OM'=>'Oman','PA'=>'Panama','PE'=>'Peru','PF'=>'French Polynesia','PG'=>'Papua New Guinea','PH'=>'Philippines','PK'=>'Pakistan','PL'=>'Poland','PM'=>'Saint Pierre and Miquelon','PN'=>'Pitcairn','PR'=>'Puerto Rico','PS'=>'State of Palestine','PT'=>'Portugal','PW'=>'Palau','PY'=>'Paraguay','QA'=>'Qatar','RE'=>'Réunion','RO'=>'Romania','RS'=>'Serbia','RU'=>'Russian Federation','RW'=>'Rwanda','SA'=>'Saudi Arabia','SB'=>'Solomon Islands','SC'=>'Seychelles','SD'=>'Sudan','SE'=>'Sweden','SG'=>'Singapore','SH'=>'Saint Helena, Ascension and Tristan da Cunha','SI'=>'Slovenia','SJ'=>'Svalbard and Jan Mayen','SK'=>'Slovakia','SL'=>'Sierra Leone','SM'=>'San Marino','SN'=>'Senegal','SO'=>'Somalia','SR'=>'Suriname','SS'=>'South Sudan','ST'=>'Sao Tome and Principe','SV'=>'El Salvador','SX'=>'Sint Maarten (Dutch part)','SY'=>'Syrian Arab Republic','SZ'=>'Swaziland','TC'=>'Turks and Caicos Islands','TD'=>'Chad','TF'=>'French Southern Territories','TG'=>'Togo','TH'=>'Thailand','TJ'=>'Tajikistan','TK'=>'Tokelau','TL'=>'Timor-Leste','TM'=>'Turkmenistan','TN'=>'Tunisia','TO'=>'Tonga','TR'=>'Turkey','TT'=>'Trinidad and Tobago','TV'=>'Tuvalu','TW'=>'Taiwan','TZ'=>'United Republic of Tanzania','UA'=>'Ukraine','UG'=>'Uganda','UM'=>'United States Minor Outlying Islands','US'=>'United States','UY'=>'Uruguay','UZ'=>'Uzbekistan','VA'=>'Holy See (Vatican City State)','VC'=>'Saint Vincent and the Grenadines','VE'=>'Venezuela','VG'=>'British Virgin Islands','VI'=>'U.S. Virgin Islands','VN'=>'Viet Nam','VU'=>'Vanuatu','WF'=>'Wallis and Futuna','WS'=>'Samoa','YE'=>'Yemen','YT'=>'Mayotte','ZA'=>'South Africa','ZM'=>'Zambia','ZW'=>'Zimbabwe','ZZ'=>'Private use');
lib/data/locales.php 0000666 00000013457 15214141740 0010373 0 ustar 00 array(0=>'Afrikaans',1=>'Afrikaans'),'ar'=>array(0=>'Arabic',1=>'العربية'),'ary'=>array(0=>'Moroccan Arabic',1=>'العربية المغربية'),'as'=>array(0=>'Assamese',1=>'অসমীয়া'),'azb'=>array(0=>'South Azerbaijani',1=>'گؤنئی آذربایجان'),'az'=>array(0=>'Azerbaijani',1=>'Azərbaycan dili'),'bel'=>array(0=>'Belarusian',1=>'Беларуская мова'),'bg_BG'=>array(0=>'Bulgarian',1=>'Български'),'bn_BD'=>array(0=>'Bengali (Bangladesh)',1=>'বাংলা'),'bo'=>array(0=>'Tibetan',1=>'བོད་ཡིག'),'bs_BA'=>array(0=>'Bosnian',1=>'Bosanski'),'ca'=>array(0=>'Catalan',1=>'Català'),'ceb'=>array(0=>'Cebuano',1=>'Cebuano'),'cs_CZ'=>array(0=>'Czech',1=>'Čeština'),'cy'=>array(0=>'Welsh',1=>'Cymraeg'),'da_DK'=>array(0=>'Danish',1=>'Dansk'),'de_CH'=>array(0=>'German (Switzerland)',1=>'Deutsch (Schweiz)'),'de_DE'=>array(0=>'German',1=>'Deutsch'),'de_DE_formal'=>array(0=>'German (Formal)',1=>'Deutsch (Sie)'),'de_CH_informal'=>array(0=>'German (Switzerland, Informal)',1=>'Deutsch (Schweiz, Du)'),'dzo'=>array(0=>'Dzongkha',1=>'རྫོང་ཁ'),'el'=>array(0=>'Greek',1=>'Ελληνικά'),'en_ZA'=>array(0=>'English (South Africa)',1=>'English (South Africa)'),'en_AU'=>array(0=>'English (Australia)',1=>'English (Australia)'),'en_NZ'=>array(0=>'English (New Zealand)',1=>'English (New Zealand)'),'en_CA'=>array(0=>'English (Canada)',1=>'English (Canada)'),'en_GB'=>array(0=>'English (UK)',1=>'English (UK)'),'eo'=>array(0=>'Esperanto',1=>'Esperanto'),'es_AR'=>array(0=>'Spanish (Argentina)',1=>'Español de Argentina'),'es_MX'=>array(0=>'Spanish (Mexico)',1=>'Español de México'),'es_CL'=>array(0=>'Spanish (Chile)',1=>'Español de Chile'),'es_VE'=>array(0=>'Spanish (Venezuela)',1=>'Español de Venezuela'),'es_CR'=>array(0=>'Spanish (Costa Rica)',1=>'Español de Costa Rica'),'es_PE'=>array(0=>'Spanish (Peru)',1=>'Español de Perú'),'es_GT'=>array(0=>'Spanish (Guatemala)',1=>'Español de Guatemala'),'es_CO'=>array(0=>'Spanish (Colombia)',1=>'Español de Colombia'),'es_ES'=>array(0=>'Spanish (Spain)',1=>'Español'),'et'=>array(0=>'Estonian',1=>'Eesti'),'eu'=>array(0=>'Basque',1=>'Euskara'),'fa_IR'=>array(0=>'Persian',1=>'فارسی'),'fi'=>array(0=>'Finnish',1=>'Suomi'),'fr_CA'=>array(0=>'French (Canada)',1=>'Français du Canada'),'fr_BE'=>array(0=>'French (Belgium)',1=>'Français de Belgique'),'fr_FR'=>array(0=>'French (France)',1=>'Français'),'fur'=>array(0=>'Friulian',1=>'Friulian'),'gd'=>array(0=>'Scottish Gaelic',1=>'Gàidhlig'),'gl_ES'=>array(0=>'Galician',1=>'Galego'),'gu'=>array(0=>'Gujarati',1=>'ગુજરાતી'),'haz'=>array(0=>'Hazaragi',1=>'هزاره گی'),'he_IL'=>array(0=>'Hebrew',1=>'עִבְרִית'),'hi_IN'=>array(0=>'Hindi',1=>'हिन्दी'),'hr'=>array(0=>'Croatian',1=>'Hrvatski'),'hu_HU'=>array(0=>'Hungarian',1=>'Magyar'),'hy'=>array(0=>'Armenian',1=>'Հայերեն'),'id_ID'=>array(0=>'Indonesian',1=>'Bahasa Indonesia'),'is_IS'=>array(0=>'Icelandic',1=>'Íslenska'),'it_IT'=>array(0=>'Italian',1=>'Italiano'),'ja'=>array(0=>'Japanese',1=>'日本語'),'jv_ID'=>array(0=>'Javanese',1=>'Basa Jawa'),'ka_GE'=>array(0=>'Georgian',1=>'ქართული'),'kab'=>array(0=>'Kabyle',1=>'Taqbaylit'),'kk'=>array(0=>'Kazakh',1=>'Қазақ тілі'),'km'=>array(0=>'Khmer',1=>'ភាសាខ្មែរ'),'ko_KR'=>array(0=>'Korean',1=>'한국어'),'ckb'=>array(0=>'Kurdish (Sorani)',1=>'كوردی'),'lo'=>array(0=>'Lao',1=>'ພາສາລາວ'),'lt_LT'=>array(0=>'Lithuanian',1=>'Lietuvių kalba'),'lv'=>array(0=>'Latvian',1=>'Latviešu valoda'),'mk_MK'=>array(0=>'Macedonian',1=>'Македонски јазик'),'ml_IN'=>array(0=>'Malayalam',1=>'മലയാളം'),'mn'=>array(0=>'Mongolian',1=>'Монгол'),'mr'=>array(0=>'Marathi',1=>'मराठी'),'ms_MY'=>array(0=>'Malay',1=>'Bahasa Melayu'),'my_MM'=>array(0=>'Myanmar (Burmese)',1=>'ဗမာစာ'),'nb_NO'=>array(0=>'Norwegian (Bokmål)',1=>'Norsk bokmål'),'ne_NP'=>array(0=>'Nepali',1=>'नेपाली'),'nl_NL_formal'=>array(0=>'Dutch (Formal)',1=>'Nederlands (Formeel)'),'nl_BE'=>array(0=>'Dutch (Belgium)',1=>'Nederlands (België)'),'nl_NL'=>array(0=>'Dutch',1=>'Nederlands'),'nn_NO'=>array(0=>'Norwegian (Nynorsk)',1=>'Norsk nynorsk'),'oci'=>array(0=>'Occitan',1=>'Occitan'),'pa_IN'=>array(0=>'Punjabi',1=>'ਪੰਜਾਬੀ'),'pl_PL'=>array(0=>'Polish',1=>'Polski'),'ps'=>array(0=>'Pashto',1=>'پښتو'),'pt_PT'=>array(0=>'Portuguese (Portugal)',1=>'Português'),'pt_BR'=>array(0=>'Portuguese (Brazil)',1=>'Português do Brasil'),'pt_PT_ao90'=>array(0=>'Portuguese (Portugal, AO90)',1=>'Português (AO90)'),'rhg'=>array(0=>'Rohingya',1=>'Ruáinga'),'ro_RO'=>array(0=>'Romanian',1=>'Română'),'ru_RU'=>array(0=>'Russian',1=>'Русский'),'sah'=>array(0=>'Sakha',1=>'Сахалыы'),'si_LK'=>array(0=>'Sinhala',1=>'සිංහල'),'sk_SK'=>array(0=>'Slovak',1=>'Slovenčina'),'sl_SI'=>array(0=>'Slovenian',1=>'Slovenščina'),'sq'=>array(0=>'Albanian',1=>'Shqip'),'sr_RS'=>array(0=>'Serbian',1=>'Српски језик'),'sv_SE'=>array(0=>'Swedish',1=>'Svenska'),'szl'=>array(0=>'Silesian',1=>'Ślōnskŏ gŏdka'),'ta_IN'=>array(0=>'Tamil',1=>'தமிழ்'),'te'=>array(0=>'Telugu',1=>'తెలుగు'),'th'=>array(0=>'Thai',1=>'ไทย'),'tl'=>array(0=>'Tagalog',1=>'Tagalog'),'tr_TR'=>array(0=>'Turkish',1=>'Türkçe'),'tt_RU'=>array(0=>'Tatar',1=>'Татар теле'),'tah'=>array(0=>'Tahitian',1=>'Reo Tahiti'),'ug_CN'=>array(0=>'Uighur',1=>'ئۇيغۇرچە'),'uk'=>array(0=>'Ukrainian',1=>'Українська'),'ur'=>array(0=>'Urdu',1=>'اردو'),'uz_UZ'=>array(0=>'Uzbek',1=>'O‘zbekcha'),'vi'=>array(0=>'Vietnamese',1=>'Tiếng Việt'),'zh_CN'=>array(0=>'Chinese (China)',1=>'简体中文'),'zh_HK'=>array(0=>'Chinese (Hong Kong)',1=>'香港中文版 '),'zh_TW'=>array(0=>'Chinese (Taiwan)',1=>'繁體中文'));
lib/data/languages.php 0000666 00000007011 15214141740 0010704 0 ustar 00 'Afar','ab'=>'Abkhazian','ae'=>'Avestan','af'=>'Afrikaans','ak'=>'Akan','am'=>'Amharic','an'=>'Aragonese','ar'=>'Arabic','arq'=>'Algerian Arabic','ary'=>'Moroccan Arabic','as'=>'Assamese','ast'=>'Asturian','av'=>'Avaric','ay'=>'Aymara','az'=>'Azerbaijani','azb'=>'South Azerbaijani','ba'=>'Bashkir','bal'=>'Baluchi','bcc'=>'Southern Balochi','be'=>'Belarusian','bg'=>'Bulgarian','bi'=>'Bislama','bm'=>'Bambara','bn'=>'Bengali','bo'=>'Tibetan','br'=>'Breton','bs'=>'Bosnian','ca'=>'Catalan','ce'=>'Chechen','ceb'=>'Cebuano','ch'=>'Chamorro','ckb'=>'Central Kurdish','co'=>'Corsican','cr'=>'Cree','cs'=>'Czech','cu'=>'Church Slavic','cv'=>'Chuvash','cy'=>'Welsh','da'=>'Danish','de'=>'German','dv'=>'Dhivehi','dz'=>'Dzongkha','ee'=>'Ewe','el'=>'Greek','en'=>'English','eo'=>'Esperanto','es'=>'Spanish','et'=>'Estonian','eu'=>'Basque','fa'=>'Persian','ff'=>'Fulah','fi'=>'Finnish','fj'=>'Fijian','fo'=>'Faroese','fr'=>'French','frp'=>'Arpitan','fuc'=>'Pulaar','fur'=>'Friulian','fy'=>'Western Frisian','ga'=>'Irish','gd'=>'Scottish Gaelic','gl'=>'Galician','gn'=>'Guarani','gsw'=>'Swiss German','gu'=>'Gujarati','gv'=>'Manx','ha'=>'Hausa','haw'=>'Hawaiian','haz'=>'Hazaragi','he'=>'Hebrew','hi'=>'Hindi','ho'=>'Hiri Motu','hr'=>'Croatian','ht'=>'Haitian','hu'=>'Hungarian','hy'=>'Armenian','hz'=>'Herero','ia'=>'Interlingua','id'=>'Indonesian','ie'=>'Interlingue','ig'=>'Igbo','ii'=>'Sichuan Yi','ik'=>'Inupiaq','io'=>'Ido','is'=>'Icelandic','it'=>'Italian','iu'=>'Inuktitut','ja'=>'Japanese','jv'=>'Javanese','ka'=>'Georgian','kab'=>'Kabyle','kg'=>'Kongo','ki'=>'Kikuyu','kj'=>'Kuanyama','kk'=>'Kazakh','kl'=>'Kalaallisut','km'=>'Central Khmer','kn'=>'Kannada','ko'=>'Korean','kr'=>'Kanuri','ks'=>'Kashmiri','ku'=>'Kurdish','kv'=>'Komi','kw'=>'Cornish','ky'=>'Kirghiz','la'=>'Latin','lb'=>'Luxembourgish','lg'=>'Ganda','li'=>'Limburgan','ln'=>'Lingala','lo'=>'Lao','lt'=>'Lithuanian','lu'=>'Luba-Katanga','lv'=>'Latvian','mg'=>'Malagasy','mh'=>'Marshallese','mi'=>'Maori','mk'=>'Macedonian','ml'=>'Malayalam','mn'=>'Mongolian','mr'=>'Marathi','ms'=>'Malay','mt'=>'Maltese','my'=>'Burmese','na'=>'Nauru','nb'=>'Norwegian Bokmål','nd'=>'North Ndebele','ne'=>'Nepali','ng'=>'Ndonga','nl'=>'Dutch','nn'=>'Norwegian Nynorsk','no'=>'Norwegian','nr'=>'South Ndebele','nv'=>'Navajo','ny'=>'Nyanja','oc'=>'Occitan (post 1500)','oj'=>'Ojibwa','om'=>'Oromo','or'=>'Oriya','ory'=>'Oriya (individual language)','os'=>'Ossetian','pa'=>'Panjabi','pi'=>'Pali','pl'=>'Polish','ps'=>'Pushto','pt'=>'Portuguese','qu'=>'Quechua','rhg'=>'Rohingya','rm'=>'Romansh','rn'=>'Rundi','ro'=>'Romanian','ru'=>'Russian','rue'=>'Rusyn','rup'=>'Macedo-Romanian','rw'=>'Kinyarwanda','sa'=>'Sanskrit','sah'=>'Yakut','sc'=>'Sardinian','sd'=>'Sindhi','se'=>'Northern Sami','sg'=>'Sango','sh'=>'Serbo-Croatian','si'=>'Sinhala','sk'=>'Slovak','sl'=>'Slovenian','sm'=>'Samoan','sn'=>'Shona','so'=>'Somali','sq'=>'Albanian','sr'=>'Serbian','ss'=>'Swati','st'=>'Southern Sotho','su'=>'Sundanese','sv'=>'Swedish','sw'=>'Swahili','szl'=>'Silesian','ta'=>'Tamil','te'=>'Telugu','tg'=>'Tajik','th'=>'Thai','ti'=>'Tigrinya','tk'=>'Turkmen','tl'=>'Tagalog','tn'=>'Tswana','to'=>'Tonga (Tonga Islands)','tr'=>'Turkish','ts'=>'Tsonga','tt'=>'Tatar','tw'=>'Twi','twd'=>'Twents','ty'=>'Tahitian','tzm'=>'Central Atlas Tamazight','ug'=>'Uighur','uk'=>'Ukrainian','ur'=>'Urdu','uz'=>'Uzbek','ve'=>'Venda','vi'=>'Vietnamese','vo'=>'Volapük','wa'=>'Walloon','wo'=>'Wolof','xh'=>'Xhosa','xmf'=>'Mingrelian','yi'=>'Yiddish','yo'=>'Yoruba','za'=>'Zhuang','zh'=>'Chinese','zu'=>'Zulu');
lib/data/plurals.php 0000666 00000005201 15214141740 0010417 0 ustar 00 1,'am'=>1,'ar'=>2,'ary'=>2,'be'=>3,'bm'=>4,'bo'=>4,'br'=>1,'bs'=>3,'cs'=>5,'cy'=>6,'dz'=>4,'ff'=>1,'fr'=>1,'ga'=>7,'gd'=>8,'gv'=>9,'hr'=>10,'id'=>4,'ii'=>4,'iu'=>11,'ja'=>4,'ka'=>4,'kk'=>4,'km'=>4,'kn'=>4,'ko'=>4,'kw'=>11,'ky'=>4,'ln'=>1,'lo'=>4,'lt'=>12,'lv'=>13,'mg'=>1,'mi'=>1,'mk'=>14,'ms'=>4,'mt'=>15,'my'=>4,'nr'=>4,'oc'=>1,'pl'=>16,'ro'=>17,'ru'=>3,'sa'=>11,'sg'=>4,'sk'=>5,'sl'=>18,'sm'=>4,'sr'=>3,'su'=>4,'th'=>4,'ti'=>1,'tl'=>1,'to'=>4,'tt'=>4,'ug'=>4,'uk'=>3,'vi'=>4,'wa'=>1,'wo'=>4,'yo'=>4,'zh'=>4,''=>array(0=>array(0=>'n != 1',1=>array(0=>'one',1=>'other')),1=>array(0=>'n > 1',1=>array(0=>'one',1=>'other')),2=>array(0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100 >= 3 && n%100<=10 ? 3 : n%100 >= 11 && n%100<=99 ? 4 : 5',1=>array(0=>'zero',1=>'one',2=>'two',3=>'few',4=>'many',5=>'other')),3=>array(0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>array(0=>'one',1=>'few',2=>'other')),4=>array(0=>'0',1=>array(0=>'other')),5=>array(0=>'( n == 1 ) ? 0 : ( n >= 2 && n <= 4 ) ? 1 : 2',1=>array(0=>'one',1=>'few',2=>'other')),6=>array(0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 5',1=>array(0=>'zero',1=>'one',2=>'two',3=>'few',4=>'many',5=>'other')),7=>array(0=>'n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4',1=>array(0=>'one',1=>'two',2=>'few',3=>'many',4=>'other')),8=>array(0=>'n==1||n==11 ? 0 : n==2||n==12 ? 1 :(n >= 3 && n<=10)||(n >= 13 && n<=19)? 2 : 3',1=>array(0=>'one',1=>'two',2=>'few',3=>'other')),9=>array(0=>'n%10==1 ? 0 : n%10==2 ? 1 : n%20==0 ? 2 : 3',1=>array(0=>'one',1=>'two',2=>'few',3=>'other')),10=>array(0=>'n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2',1=>array(0=>'one',1=>'few',2=>'other')),11=>array(0=>'n == 1 ? 0 : n == 2 ? 1 : 2',1=>array(0=>'one',1=>'two',2=>'other')),12=>array(0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>array(0=>'one',1=>'few',2=>'other')),13=>array(0=>'n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2',1=>array(0=>'one',1=>'other',2=>'zero')),14=>array(0=>'( n % 10 == 1 && n % 100 != 11 ) ? 0 : 1',1=>array(0=>'one',1=>'other')),15=>array(0=>'(n==1 ? 0 : n==0||( n%100>1 && n%100<11)? 1 :(n%100>10 && n%100<20)? 2 : 3)',1=>array(0=>'one',1=>'few',2=>'many',3=>'other')),16=>array(0=>'(n==1 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>array(0=>'one',1=>'few',2=>'other')),17=>array(0=>'(n==1 ? 0 :(((n%100>19)||(( n%100==0)&&(n!=0)))? 2 : 1))',1=>array(0=>'one',1=>'few',2=>'other')),18=>array(0=>'n%100==1 ? 0 : n%100==2 ? 1 : n%100==3||n%100==4 ? 2 : 3',1=>array(0=>'one',1=>'two',2=>'few',3=>'other'))));
loco.php 0000666 00000010251 15214141740 0006213 0 ustar 00
srctplloco.phplanguageslanguages/loco-translate.pottmplibpubtest
pub/js/debug.js 0000666 00000002354 15214141740 0007401 0 ustar 00 /**
* Run Javascript diagnostics
*/
!function( loco, $ ){
// check tick symbol is readable by JavaScript as UTF-8
// E2\x9C\x93 => 0x2713
var span = $('#loco-utf8-check'),
tick = span[0].textContent
;
if( 1 !== tick.length || 0x2713 !== tick.charCodeAt(0) ){
loco.notices.warn("This page has a problem rendering UTF-8").stick();
}
// show custom endpoint if set in global
if( window.ajaxurl ){
$('#loco-ajax-url').text( window.ajaxurl );
}
// check UTF-8 passes to Ajax and back without a problem
function onAjaxTestPass( data, status, xhr ){
if( data && data.ping ){
$('#loco-ajax-check').text( data.ping );
}
else {
onAjaxTestFail( xhr, status, data && data.error && data.error.message );
}
}
function onAjaxTestFail( xhr, error, message ){
if( 'success' !== error ){
message = loco.ajax.parse( loco.ajax.strip( xhr.responseText ) );
}
$('#loco-ajax-check').text( 'FAILED: '+message ).addClass('loco-danger');
}
loco.ajax.post( 'ping', {echo:'\u039F\u039A \u2713'}, onAjaxTestPass, onAjaxTestFail );
}( window.locoScope, window.jQuery ); pub/js/poview.js 0000666 00000015303 15214141740 0007622 0 ustar 00 /**
* Script for PO/POT source view screen
*/
!function( window, document, $ ){
var $modal,
loco = window.locoScope,
conf = window.locoConf,
view = document.getElementById('loco-po');
// index messages and enable text filter
! function( view, dict ){
var min, max,
texts = [],
blocks = [],
valid = true,
filtered = false,
items = $(view).find('li')
;
function flush(){
if( texts.length ){
blocks.push( [min,max] );
dict.push( texts );
texts = [];
}
min = null;
}
items.each( function( i, li ){
var text, $li = $(li);
// empty line indicates end of message
if( $li.find('span.po-none').length ) {
flush();
}
// context indexable if po-text present
else {
max = i;
if( null == min ){
min = i;
}
if( text = $li.find('.po-text').text() ){
texts = texts.concat( text.replace(/\\[ntvfab\\"]/g, ' ').split(' ') );
}
}
} );
flush();
// indexing done, enable filtering
// TODO for filtering to perform well, we must perform off-screen buffering of redundant
nodes
// TODO found text highlighting. (more complex than first thought)
function ol( start ){
return $('').attr('start',start).appendTo(view);
}
function filter(s){
var i, block, found = dict.find(s), f = -1, length = found.length, $ol;
$('ol',view).remove();
if( length ){
while( ++f < length ){
block = blocks[ found[f] ];
i = block[0];
$ol = ol( i+1 );
for( ; i <= block[1]; i++ ){
$ol.append( items[i]/*.cloneNode(true)*/ );
}
}
validate(true);
}
else {
validate(false);
// translators: When text filtering reduces to an empty view
ol(0).append( $('
').text( loco.l10n._('Nothing matches the text filter') ) );
}
filtered = true;
resize();
};
function unfilter(){
if( filtered ){
validate(true);
filtered = false;
$('ol',view).remove();
ol(1).append( items );
resize();
}
}
function validate( bool ){
if( valid !== bool ){
$('#loco-content')[ bool ? 'removeClass' : 'addClass' ]('loco-invalid');
valid = bool;
}
}
loco.watchtext( $(view.parentNode).find('form.loco-filter')[0].q, function(q){
q ? filter(q) : unfilter();
} );
}( view, loco.fulltext.init() );
// OK to show view now. may have taken a while to render and index
$(view).removeClass('loco-loading');
// resize function fits scrollable viewport, accounting for headroom and touching bottom of screen.
var resize = function(){
function top( el, ancestor ){
var y = el.offsetTop||0;
while( ( el = el.offsetParent ) && el !== ancestor ){
y += el.offsetTop||0;
}
return y;
}
var fixHeight,
maxHeight = view.clientHeight - 2
;
return function(){
var topBanner = top( view, document.body ),
winHeight = window.innerHeight,
setHeight = winHeight - topBanner - 20
;
if( fixHeight !== setHeight ){
if( setHeight < maxHeight ){
view.style.height = String(setHeight)+'px';
}
else {
view.style.height = '';
}
fixHeight = setHeight;
}
};
}();
resize();
$(window).resize( resize );
// enable file reference links to open modal to ajax service
$(view).click( function(event){
var link = event.target;
if( link.hasAttribute('href') ){
event.preventDefault();
getModal().html('').dialog('option','title','Loading..').off('dialogopen').dialog('open').on('dialogopen',onModalOpen);
var postdata = $.extend( { ref:link.textContent, path:conf.popath }, conf.project||{} );
loco.ajax.post( 'fsReference', postdata, onRefSource, onRefError );
return false;
}
} );
// http://api.jqueryui.com/dialog/
function getModal(){
return $modal || ( $modal = $('').dialog( {
dialogClass : 'loco-modal',
modal : true,
autoOpen : false,
closeOnEscape : true,
resizable : false,
height : 500
} ) );
}
// when reference pulling fails (e.g. file not found, or line nonexistant)
function onRefError( xhr, error, message ){
$error = $('').text( message );
getModal().dialog('close').html('').dialog('option','title','Error').append($error).dialog('open');
}
// display reference source when received via ajax response
function onRefSource( result ){
var code = result && result.code;
if( ! code ){
return;
}
var i = -1,
length = code.length,
$ol = $('').attr('class',result.type);
while( ++i < length ){
$('').html( code[i] ).appendTo($ol);
}
// Highlight referenced line
$ol.find('li').eq( result.line - 1 ).attr('class','highlighted');
// TODO enable highlight toggling of other lines via click/drag etc..
getModal().dialog('close').html('').dialog('option','title', result.path+':'+result.line).append($ol).dialog('open');
}
// scroll to first highlighted line of code once modal open
function onModalOpen( event, ui ){
var div = event.target,
line = $(div).find('li.highlighted')[0],
yAbs = line && line.offsetTop || 0, // <- position of line relative to container
yVis = Math.floor( div.clientHeight / 2 ), // <- target position of line relative to view port
yAdj = Math.max( 0, yAbs - yVis ) // scroll required to move line to visual position
;
div.scrollTop = yAdj;
}
}( window, document, jQuery ); pub/js/potinit.js 0000666 00000002025 15214141740 0007774 0 ustar 00 /**
* Script for POT file initializing page
*/
!function( window, document, $ ){
var loco = window.locoScope,
fsHook = document.getElementById('loco-fs'),
elForm = document.getElementById('loco-potinit')
;
// enable disable form submission
/*function setFormDisabled( disabled ){
$(elForm).find('button.button-primary').each( function( i, button ){
button.disabled = disabled;
} );
}*/
// Submit form to Ajax end point when ..erm.. submitted
function onXgettextSuccess( data ){
var href = data && data.redirect;
if( href ){
location.assign( href );
}
}
function process( event ){
event.preventDefault();
loco.ajax.submit( event.target, onXgettextSuccess );
return false;
}
$(elForm)
.submit( process );
//setFormDisabled( false );
if( fsHook ){
loco.fs.init(fsHook).setForm( elForm );
}
}( window, document, jQuery ); pub/js/podiff.js 0000666 00000011220 15214141740 0007552 0 ustar 00 /**
* Script for PO/POT file diff renderer and roll back function
*/
!function( window, document, $ ){
var xhr,
cache = [],
conf = window.locoConf,
loco = window.locoScope,
revOffset = 0,
maxOffset = conf.paths.length - 2,
elRoot = document.getElementById('loco-ui'),
fsHook = document.getElementById('loco-fs'),
elForm = elRoot.getElementsByTagName('form').item(0),
buttons = elRoot.getElementsByTagName('button'),
$metas = $(elRoot).find('div.diff-meta'),
prevBut = buttons.item(0),
nextBut = buttons.item(1)
;
if( fsHook && elForm ){
loco.fs
.init( fsHook )
.setForm( elForm );
}
function showLoading(){
return $(elRoot).addClass('loading');
}
function hideLoading(){
return $(elRoot).removeClass('loading');
}
function setContent( src ){
return $(elRoot).find('div.diff').html( src );
}
function setError( message ){
hideLoading();
return $('').text( message ).appendTo( setContent('') );
}
// applying line numbers in JS is easier than hacking HTML on the back end, because dom is parsed
function applyLineNumbers( i, tbody ){
var i, cells,
rows = tbody.getElementsByTagName('tr'),
nrow = rows.length,
data = tbody.getAttribute('data-diff').split(/\D+/),
xmin = data[0], xmax = data[1],
ymin = data[2], ymax = data[3]
;
/*function lpad( n, max ){
var str = String( n ),
len = String(max).length;
while( str.length < len ){
str = '\xA0'+str;
}
return str;
}*/
function apply( td, num, max ){
if( num <= max ){
$('').text( String(num) ).prependTo( td );
}
}
for( i = 0; i < nrow; i++ ){
cells = rows[i].getElementsByTagName('td');
apply( cells[0], xmin++, xmax );
apply( cells[2], ymin++, ymax );
}
}
function loadDiff( offset ){
if( xhr ){
xhr.abort();
}
// use in-code cache
var src = cache[offset];
if( null != src ){
setContent( src );
hideLoading();
return;
}
// remote load required
setContent('');
showLoading();
xhr = loco.ajax.post( 'diff',
{
lhs: conf.paths[offset],
rhs: conf.paths[offset+1]
},
function( r, state, _xhr ){
if( _xhr === xhr ){
if( src = r && r.html ){
cache[offset] = src;
setContent( src ).find('tbody').each( applyLineNumbers );
hideLoading();
}
else {
setError( r && r.error || 'Unknown error' );
}
}
},
function( _xhr, error, message ){
if( _xhr === xhr ){
xhr = null;
// ajax utilty should have extracted meaningful error
setError('Failed to generate diff');
}
}
);
}
/**
* Go to next revision (newer)
*/
function goNext( event ){
event.preventDefault();
go( revOffset - 1 );
return false;
}
/**
* Go to previous revision (older)
*/
function goPrev( event ){
event.preventDefault();
go( revOffset + 1 );
return false;
}
function go( offset ){
if( offset >=0 && offset <= maxOffset ){
revOffset = offset;
loadDiff( offset );
redraw();
}
}
function redraw(){
var i = revOffset, j = i + 1;
// lock navigation to available range
prevBut.disabled = i >= maxOffset;
nextBut.disabled = i <= 0;
//
$metas.addClass('jshide').removeClass('diff-meta-current');
$metas.eq(i).removeClass('jshide').addClass('diff-meta-current');
$metas.eq(j).removeClass('jshide');
}
// enable navigation if there is something to navigate to
if( maxOffset ){
$(prevBut).click( goPrev ).parent().removeClass('jshide');
$(nextBut).click( goNext ).parent().removeClass('jshide');
}
// load default diff, which is current version compared to most recent backup
go( 0 );
}( window, document, jQuery ); pub/js/editor.js 0000666 00000044260 15214141740 0007603 0 ustar 00 /**
* Script for PO file editor pages
*/
!function( window, $ ){
var loco = window.locoScope,
conf = window.locoConf,
syncParams = null,
saveParams = null,
ajaxUpload = conf.multipart,
// UI translation
translator = loco.l10n,
sprintf = loco.string.sprintf,
// PO file data
locale = conf.locale,
messages = loco.po.init( locale ).wrap( conf.powrap ),
template = ! locale,
// form containing action buttons
elForm = document.getElementById('loco-actions'),
filePath = conf.popath,
syncPath = conf.potpath,
// file system connect when file is locked
elFilesys = document.getElementById('loco-fs'),
fsConnect = elFilesys && loco.fs.init( elFilesys ),
// prevent all write operations if readonly mode
readonly = conf.readonly,
editable = ! readonly,
// Editor components
editor,
saveButton,
innerDiv = document.getElementById('loco-editor-inner')
;
// warn if ajax uploads are enabled but not supported
if( ajaxUpload && ! ( window.FormData && window.Blob ) ){
ajaxUpload = false;
loco.notices.warn("Your browser doesn't support Ajax file uploads. Falling back to standard postdata");
}
/**
*
*/
function doSyncAction( callback ){
function onSuccess( result ){
var info = [],
doc = messages,
exp = result.po,
src = result.pot,
pot = loco.po.init().load( exp ),
done = doc.merge( pot ),
nadd = done.add.length,
ndel = done.del.length,
t = translator;
// reload even if unchanged, cos indexes could be off
editor.load( doc );
// Show summary
if( nadd || ndel ){
if( src ){
// Translators: Where %s is the name of the POT template file. Message appears after sync
info.push( sprintf( t._('Merged from %s'), src ) );
}
else {
// Translators: Message appears after sync operation
info.push( t._('Merged from source code') );
}
// Translators: Summary of new strings after running in-editor Sync
nadd && info.push( sprintf( t._n('1 new string added','%s new strings added', nadd ), nadd ) );
// Translators: Summary of existing strings that no longer exist after running in-editor Sync
ndel && info.push( sprintf( t._n('1 obsolete string removed','%s obsolete strings removed', ndel ), ndel ) );
// editor thinks it's saved, but we want the UI to appear otherwise
$(innerDiv).trigger('poUnsaved',[]);
updateStatus();
// debug info in lieu of proper merge confirmation:
window.console && debugMerge( console, done );
}
else if( src ){
// Translators: Message appears after sync operation when nothing has changed. %s refers to a POT file.
info.push( sprintf( t._('Already up to date with %s'), src ) );
}
else {
// Translators: Message appears after sync operation when nothing has changed
info.push( t._('Already up to date with source code') );
}
loco.notices.success( info.join('. ') );
$(innerDiv).trigger('poMerge',[result]);
// done sync
callback && callback();
}
loco.ajax.post( 'sync', syncParams, onSuccess, callback );
}
function debugMerge( console, result ){
var i = -1, t = result.add.length;
while( ++i < t ){
console.log(' + '+result.add[i].source() );
}
i = -1, t = result.del.length;
while( ++i < t ){
console.log(' - '+result.del[i].source() );
}
}
/**
* @param params {Object}
* @return FormData
*/
function initMultiPart( params ){
var p, data = new FormData;
for( p in params ){
if( params.hasOwnProperty(p) ) {
data.append(p, params[p]);
}
}
return data;
}
/**
* Post full editor contents to "posave" endpoint
*/
function doSaveAction( callback ){
function onSuccess( result ){
callback && callback();
editor.save( true );
// Update saved time update
$('#loco-po-modified').text( result.datetime||'[datetime error]' );
}
var postData = $.extend( {locale:String(messages.locale()||'')}, saveParams||{} );
if( fsConnect ){
fsConnect.applyCreds(postData);
}
// submit PO as concrete file if configured
if( ajaxUpload ){
postData = initMultiPart(postData);
postData.append('po', new Blob([String(messages)],{type:'application/x-gettext'}), String(postData.path).split('/').pop()||'untitled.po' );
}
else {
postData.data = String(messages);
}
loco.ajax.post( 'save', postData, onSuccess, callback );
}
function saveIfDirty(){
editor.dirty && doSaveAction();
}
function onUnloadWarning(){
// Translators: Warning appears when user tries to refresh or navigate away when editor work is unsaved
return translator._("Your changes will be lost if you continue without saving");
}
function registerSaveButton( button ){
saveButton = button;
// enables and disable according to save/unsave events
editor
.on('poUnsaved', function(){
enable();
$(button).addClass( 'button-primary loco-flagged' );
} )
.on('poSave', function(){
disable();
$(button).removeClass( 'button-primary loco-flagged' );
} )
;
function disable(){
button.disabled = true;
}
function enable(){
button.disabled = false;
}
function think(){
disable();
$(button).addClass('loco-loading');
}
function unthink(){
enable();
$(button).removeClass('loco-loading');
}
saveParams = $.extend( { path: filePath }, conf.project||{} );
$(button).click( function(event){
event.preventDefault();
think();
doSaveAction( unthink );
return false;
} );
return true;
};
function registerSyncButton( button ){
var project = conf.project;
if( project ){
function disable(){
button.disabled = true;
}
function enable(){
button.disabled = false;
}
function think(){
disable();
$(button).addClass('loco-loading');
}
function unthink(){
enable();
$(button).removeClass('loco-loading');
}
// Only permit sync when document is saved
editor
.on('poUnsaved', function(){
disable();
} )
.on('poSave', function(){
enable();
} )
;
// params for sync end point
syncParams = {
bundle: project.bundle,
domain: project.domain,
type: template ? 'pot' : 'po',
sync: syncPath||''
};
// enable syncing on button click
$(button)
.click( function(event){
event.preventDefault();
think();
doSyncAction( unthink );
return false;
} )
//.attr('title', syncPath ? sprintf( translator._('Update from %s'), syncPath ) : translator._('Update from source code') )
;
enable();
}
return true;
}
function registerFuzzyButton( button ){
var toggled = false,
enabled = false
;
function redraw( message, state ){
// fuzziness only makes sense when top-level string is translated
var allowed = message && message.translated(0) || false;
if( enabled !== allowed ){
button.disabled = ! allowed;
enabled = allowed;
}
// toggle on/off according to new fuzziness
if( state !== toggled ){
$(button)[ state ? 'addClass' : 'removeClass' ]('inverted');
toggled = state;
}
}
// state changes depending on whether an asset is selected and is fuzzy
editor
.on('poSelected', function( event, message ){
redraw( message, message && message.fuzzy() || false );
} )
.on( 'poEmpty', function( event, blank, message, pluralIndex ){
if( 0 === pluralIndex && blank === enabled ){
redraw( message, toggled );
}
} )
.on( 'poFuzzy', function( event, message, newState ){
redraw( message, newState );
} )
;
// click toggles current state
$(button).click( function( event ){
event.preventDefault();
editor.fuzzy( ! editor.fuzzy() );
return false;
} );
return true;
};
function registerRevertButton( button ){
// No need for revert when document is saved
editor
.on('poUnsaved', function(){
button.disabled = false;
} )
.on('poSave', function(){
button.disabled = true;
} )
;
// handling unsaved state prompt with onbeforeunload, see below
$(button).click( function( event ){
event.preventDefault();
location.reload();
return false;
} );
return true;
};
function registerInvisiblesButton( button ){
var $button = $(button);
button.disabled = false;
editor.on('poInvs', function( event, state ){
$button[ state ? 'addClass' : 'removeClass' ]('inverted');
});
$button.click( function( event ){
event.preventDefault();
editor.setInvs( ! editor.getInvs() );
return false;
} );
locoScope.tooltip.init($button);
return true;
}
function registerCodeviewButton( button ){
var $button = $(button);
button.disabled = false;
$button.click( function(event){
event.preventDefault();
var state = ! editor.getMono();
editor.setMono( state );
$button[ state ? 'addClass' : 'removeClass' ]('inverted');
return false;
} );
locoScope.tooltip.init($button);
return true;
};
function registerAddButton( button ){
button.disabled = false;
$(button).click( function( event ){
event.preventDefault();
// Need a placeholder guaranteed to be unique for new items
var i = 1, baseid, msgid, regex = /(\d+)$/;
msgid = baseid = 'New message';
while( messages.get( msgid ) ){
i = regex.exec(msgid) ? Math.max(i,RegExp.$1) : i;
msgid = baseid+' '+( ++i );
}
editor.add( msgid );
return false;
} );
return true;
};
function registerDelButton( button ){
button.disabled = false;
$(button).click( function(event){
event.preventDefault();
editor.del();
return false;
} );
return true;
};
function registerDownloadButton( button, id ){
button.disabled = false;
$(button).click( function( event ){
var form = button.form,
path = filePath;
// swap out path
if( 'binary' === id ){
path = path.replace(/\.po$/,'.mo');
}
form.path.value = path;
form.source.value = messages.toString();
// allow form to submit
return true;
} );
return true;
}
// event handler that stops dead
function noop( event ){
event.preventDefault();
return false;
}
/*/ dummy function for enabling buttons that do nothing (or do something inherently)
function registerNoopButton( button ){
return true;
}*/
/**
* Update status message above editor.
* This is dynamic version of PHP Loco_gettext_Metadata::getProgressSummary
* TODO implement progress bar, not just text.
*/
function updateStatus(){
var t = translator,
stats = editor.stats(),
total = stats.t,
fuzzy = stats.f,
empty = stats.u,
// Translators: Shows total string count at top of editor
stext = sprintf( t._n('1 string','%s strings',total ), total.format(0) ),
extra = [];
if( locale ){
// Translators: Shows percentage translated at top of editor
stext = sprintf( t._('%s%% translated'), stats.p.replace('%','') ) +', '+ stext;
// Translators: Shows number of fuzzy strings at top of editor
fuzzy && extra.push( sprintf( t._('%s fuzzy'), fuzzy.format(0) ) );
// Translators: Shows number of untranslated strings at top of editor
empty && extra.push( sprintf( t._('%s untranslated'), empty.format(0) ) );
if( extra.length ){
stext += ' ('+extra.join(', ')+')';
}
}
$('#loco-po-status').text( stext );
}
/**
* Enable text filtering
*/
function initSearchFilter( elSearch ){
editor.searchable( loco.fulltext.init() );
// prep search text field
elSearch.disabled = false;
elSearch.value = '';
function showValidFilter( numFound ){
$(elSearch.parentNode)[ numFound || null == numFound ? 'removeClass' : 'addClass' ]('invalid');
}
var listener = loco.watchtext( elSearch, function( value ){
var numFound = editor.filter( value, true );
showValidFilter( numFound );
} );
editor
.on( 'poFilter', function( event, value, numFound ){
listener.val( value||'' );
showValidFilter( numFound );
} )
.on( 'poMerge', function( event, result ){
var value = listener.val();
value && editor.filter( value );
} )
;
}
// resize function fits editor to screen, accounting for headroom and touching bottom of screen.
var resize = function(){
function top( el, ancestor ){
var y = el.offsetTop||0;
while( ( el = el.offsetParent ) && el !== ancestor ){
y += el.offsetTop||0;
}
return y;
}
var fixHeight,
minHeight = parseInt($(innerDiv).css('min-height')||0)
;
return function(){
var padBottom = 20,
topBanner = top( innerDiv, document.body ),
winHeight = window.innerHeight,
setHeight = Math.max( minHeight, winHeight - topBanner - padBottom )
;
if( fixHeight !== setHeight ){
innerDiv.style.height = String(setHeight)+'px';
fixHeight = setHeight;
}
};
}();
// ensure outer resize is handled before editor's internal resize
resize();
$(window).resize( resize );
// initialize editor
innerDiv.innerHTML = '';
editor = loco.po.ed
.init( innerDiv )
.localise( translator )
;
loco.po.kbd
.init( editor )
.add( 'save', saveIfDirty )
.enable('copy','clear','enter','next','prev','fuzzy','save','invis')
;
// initialize toolbar button actions
var buttons = {
// help: registerNoopButton,
save: editable && registerSaveButton,
sync: editable && registerSyncButton,
revert: registerRevertButton,
// editor mode togglers
invs: registerInvisiblesButton,
code: registerCodeviewButton,
// downloads / post-throughs
source: registerDownloadButton,
binary: template ? null : registerDownloadButton
};
// POT only
if( template ){
buttons.add = editable && registerAddButton;
buttons.del = editable && registerDelButton;
}
// PO only
else {
buttons.fuzzy = registerFuzzyButton;
};
$('#loco-toolbar').find('button').each( function(i,el){
var id = el.getAttribute('data-loco'), register = buttons[id];
register && register(el,id) || $(el).hide();
} );
// disable submit on dummy form
$(elForm).submit( noop );
// enable text filtering
initSearchFilter( document.getElementById('loco-search') );
// editor event behaviours
editor
.on('poUnsaved', function(){
window.onbeforeunload = onUnloadWarning;
} )
.on('poSave', function(){
updateStatus();
window.onbeforeunload = null;
} )
.on( 'poUpdate', updateStatus );
;
// load raw message data
messages.load( conf.podata );
// ready to render editor
editor.load( messages );
// locale should be cast to full object once set in editor
if( locale = editor.targetLocale ){
locale.isRTL() && $(innerDiv).addClass('trg-rtl');
}
// enable template mode when no target locale
else {
editor.unlock();
}
// ok, editor ready
updateStatus();
// clean up
delete window.locoConf;
conf = buttons = null;
}( window, jQuery ); pub/js/delete.js 0000666 00000000476 15214141740 0007560 0 ustar 00 /**
* Script for file delete operation
*/
!function( window, document, $ ){
var fsHook = document.getElementById('loco-fs'),
elForm = document.getElementById('loco-del')
;
if( fsHook && elForm ){
window.locoScope.fs.init(fsHook).setForm(elForm);
}
}( window, document, jQuery ); pub/js/setup.js 0000666 00000006511 15214141740 0007452 0 ustar 00 /**
* Script for bundle setup page
* TODO translations
*/
!function( window, document, $ ){
/**
* Look up bundle configuration on remote server
*/
function find( vendor, slug, version ){
function onFailure(){
if( timer ){
destroy();
onTimeout();
}
};
function onResponse( data, status, obj ){
if( timer ){
destroy();
var match = data && data.exact,
status = data && data.status
;
if( match ){
setJson( match );
}
else if( 404 === status ){
unsetJson("Sorry, we don't know a bundle by this name");
}
else {
loco.notices.debug( data.error || 'Unknown server error' );
onTimeout();
}
}
};
function onTimeout(){
unsetJson('Failed to contact remote API');
timer = null;
}
function destroy(){
if( timer ){
clearTimeout( timer );
timer = null;
}
}
var timer = setTimeout( onTimeout, 3000 );
unsetJson('');
$.ajax( {
url: conf.apiUrl+'/'+vendor+'/'+slug+'.jsonp?version='+encodeURIComponent(version),
dataType: 'jsonp',
success: onResponse,
error: onFailure,
cache: true
} );
return {
abort: destroy
};
}
function setJson( json ){
elForm['json-content'].value = json;
$('#loco-remote-empty').hide();
//$('#loco-remote-query').hide();
$('#loco-remote-found').show();
}
function unsetJson( message ){
elForm['json-content'].value = '';
//$('#loco-remote-query').show();
$('#loco-remote-empty').show().find('span').text( message );
$('#loco-remote-found').hide().removeClass('jshide');
}
function onFindClick( event ){
event.preventDefault();
finder && finder.abort();
finder = find( elForm.vendor.value, elForm.slug.value, elForm.version.value );
return false;
}
function onCancelClick( event ){
event.preventDefault();
unsetJson('');
return false;
}
function setVendors( list ){
var i = -1,
value, label,
length = list.length,
$select = $(elForm.vendor).html('')
;
while( ++i < length ){
value = list[i][0];
label = list[i][1];
$select.append( $('').attr('value',value).text(label) );
}
}
var loco = window.locoScope,
conf = window.locoConf,
finder,
elForm = document.getElementById('loco-remote'),
$findButt = $(elForm).find('button[type="button"]').click( onFindClick ),
$resetButt = $(elForm).find('input[type="reset"]').click( onCancelClick );
// pull vendor list
$.ajax( {
url: conf.apiUrl+'/vendors.jsonp',
dataType: 'jsonp',
success: setVendors,
cache: true
} );
}( window, document, jQuery ); pub/js/move.js 0000666 00000002633 15214141740 0007261 0 ustar 00 /**
* Script for file move operation
*/
!function( window, document, $ ){
var fsConn,
destPath,
fsHook = document.getElementById('loco-fs'),
elForm = document.getElementById('loco-move'),
origPath = elForm.path.value
;
function setFormDisabled( disabled ){
$(elForm).find('button.button-primary').each( function(i,button){
button.disabled = disabled;
} );
}
function onFsConnect( valid ){
setFormDisabled( ! ( valid && destPath ) );
}
function validate(event){
var field = event.target||{}, value;
if( 'dest' === field.name && ( field.checked || 'text' === field.type ) ){
value = field.value;
if( value && value !== destPath ){
destPath = value;
setFormDisabled(true);
// check chosen target permissions
if( origPath !== value ){
fsHook.dest.value = value;
fsConn.connect();
}
}
}
}
function process( event ){
if( destPath ){
return true;
}
event.preventDefault();
return false;
}
if( fsHook && elForm ){
fsConn = window.locoScope.fs.init(fsHook).setForm(elForm).listen(onFsConnect);
$(elForm).change(validate).submit(process);
}
}( window, document, jQuery );
pub/js/min/admin.js 0000666 00000361521 15214141740 0010172 0 ustar 00 (function(w,y,h,S){var m=function(){var c={};return{register:function(f,h){c[f]=h},require:function(f,h){var l=c[f];if(!l)throw Error('CommonJS error: failed to require("'+h+'")');return l}}}();m.register("$1",function(c,f,h){function l(b){var e=typeof b;if("string"===e)if(/[^ <>!=()%^&|?:n0-9]/.test(b))console.error("Invalid plural: "+b);else return new Function("n","return "+b);"function"!==e&&(b=function(a){return 1!=a});return b}c.init=function(b){function e(d,b,e){return(d=a[d])&&d[e]?d[e]:b||
""}b=l(b);var a={};return{_:function(a){return e(a,a,0)},_x:function(a,b){return e(b+"\u0004"+a,a,0)},_n:function(a,p,k){k=Number(b(k));isNaN(k)&&(k=0);return e(a,k?p:a,k)},load:function(d){a=d||{};return this},pluraleq:function(a){b=l(a);return this}}};return c}({},w,y));m.register("$2",function(c,f,h){Array.prototype.indexOf||(Array.prototype.indexOf=function(c){if(null==this)throw new TypeError;var b,e=Object(this),a=e.length>>>0;if(0===a)return-1;b=0;1=a)return-1;for(b=0<=b?b:Math.max(a-Math.abs(b),0);b');p=h('');k=h('');n=h('