Index: extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php
===================================================================
--- extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php	(revision 9208)
+++ extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php	(working copy)
@@ -93,13 +93,18 @@
 				bb_set_current_user( $user->ID );
 			}

-			if ( 1 < $argc && 'update' == $argv[1] ) {
+			if ( 1 < $argc && ( 'update' == $argv[1] || 'i18n' == $argv[1] ) ) {
 				if ( !isset( $_SERVER['REMOTE_ADDR'] ) )
 					$_SERVER['REMOTE_ADDR'] = '127.0.0.1';

 				if ( 2 == $argc ) // php bb-load.php update
 					return $this->process_changes();

+				if ( 'i18n' == $argv[1] ) {
+					// php bb-load.php i18n slug [dev|stable, default: dev] [code|readme|all, default: all]
+					return $this->process_i18n( $argv[2], ( 'stable' === $argv[3] ) ? $argv[3] : 'dev', $argv[4] );
+				}
+
 				switch ( $argv[2] ) {
 				case 'all' :
 					return $this->process_all();
@@ -1996,6 +2001,242 @@
 		return false;
 	}

+	function process_i18n( $slug, $branch = 'dev', $type = 'all' ) {
+		if ( empty( $slug ) )
+			return false;
+
+		if ( 'stable' !== $branch )
+			$branch = 'dev';
+
+		if ( 'code' !== $type && 'readme' !== $type )
+			$type = 'all';
+
+		$path_rel = "{$slug}/trunk/";
+
+		if ( 'stable' === $branch ) {
+			if ( false == ( $stable_tag = $this->get_stable_tag_dir_using( $path_rel ) ) ) {
+				// Can't get a stable tag, bail out
+				return false;
+			} else if ( 'trunk' == trim( $stable_tag['tag_dir'], '/' ) ) {
+				// If stable is trunk, then it's really same as dev, switch to that
+				$branch = 'dev';
+			} else {
+				// We're dealing with an actual stable tag, go for it
+				$path_rel = "{$slug}/{$stable_tag['tag_dir']}"; //
+			}
+		}
+
+		if ( 'code' === $type || 'all' === $type )
+			$this->process_code_i18n( $path_rel, $branch );
+
+		if ( 'readme' === $type || 'all' === $type )
+			$this->process_readme_i18n( $path_rel, $branch );
+
+		echo "Processed {$type} for {$path_rel}\n";
+		return true;
+	}
+
+	function process_code_i18n( $path_rel, $branch = 'dev' ) {
+		if ( false == ( $export_path = $this->create_export( $path_rel ) ) )
+			return false;
+
+		$slug = preg_replace( '|^/?([^/]+)/?.+?$|', '\1', $path_rel );
+
+		if ( empty( $slug ) || empty( $export_path ) || !preg_match( '/^[a-z0-9-]+$/i', $slug ) || !is_dir( $export_path ) )
+			return false;
+
+		$old_cwd = getcwd();
+		chdir( $export_path );
+
+		// Check for a plugin text domain declaration and loading, grep recursively, not necessarily in [slug].php
+		if ( ! shell_exec( 'grep -r --include "*.php" "Text Domain: ' . escapeshellarg( $slug ) . '" .' ) && ! shell_exec( 'grep -r --include "*.php" "\bload_plugin_textdomain\b" .' ) )
+			return false;
+
+		if ( !class_exists( 'PotExtMeta' ) )
+			require_once( __DIR__ . '/i18n-tools/pot-ext-meta.php' );
+
+		$pot_file      = "./{$slug}.pot";
+		$other_pot_loc = str_replace( './', './languages/', $pot_file );
+
+		if ( file_exists( $other_pot_loc ) ) {      // Some plugins have their pot file in /languages/, use it
+			$pot_file = $other_pot_loc;
+		} else if ( ! file_exists( $pot_file ) ) {  // BuddyPress, for EG, has theirs in the root's repo, use it
+			$makepot = new MakePOT;
+			// Create pot file from code
+			if ( ! $makepot->wp_plugin( '.', $pot_file, $slug ) || ! file_exists( $pot_file ) )
+				return false;
+		}
+
+		// DEBUG
+		// system( "cat {$pot_file}" );
+
+		$this->import_to_glotpress_project( $slug, $branch, $pot_file );
+
+		chdir( $old_cwd );
+		return true;
+	}
+
+	function process_readme_i18n( $path_rel, $branch = 'dev' ) {
+		if ( false == ( $export_path = $this->create_export( $path_rel ) ) )
+			return false;
+
+		$slug = preg_replace( '|^/?([^/]+)/?.+?$|', '\1', $path_rel );
+
+		if ( empty( $slug ) || empty( $export_path ) || !preg_match( '/^[a-z0-9-]+$/i', $slug ) || !is_dir( $export_path ) )
+			return false;
+
+		$old_cwd = getcwd();
+		chdir( $export_path );
+
+		$readme = $this->parse_readme_in( $path_rel );
+
+		if ( !class_exists( 'PO' ) )
+			require_once( __DIR__ . '/i18n-tools/pomo/po.php' );
+
+		$pot = new PO;
+
+		foreach ( array( 'name', 'license', 'short_description' ) as $key ) {
+			$readme[ $key ] = trim( $readme[ $key ] ) ;
+		}
+
+		// If empty or "sketchy", get the plugin name form the PHP file's headers
+		if ( empty( $readme['name'] ) || 'Plugin Name' == trim( $readme['name'] ) ) {
+			// -o in grep will make sure we don't get comments opening delimiters (//, /*) or spaces as part of string
+			$name_from_php  = trim( shell_exec( 'grep -o "\bPlugin Name:.*" ' . escapeshellarg( $slug ) . '.php' ) );
+			// Remove the header label
+			$name_from_php  = str_replace( 'Plugin Name:', '', $name_from_php );
+			// Do clean out potential comment closing delimiter (*/) out of string
+			$name_from_php  = preg_replace( '|^(.+)[\s]+?\*/$|', '\1', $name_from_php );
+			// Use trimmed results as plugin name
+			$readme['name'] = trim( $name_from_php );
+		}
+
+		if ( !empty( $readme['name'] ) ) {
+			$pot->add_entry( new Translation_Entry ( array(
+				'singular'           => $readme['name'],
+				'extracted_comments' => 'Plugin/theme name.',
+			) ) );
+		}
+
+		if ( !empty( $readme['license'] ) ) {
+			$pot->add_entry( new Translation_Entry ( array(
+				'singular'           => $readme['license'],
+				'extracted_comments' => 'License.',
+			) ) );
+		}
+
+		if ( !empty( $readme['short_description'] ) ) {
+			$pot->add_entry( new Translation_Entry ( array(
+				'singular'           => $readme['short_description'],
+				'extracted_comments' => 'Short description.',
+			) ) );
+		}
+
+		if ( !empty( $readme['screenshots'] ) ) {
+			foreach ( $readme['screenshots'] as $sshot_key => $sshot_desc ) {
+				$sshot_desc = trim( $sshot_desc );
+				$pot->add_entry( new Translation_Entry ( array(
+					'singular'           => $sshot_desc,
+					'extracted_comments' => 'Screenshot description.',
+				) ) );
+			}
+
+		}
+
+		if ( empty( $readme['sections'] ) )
+			$readme['sections'] = array();
+
+		// Adding remaining content as a section so it's processed by the same loop below
+		if ( !empty( $readme['remaining_content'] ) )
+			$readme['sections']['remaining_content'] = $readme['remaining_content'];
+
+		$strings = array();
+
+		foreach ( $readme['sections'] as $section_key => $section_text ) {
+			if ( 'screenshots' == $section_key )
+				continue;
+
+			/*
+			 * Scanned tags based on block elements found in Automattic_Readme::filter_text() $allowed.
+			 * Scanning H3/4, li, p and blockquote. Other tags are ignored  in strings (a, strong, cite, etc).
+			 * Processing notes:
+			 * * Don't normalize/modify original text, will be used as a search pattern in original doc in some use-cases.
+			 * * Using regexes over XML parsing for performance reasons, could move to the latter for more accuracy.
+			 */
+
+			if ( 'changelog' !== $section_key ) { // No need to scan non-translatable version headers in changelog
+				if ( preg_match_all( '|<h[3-4]+[^>]*>(.+)</h[3-4]+>|', $section_text, $matches ) ) {
+					if ( !empty( $matches[1] ) ) {
+						foreach ( $matches[1] as $text ) {
+							$strings = $this->handle_translator_comment( $strings, $text, "{$section_key} header" );
+						}
+					}
+				}
+			}
+
+			if ( preg_match_all( '|<li>(.+)</li>|', $section_text, $matches ) ) {
+				if ( !empty( $matches[1] ) ) {
+					foreach ( $matches[1] as $text ) {
+						$strings = $this->handle_translator_comment( $strings, $text, "{$section_key} list item" );
+					}
+				}
+			}
+
+			if ( preg_match_all( '|<p>(.+)</p>|', $section_text, $matches ) ) {
+				if ( !empty( $matches[1] ) ) {
+					foreach ( $matches[1] as $text ) {
+						$strings = $this->handle_translator_comment( $strings, $text, "{$section_key} paragraph" );
+					}
+				}
+			}
+
+			if ( preg_match_all( '|<blockquote>(.+)</blockquote>|', $section_text, $matches ) ) {
+				if ( !empty( $matches[1] ) ) {
+					foreach ( $matches[1] as $text ) {
+						$strings = $this->handle_translator_comment( $strings, $text, "{$section_key} block quote" );
+					}
+				}
+			}
+		}
+
+		foreach ( $strings as $text => $comments ) {
+			$pot->add_entry( new Translation_Entry ( array(
+				'singular'           => $text,
+				'extracted_comments' => 'Found in ' . implode( $comments, ", " ) . '.',
+			) ) );
+		}
+
+		$pot_file = "./{$slug}-readme.pot";
+		$pot->export_to_file( $pot_file );
+
+		// DEBUG
+		// system( "cat {$pot_file}" );
+
+		// import to GlotPress, dev or stable
+		$this->import_to_glotpress_project( $slug, "{$branch}-readme", $pot_file );
+
+		chdir( $old_cwd );
+		return true;
+	}
+
+	function handle_translator_comment( $array, $key, $val ) {
+		$val = trim( preg_replace( '/[^a-z0-9]/i', ' ', $val ) ); // cleanup key names for display
+		if ( empty( $array[ $key ] ) ) {
+			$array[ $key ] = array( $val );
+		} else if ( !in_array( $val, $array[ $key ] ) ) {
+			$array[ $key ][] = $val;
+		}
+		return $array;
+	}
+
+	function import_to_glotpress_project( $project, $branch, $file ) {
+		$cmd = 'php ' . __DIR__ . '/../../../translate/glotpress/scripts/import-originals.php -o po -p ' . escapeshellarg( "wp-plugins/{$project }/{$branch}" ) . ' -f ' . escapeshellarg( $file );
+		// DEBUG
+		// var_dump( $cmd );
+		system( $cmd );
+		// Note: this will nly work if the GlotPress project/sub-projects exist. To be improved, or must insure they are.
+	}
+
 	function get_all_roots( $via = 'local' ) {
 		global $bbdb;
 		$root_rels = false;