Index: extend/plugins-plugins/svn-track/class.dotorg-plugins-i18n.php
===================================================================
--- extend/plugins-plugins/svn-track/class.dotorg-plugins-i18n.php	(revision 0)
+++ extend/plugins-plugins/svn-track/class.dotorg-plugins-i18n.php	(working copy)
@@ -0,0 +1,502 @@
+<?php
+class Dotorg_Plugin_I18n {
+	var $db;
+	var $tracker;
+
+	// TODO: remove when we launch for all plugins.
+	var $translated_plugins = array(
+		'blogware-importer',
+		'livejournal-importer',
+	);
+
+	var $i18n_cache_group = 'plugin-i18n';
+
+	function __construct( $db, $tracker = null ) {
+		if ( !empty( $db ) && is_object( $db ) )
+			$this->db = $db;
+		if ( !empty( $tracker ) && is_object( $tracker ) )
+			$this->tracker = $tracker;
+		wp_cache_add_global_groups( $this->i18n_cache_group );
+	}
+
+	/*
+	 * ***********************
+	 * Processing
+	 * ***********************
+	 */
+
+	function process( $slug, $branch = 'dev', $type = 'all' ) {
+		if ( empty( $slug ) || empty( $this->tracker ) )
+			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->tracker->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( $path_rel, $branch );
+
+		if ( 'readme' === $type || 'all' === $type )
+			$this->process_readme( $path_rel, $branch );
+
+		echo "Processed {$type} for {$path_rel}\n";
+		return true;
+	}
+
+	function process_code( $path_rel, $branch = 'dev' ) {
+		if ( empty( $this->tracker ) )
+			return false;
+
+		$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->tracker->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( $path_rel, $branch = 'dev' ) {
+		if ( empty( $this->tracker ) )
+			return false;
+
+		$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->tracker->create_export( $path_rel );
+
+		if ( empty( $export_path ) || !is_dir( $export_path ) )
+			return false;
+
+		$old_cwd = getcwd();
+		chdir( $export_path );
+
+		$readme = $this->tracker->parse_readme_in( $path_rel );
+
+		$str_priorities = array();
+
+		if ( !class_exists( 'PO' ) )
+			require_once( __DIR__ . '/i18n-tools/pomo/po.php' );
+
+		$pot = new PO;
+
+		// No need for license, being in the directory implies GPLv2 or later. Add here otherwise.
+		foreach ( array( 'name', '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' => 'Name.',
+			) ) );
+
+			$str_priorities[ $readme['name'] ] = 1;
+		}
+
+		if ( !empty( $readme['short_description'] ) ) {
+			$pot->add_entry( new Translation_Entry ( array(
+				'singular'           => $readme['short_description'],
+				'extracted_comments' => 'Short description.',
+			) ) );
+
+			$str_priorities[ $readme['short_description'] ] = 1;
+		}
+
+		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 ( 'changelog' === $section_key )
+							$str_priorities[ $text ] = -1;
+					}
+				}
+			}
+
+			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 ( 'changelog' === $section_key )
+							$str_priorities[ $text ] = -1;
+					}
+				}
+			}
+
+			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" );
+						if ( 'changelog' === $section_key )
+							$str_priorities[ $text ] = -1;
+					}
+				}
+			}
+		}
+
+		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, $str_priorities );
+
+		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, $str_priorities = array() ) {
+		// Note: this will only work if the GlotPress project/sub-projects exist. To be improved, or must insure they do.
+		$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 );
+		if ( empty( $str_priorities ) )
+			return;
+		$branch_id = $this->get_gp_branch_id( $project, $branch );
+		// Set the string priorities in GP once the originals have been imported
+		if ( empty( $branch_id ) )
+			return;
+		foreach ( (array) $str_priorities as $str => $prio ) {
+			if ( 1 !== $prio && -1 !== $prio )
+				$prio = 0;
+			$res = $this->db->query( $this->db->prepare(
+				'UPDATE translate_originals SET priority = %d WHERE project_id = %d AND status = %s AND singular = %s',
+				$prio, $branch_id, '+active', $str
+			) );
+		}
+	}
+
+	/*
+	 * ***********************
+	 * Rendering
+	 * ***********************
+	 */
+
+	function translate( $key, $content, $args = array() ) {
+		if ( empty( $key ) || empty( $content ) )
+			return $content;
+
+		if ( !empty( $args['topic_id'] ) && is_numeric( $args['topic_id'] ) )
+			$topic = get_topic( $args['topic_id'] );
+		else
+			global $topic;
+
+		if ( empty( $topic ) )
+			return $content;
+
+		/*
+		 * DEBUG: Getting $language from request or host name, should use something like $GLOBALS['locale']
+		 * and/or $GLOBALS['i18n'] once/if set properly when on localized site and get it from that.
+		 *
+		 * TODO: find the proper way
+		 *
+		 * $GLOBALS['locale'] is currently en_US on localized sites (eg: fr.wordpress.org)
+		 * $GLOBALS['i18n'] is currently false on localized sites (eg: fr.wordpress.org)
+		 */
+		$language     = '';
+		$server_name  = strtolower( $_SERVER['SERVER_NAME'] );
+		if ( 'api.wordpress.org' == $server_name && 2 == strlen( $_REQUEST['language'] ) ) {
+			$language = $_REQUEST['language'];
+		} else if ( preg_match( '/^[a-z]{2}\.wordpress\.org$/', $server_name ) ) {
+			$language = substr( $server_name, 0, 2 );
+		}
+
+		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';
+
+		if ( empty( $args['code_i18n'] ) || true !== $args['code_i18n'] ) {
+			$branch .= '-readme';
+		}
+
+		// Instantiate this before modifying $content
+		$cache_suffix = "{$language}:{$key}";
+
+		// Try the cache
+		if ( false !== ( $cache = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) {
+			// DEBUG
+			// var_dump( array( $slug, $branch, $cache_suffix, $cache ) );
+			return $cache;
+		}
+
+		$originals = $this->get_gp_originals( $slug, $branch, $key, $content );
+
+		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 );
+		}
+
+		$this->cache_set( $slug, $branch, $content, $cache_suffix );
+
+		return $content;
+	}
+
+	function cache_key( $slug, $branch, $suffix = null ) {
+		// Main keys
+		// plugin:press-this:stable-readme:originals
+		// plugin:press-this:stable-readme:original:md5($str)
+		// plugin:press-this:stable-readme:fr:md5($str)
+		$key = "plugin:{$slug}:{$branch}";
+		if ( !empty( $suffix ) )
+			$key .= ":{$suffix}";
+		return $key;
+	}
+
+	function cache_get( $slug, $branch, $suffix = null ) {
+		$key = $this->cache_key( $slug, $branch, $suffix );
+		// DEBUG
+		// wp_cache_delete( $key, $this->i18n_cache_group );
+		return wp_cache_get( $key, $this->i18n_cache_group );
+	}
+
+	function cache_set( $slug, $branch, $content, $suffix = null ) {
+		$key = $this->cache_key( $slug, $branch, $suffix );
+		return wp_cache_set( $key, $content, $this->i18n_cache_group );
+	}
+
+	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}"
+		) );
+
+		return ( empty( $branch_id ) ) ? 0 : $branch_id;
+	}
+
+	function get_gp_originals( $slug, $branch, $key, $str ) {
+		// Try to get a single original with the whole content first (title, etc), if passed, or get them all otherwise.
+		if ( !empty( $key ) && !empty( $str ) ) {
+			$originals = $this->search_gp_original( $slug, $branch, $key, $str );
+			if ( !empty( $originals ) )
+				return array( $originals );
+			// Do not cache this as originals, search_gp_original() does its own caching
+		}
+
+		$cache_suffix = 'originals';
+
+		if ( false !== ( $originals = $this->cache_get( $slug, $branch, $cache_suffix ) ) )
+			return $originals;
+
+		$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 ) )
+			$originals = array(); // still cache if empty, but as array, never false
+
+		$this->cache_set( $slug, $branch, $originals, $cache_suffix );
+
+		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, $key, $str ) {
+		$cache_suffix = "original:{$key}";
+
+		if ( false !== ( $original = $this->cache_get( $slug, $branch, $cache_suffix ) ) )
+			return $original;
+
+		$branch_id = $this->get_gp_branch_id( $slug, $branch );
+
+		if ( empty( $branch_id ) )
+			return false;
+
+		$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 ) )
+			$original = null;
+
+		$this->cache_set( $slug, $branch, $original, $cache_suffix );
+
+		return $original;
+	}
+
+	function translate_gp_original( $original, $translation, $content) {
+		$content = str_replace( $original, $translation, $content );
+		return $content;
+	}
+}
\ No newline at end of file

Property changes on: extend/plugins-plugins/svn-track/class.dotorg-plugins-i18n.php
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php
===================================================================
--- extend/plugins-plugins/svn-track/class.dotorg-plugins-tracker.php	(revision 9233)
+++ 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();
@@ -277,9 +282,19 @@
 			$content = preg_replace_callback( "#<img class='screenshot' src='(screenshot-[^']+)' alt='[^']+' />#", array( &$this, '_screenshot_img_tag' ), $content );
 		}

-		return $content;
+		return $this->translate( $show, $content );
 	}

+	function i18n_obj( $db, $tracker = null ) {
+		if ( !class_exists( 'Dotorg_Plugin_I18n' ) )
+			require_once( __DIR__ . '/class.dotorg-plugins-i18n.php' );
+		return new Dotorg_Plugin_I18n( $db, $tracker );
+	}
+
+	function translate( $key, $content, $args = array() ) {
+		return $this->i18n_obj( $this->db )->translate( $key, $content, $args );
+	}
+
 	function _screenshot_shortcode( $matches ) {
 		return $this->_screenshot_image( $matches[1], $matches[2] );
 	}
@@ -1562,6 +1577,10 @@
 			else
 				bb_delete_topicmeta( $topic_id, $key );
 		}
+
+		// Reprocess the readme for translation.
+		// DEBUG: placeholder, untested and not ready as of yet.
+		// $this->process_i18n( $slug, 'stable', 'readme' ); // Figures on its own if stable means dev (trunk).
 	}

 	function load_topic_page( $topic_id ) {
@@ -1731,15 +1750,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 ) {
+		$server_name = strtolower( $_SERVER[ 'SERVER_NAME' ] );
+		if ( 'wordpress.org' != $server_name && preg_match( '/\.wordpress\.org$/', $server_name ) )
+			return str_replace( '://wordpress.org/', "://{$server_name}/", $url );
+		return $url;
+	}
+
 	function section_title( $section ) {
 		switch ( $section ) {
 			case 'description':
@@ -1996,6 +2022,10 @@
 		return false;
 	}

+	function process_i18n( $slug, $branch = 'dev', $type = 'all' ) {
+		return $this->i18n_obj( $this->db, $this )->process( $slug, $branch, $type );
+	}
+
 	function get_all_roots( $via = 'local' ) {
 		global $bbdb;
 		$root_rels = false;
Index: bb-theme/plugins/search.php
===================================================================
--- bb-theme/plugins/search.php	(revision 9233)
+++ bb-theme/plugins/search.php	(working copy)
@@ -32,7 +32,7 @@

 	echo wporg_get_plugin_icon( $topic );
 ?>
-	<h3><a href="<?php topic_link(); ?>"><?php topic_title(); ?></a></h3>
+	<h3><a href="<?php topic_link(); ?>"><?php echo $svn_tracker->translate( 'title', get_topic_title() ); ?></a></h3>
 	<?php echo $svn_tracker->_post_section( 'short_description', $bb_post->post_text, 0); ?>
 	<ul class="plugin-meta">
 		<li><?php $svn_tracker->template( 'version', 'before=<span class="info-marker">'. __( 'Version:', 'wporg' ) .'</span> '); ?></li>
Index: bb-theme/plugins/topic-404.php
===================================================================
--- bb-theme/plugins/topic-404.php	(revision 9233)
+++ bb-theme/plugins/topic-404.php	(working copy)
@@ -70,7 +70,7 @@

 <div class="plugin-block">
 	<?php $topic = get_topic( $bb_post->topic_id ); ?>
-	<h3><a href="<?php topic_link(); ?>"><?php topic_title(); ?></a></h3>
+	<h3><a href="<?php topic_link(); ?>"><?php echo $svn_tracker->translate( 'title', get_topic_title() ); ?></a></h3>
 	<?php post_text(); ?>
 	<ul class="plugin-meta">
 		<li><?php $svn_tracker->template( 'version', 'before=<span class="info-marker">Version</span> '); ?></li>
Index: bb-theme/plugins/topic.php
===================================================================
--- bb-theme/plugins/topic.php	(revision 9233)
+++ 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">