Index: bb-theme/plugins/topic.php
===================================================================
--- bb-theme/plugins/topic.php	(revision 9225)
+++ bb-theme/plugins/topic.php	(working copy)
@@ -44,7 +44,7 @@
 </style>
 <?php endif; ?>
 
-	<h2 itemprop="name"><?php echo apply_filters( 'topic_title', $topic->topic_title ); ?></h2>
+	<h2 itemprop="name"><?php echo apply_filters( 'topic_title', $svn_tracker->translate( 'title', $topic->topic_title ) ); ?></h2>
 </div>
 
 <div id="plugin-description">
Index: extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php
===================================================================
--- extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php	(revision 9225)
+++ extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php	(working copy)
@@ -37,6 +37,12 @@
 
 	var $popular_cache_key = 'popular_plugins11';
 
+	// TODO: remove when we launch for all plugins.
+	var $translated_plugins = array(
+		'blogware-importer',
+		'livejournal-importer',
+	);
+
 	function __construct( $db, $svn_url, $svn_admin_class = false ) {
 		$this->db = $db;
 		if ( $svn_admin_class ) {
@@ -93,13 +99,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();
@@ -277,9 +288,127 @@
 			$content = preg_replace_callback( "#<img class='screenshot' src='(screenshot-[^']+)' alt='[^']+' />#", array( &$this, '_screenshot_img_tag' ), $content );
 		}
 
+		return $this->translate( $show, $content );
+	}
+
+	function translate( $key, $content ) {
+		global $topic;
+
+		if ( empty( $key ) || empty( $content ) )
+			return $content;
+
+		/*
+		 * DEBUG: Getting $language from host name, should use $locale and/or $l10n globals
+		 * once set properly when on localized site and get it from that.
+		 * $locale is currently en_US on localized sites (eg: fr.wordpress.org)
+		 * $i18n is currently false on localized sites (eg: fr.wordpress.org)
+		 */
+		$language = str_replace( '.wordpress.org', '', $_SERVER[ 'SERVER_NAME' ] );
+
+		if ( empty( $language ) || 2 !== strlen( $language ) )
+			return $content;
+
+		$slug = $topic->plugin_san;
+
+		// DEBUG: in_array check is because we'll start the program with a finite list of plugins
+		// TODO: remove when we launch for all plugins.
+		if ( empty( $slug ) || ! in_array( $slug, $this->translated_plugins ) )
+			return $content;
+
+		$branch    = ( empty( $topic->stable_tag ) || 'trunk' === $topic->stable_tag ) ? 'dev' : 'stable';
+
+		// Try to get a single original with the whole content first (title, etc), or get them all otherwise.
+		$originals = $this->_search_gp_original( $slug, $branch, $content );
+		if ( empty( $originals ) )
+			$originals = $this->_get_gp_originals( $slug, $branch );
+
+		if ( empty( $originals ) )
+			return $content;
+
+		$translation_set_id = $this->_get_gp_translation_set_id( $slug, $branch, $language );
+
+		if ( empty( $translation_set_id ) )
+			return $content;
+
+		foreach ( $originals as $original ) {
+			if ( empty( $original->id ) )
+				continue;
+
+			$translation = $this->db->get_var( $this->db->prepare(
+				'SELECT translation_0 FROM translate_translations WHERE original_id = %d AND translation_set_id = %d AND status = %s',
+				$original->id, $translation_set_id, 'current'
+			) );
+
+			if ( empty( $translation ) )
+				continue;
+
+			$content = $this->_translate_gp_original( $original->singular, $translation, $content );
+		}
+
 		return $content;
 	}
 
+	function _get_gp_branch_id( $slug, $branch ) {
+		$branch_id = $this->db->get_var( $this->db->prepare(
+			'SELECT id FROM translate_projects WHERE path = %s',
+			"wp-plugins/{$slug}/{$branch}-readme"
+		) );
+
+		return ( empty( $branch_id ) ) ? 0 : $branch_id;
+	}
+
+	function _get_gp_originals( $slug, $branch ) {
+		$branch_id = $this->_get_gp_branch_id( $slug, $branch );
+
+		if ( empty( $branch_id ) )
+			return array();
+
+		$originals = $this->db->get_results( $this->db->prepare(
+			'SELECT id, singular, comment FROM translate_originals WHERE project_id = %d AND status = %s',
+			$branch_id, '+active'
+		) );
+
+		if ( empty( $originals ) )
+			return array();
+
+		return $originals;
+	}
+
+	function _get_gp_translation_set_id( $slug, $branch, $locale ) {
+		$branch_id = $this->_get_gp_branch_id( $slug, $branch );
+
+		if ( empty( $branch_id ) )
+			return 0;
+
+		$translation_set_id = $this->db->get_var( $this->db->prepare(
+			'SELECT id FROM translate_translation_sets WHERE project_id = %d AND locale = %s',
+			$branch_id, $locale ) );
+
+		return ( empty( $translation_set_id ) ) ? 0 : $translation_set_id;
+	}
+
+	function _search_gp_original( $slug, $branch, $str ) {
+		$branch_id = $this->_get_gp_branch_id( $slug, $branch );
+
+		if ( empty( $branch_id ) )
+			return array();
+
+		$original = $this->db->get_row( $this->db->prepare(
+			'SELECT id, singular, comment FROM translate_originals WHERE project_id = %d AND status = %s AND singular = %s',
+			$branch_id, '+active', $str
+		) );
+
+		if ( empty( $original ) )
+			return array();
+
+		return array( $original );
+	}
+
+	function _translate_gp_original( $original, $translation, $content) {
+		$content = str_replace( $original, $translation, $content );
+		return $content;
+	}
+
 	function _screenshot_shortcode( $matches ) {
 		return $this->_screenshot_image( $matches[1], $matches[2] );
 	}
@@ -1731,15 +1860,22 @@
 			return 'http://wordpress.org/support/view/plugin-reviews/' . $topic->topic_slug;
 		if ( bb_get_option( 'mod_rewrite' ) ) {
 			if ( 'description' == $section )
-				return rtrim( get_topic_link( $topic_id ), '/' ) . '/';
-			return rtrim( get_topic_link( $topic_id ), '/' ) . "/$section/";
+				return $this->localize_section_url( rtrim( get_topic_link( $topic_id ), '/' ) . '/' );
+			return  $this->localize_section_url( rtrim( get_topic_link( $topic_id ), '/' ) . "/$section/" );
 		} else {
 			if ( 'description' == $section )
-				return remove_query_arg( 'show', get_topic_link( $topic_id ) );
-			return add_query_arg( 'show', $section, get_topic_link( $topic_id ) );
+				return  $this->localize_section_url( remove_query_arg( 'show', get_topic_link( $topic_id ) ) );
+			return  $this->localize_section_url( add_query_arg( 'show', $section, get_topic_link( $topic_id ) ) );
 		}
 	}
 
+	function localize_section_url( $url ) {
+		$curr_host = $_SERVER[ 'SERVER_NAME' ];
+		if ( 'wordpress.org' != $curr_host && preg_match( '/\.wordpress\.org$/', $curr_host ) )
+			return str_replace( '://wordpress.org/', "://{$curr_host}/", $url );
+		return $url;
+	}
+
 	function section_title( $section ) {
 		switch ( $section ) {
 			case 'description':
@@ -1996,6 +2132,256 @@
 		return false;
 	}
 
+	function process_i18n( $slug, $branch = 'dev', $type = 'all' ) {
+		if ( empty( $slug ) )
+			return false;
+
+		// DEBUG: in_array check is because we'll start the program with a finite list of plugins
+		// TODO: remove when we launch for all plugins.
+		if ( ! in_array( $slug, $this->translated_plugins ) )
+			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' ) {
+		$slug = preg_replace( '|^/?([^/]+)/?.+?$|', '\1', $path_rel );
+
+		if ( empty( $slug ) || !preg_match( '/^[a-z0-9-]+$/i', $slug ) )
+			return false;
+
+		// DEBUG: in_array check is because we'll start the program with a finite list of plugins
+		// TODO: remove when we launch for all plugins.
+		if ( !in_array( $slug, $this->translated_plugins ) )
+			return false;
+
+		$export_path = $this->create_export( $path_rel );
+
+		if ( empty( $export_path ) || !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' );
+
+		// Create pot file from code
+		$pot_file = "./tmp-{$slug}.pot"; // Using tmp- prefix in case a plugin has $slug.pot committed
+		$makepot  = new MakePOT;
+
+		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' ) {
+		$slug = preg_replace( '|^/?([^/]+)/?.+?$|', '\1', $path_rel );
+
+		if ( empty( $slug ) || !preg_match( '/^[a-z0-9-]+$/i', $slug ) )
+			return false;
+
+		// DEBUG: in_array as separate check because we'll start the program with a finite list of plugins
+		// TODO: remove when we launch for all plugins.
+		if ( !in_array( $slug, $this->translated_plugins ) )
+			return false;
+
+		$export_path = $this->create_export( $path_rel );
+
+		if ( empty( $export_path ) || !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 = "./tmp-{$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 only work if the GlotPress project/sub-projects exist. To be improved, or must insure they do.
+	}
+
 	function get_all_roots( $via = 'local' ) {
 		global $bbdb;
 		$root_rels = false;
