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,575 @@
+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']}"; //
+ }
+ }
+
+ $this->set_glotpress_for_plugin( $slug );
+
+ 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( '|
(.+)
|', $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( '|(.+)|', $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() ) { + if ( empty( $project ) || empty( $branch ) || empty( $file ) ) + return; + // Note: this will only work if the GlotPress project/sub-projects exist. + $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 + ) ); + } + } + + function set_glotpress_for_plugin( $plugin_slug ) { + if ( empty( $plugin_slug ) ) + return; + $cmd = 'php ' . __DIR__ . '/../../../translate/bin/set-wp-plugin-project.php ' . escapeshellarg( $plugin_slug ); + // DEBUG + // var_dump( $cmd ); + system( $cmd ); + } + + /* + * *********************** + * 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; + + $language = ''; + $server_name = strtolower( $_SERVER['SERVER_NAME'] ); + if ( 'api.wordpress.org' == $server_name ) { + if ( preg_match( '/^[a-z]{2}(_[A-Z]{2})?$/', trim( $_REQUEST['locale'] ) ) ) { + $language = substr( trim( $_REQUEST['locale'] ), 0, 5 ); + } else if ( !empty( $_REQUEST['request'] ) ) { + $request = maybe_unserialize( $_REQUEST['request'] ); + if ( !empty( $request ) && !empty( $request->locale ) && preg_match( '/^[a-z]{2}(_[A-Z]{2})?$/', trim( $request->locale ) ) ) { + $language = trim( $request->locale ); + } + } + } else if ( preg_match( '/^([^\.]+)\.wordpress\.org$/', $server_name, $matches ) ) { + $subdomain = $this->verify_subdomain( $matches[1] ); + if ( ! empty( $subdomain ) ) + $language = substr( $server_name, 0, 2 ); + } + + if ( empty( $language ) || 'en' === $language || 'en_US' === $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'; + + $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 ) { + // EG keys + // plugin:press-this:stable-readme:originals + // plugin:press-this:stable-readme:original:title + // plugin:press-this:stable-readme:fr:title + $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 ) { + $cache_suffix = "branch_id"; + + if ( false !== ( $branch_id = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) + return $branch_id; + + $branch_id = $this->db->get_var( $this->db->prepare( + 'SELECT id FROM translate_projects WHERE path = %s', + "wp-plugins/{$slug}/{$branch}" + ) ); + + if ( empty( $branch_id ) ) + $branch_id = 0; + + $this->cache_set( $slug, $branch, $branch_id, $cache_suffix ); + + return $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 ) { + $locale = strtolower( $locale ); + + if ( false !== strpos( $locale, '_' ) ) { + $locale = str_replace( '_', '-', $locale ); + } + + $cache_suffix = "{$locale}:translation_set_id"; + + if ( false !== ( $translation_set_id = $this->cache_get( $slug, $branch, $cache_suffix ) ) ) + return $translation_set_id; + + $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 ) ); + + if ( empty( $translation_set_id ) ) { + // Don't give up yet. Might be given fr_FR, which actually exists as locale=fr in GP. + $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, substr( $locale, 0, 2 ) ) ); + } + + if ( empty( $translation_set_id ) ) + $translation_set_id = 0; + + $this->cache_set( $slug, $branch, $translation_set_id, $cache_suffix ); + + return $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; + } + + function verify_subdomain( $locale ) { + if ( empty( $locale ) ) + return ''; + + $cache_key = "subdomains:{$locale}"; + + if ( false !== ( $subdomain = wp_cache_get( $cache_key, $this->i18n_cache_group ) ) ) { + // var_dump(array($cache_key, $subdomain)); + return $subdomain; + } + + $subdomain = ''; + + if ( 2 === strlen( $locale ) ) { + $subdomain = $this->db->get_var( $this->db->prepare( + 'SELECT subdomain FROM locales WHERE subdomain = %s LIMIT 1', + $locale + ) ); + } else if ( 5 === strlen( $locale ) ) { + $subdomain = $this->db->get_var( $this->db->prepare( + 'SELECT subdomain FROM locales WHERE locale = %s LIMIT 1', + $locale + ) ); + } + + wp_cache_set( $cache_key, $subdomain, $this->i18n_cache_group ); + + return $subdomain; + } +} \ 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 9256) +++ 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( "##", 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] ); } @@ -1168,7 +1183,7 @@ $path_rels = array_merge( $path_rels, array_keys( $entry->paths ) ); } $return_revision = array_shift( array_keys( $triggers ) ); - } else { // its some paths + } else { // its some paths $path_rels =& $triggers; $return_revision = true; } @@ -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 9256) +++ bb-theme/plugins/search.php (working copy) @@ -32,7 +32,7 @@ echo wporg_get_plugin_icon( $topic ); ?> -