| 291 | return $this->translate( $show, $content ); |
| 292 | } |
| 293 | |
| 294 | function translate( $key, $content ) { |
| 295 | global $topic; |
| 296 | |
| 297 | if ( empty( $key ) || empty( $content ) ) |
| 298 | return $content; |
| 299 | |
| 300 | /* |
| 301 | * DEBUG: Getting $language from host name, should use $locale and/or $l10n globals |
| 302 | * once set properly when on localized site and get it from that. |
| 303 | * $locale is currently en_US on localized sites (eg: fr.wordpress.org) |
| 304 | * $i18n is currently false on localized sites (eg: fr.wordpress.org) |
| 305 | */ |
| 306 | $language = str_replace( '.wordpress.org', '', $_SERVER[ 'SERVER_NAME' ] ); |
| 307 | |
| 308 | if ( empty( $language ) || 2 !== strlen( $language ) ) |
| 309 | return $content; |
| 310 | |
| 311 | $slug = $topic->plugin_san; |
| 312 | |
| 313 | // DEBUG: in_array check is because we'll start the program with a finite list of plugins |
| 314 | // TODO: remove when we launch for all plugins. |
| 315 | if ( empty( $slug ) || ! in_array( $slug, $this->translated_plugins ) ) |
| 316 | return $content; |
| 317 | |
| 318 | $branch = ( empty( $topic->stable_tag ) || 'trunk' === $topic->stable_tag ) ? 'dev' : 'stable'; |
| 319 | |
| 320 | // Try to get a single original with the whole content first (title, etc), or get them all otherwise. |
| 321 | $originals = $this->_search_gp_original( $slug, $branch, $content ); |
| 322 | if ( empty( $originals ) ) |
| 323 | $originals = $this->_get_gp_originals( $slug, $branch ); |
| 324 | |
| 325 | if ( empty( $originals ) ) |
| 326 | return $content; |
| 327 | |
| 328 | $translation_set_id = $this->_get_gp_translation_set_id( $slug, $branch, $language ); |
| 329 | |
| 330 | if ( empty( $translation_set_id ) ) |
| 331 | return $content; |
| 332 | |
| 333 | foreach ( $originals as $original ) { |
| 334 | if ( empty( $original->id ) ) |
| 335 | continue; |
| 336 | |
| 337 | $translation = $this->db->get_var( $this->db->prepare( |
| 338 | 'SELECT translation_0 FROM translate_translations WHERE original_id = %d AND translation_set_id = %d AND status = %s', |
| 339 | $original->id, $translation_set_id, 'current' |
| 340 | ) ); |
| 341 | |
| 342 | if ( empty( $translation ) ) |
| 343 | continue; |
| 344 | |
| 345 | $content = $this->_translate_gp_original( $original->singular, $translation, $content ); |
| 346 | } |
| 347 | |
| 351 | function _get_gp_branch_id( $slug, $branch ) { |
| 352 | $branch_id = $this->db->get_var( $this->db->prepare( |
| 353 | 'SELECT id FROM translate_projects WHERE path = %s', |
| 354 | "wp-plugins/{$slug}/{$branch}-readme" |
| 355 | ) ); |
| 356 | |
| 357 | return ( empty( $branch_id ) ) ? 0 : $branch_id; |
| 358 | } |
| 359 | |
| 360 | function _get_gp_originals( $slug, $branch ) { |
| 361 | $branch_id = $this->_get_gp_branch_id( $slug, $branch ); |
| 362 | |
| 363 | if ( empty( $branch_id ) ) |
| 364 | return array(); |
| 365 | |
| 366 | $originals = $this->db->get_results( $this->db->prepare( |
| 367 | 'SELECT id, singular, comment FROM translate_originals WHERE project_id = %d AND status = %s', |
| 368 | $branch_id, '+active' |
| 369 | ) ); |
| 370 | |
| 371 | if ( empty( $originals ) ) |
| 372 | return array(); |
| 373 | |
| 374 | return $originals; |
| 375 | } |
| 376 | |
| 377 | function _get_gp_translation_set_id( $slug, $branch, $locale ) { |
| 378 | $branch_id = $this->_get_gp_branch_id( $slug, $branch ); |
| 379 | |
| 380 | if ( empty( $branch_id ) ) |
| 381 | return 0; |
| 382 | |
| 383 | $translation_set_id = $this->db->get_var( $this->db->prepare( |
| 384 | 'SELECT id FROM translate_translation_sets WHERE project_id = %d AND locale = %s', |
| 385 | $branch_id, $locale ) ); |
| 386 | |
| 387 | return ( empty( $translation_set_id ) ) ? 0 : $translation_set_id; |
| 388 | } |
| 389 | |
| 390 | function _search_gp_original( $slug, $branch, $str ) { |
| 391 | $branch_id = $this->_get_gp_branch_id( $slug, $branch ); |
| 392 | |
| 393 | if ( empty( $branch_id ) ) |
| 394 | return array(); |
| 395 | |
| 396 | $original = $this->db->get_row( $this->db->prepare( |
| 397 | 'SELECT id, singular, comment FROM translate_originals WHERE project_id = %d AND status = %s AND singular = %s', |
| 398 | $branch_id, '+active', $str |
| 399 | ) ); |
| 400 | |
| 401 | if ( empty( $original ) ) |
| 402 | return array(); |
| 403 | |
| 404 | return array( $original ); |
| 405 | } |
| 406 | |
| 407 | function _translate_gp_original( $original, $translation, $content) { |
| 408 | $content = str_replace( $original, $translation, $content ); |
| 409 | return $content; |
| 410 | } |
| 411 | |
| 2135 | function process_i18n( $slug, $branch = 'dev', $type = 'all' ) { |
| 2136 | if ( empty( $slug ) ) |
| 2137 | return false; |
| 2138 | |
| 2139 | // DEBUG: in_array check is because we'll start the program with a finite list of plugins |
| 2140 | // TODO: remove when we launch for all plugins. |
| 2141 | if ( ! in_array( $slug, $this->translated_plugins ) ) |
| 2142 | return false; |
| 2143 | |
| 2144 | if ( 'stable' !== $branch ) |
| 2145 | $branch = 'dev'; |
| 2146 | |
| 2147 | if ( 'code' !== $type && 'readme' !== $type ) |
| 2148 | $type = 'all'; |
| 2149 | |
| 2150 | $path_rel = "{$slug}/trunk/"; |
| 2151 | |
| 2152 | if ( 'stable' === $branch ) { |
| 2153 | if ( false == ( $stable_tag = $this->get_stable_tag_dir_using( $path_rel ) ) ) { |
| 2154 | // Can't get a stable tag, bail out |
| 2155 | return false; |
| 2156 | } else if ( 'trunk' == trim( $stable_tag['tag_dir'], '/' ) ) { |
| 2157 | // If stable is trunk, then it's really same as dev, switch to that |
| 2158 | $branch = 'dev'; |
| 2159 | } else { |
| 2160 | // We're dealing with an actual stable tag, go for it |
| 2161 | $path_rel = "{$slug}/{$stable_tag['tag_dir']}"; // |
| 2162 | } |
| 2163 | } |
| 2164 | |
| 2165 | if ( 'code' === $type || 'all' === $type ) |
| 2166 | $this->process_code_i18n( $path_rel, $branch ); |
| 2167 | |
| 2168 | if ( 'readme' === $type || 'all' === $type ) |
| 2169 | $this->process_readme_i18n( $path_rel, $branch ); |
| 2170 | |
| 2171 | echo "Processed {$type} for {$path_rel}\n"; |
| 2172 | return true; |
| 2173 | } |
| 2174 | |
| 2175 | function process_code_i18n( $path_rel, $branch = 'dev' ) { |
| 2176 | $slug = preg_replace( '|^/?([^/]+)/?.+?$|', '\1', $path_rel ); |
| 2177 | |
| 2178 | if ( empty( $slug ) || !preg_match( '/^[a-z0-9-]+$/i', $slug ) ) |
| 2179 | return false; |
| 2180 | |
| 2181 | // DEBUG: in_array check is because we'll start the program with a finite list of plugins |
| 2182 | // TODO: remove when we launch for all plugins. |
| 2183 | if ( !in_array( $slug, $this->translated_plugins ) ) |
| 2184 | return false; |
| 2185 | |
| 2186 | $export_path = $this->create_export( $path_rel ); |
| 2187 | |
| 2188 | if ( empty( $export_path ) || !is_dir( $export_path ) ) |
| 2189 | return false; |
| 2190 | |
| 2191 | $old_cwd = getcwd(); |
| 2192 | chdir( $export_path ); |
| 2193 | |
| 2194 | // Check for a plugin text domain declaration and loading, grep recursively, not necessarily in [slug].php |
| 2195 | if ( ! shell_exec( 'grep -r --include "*.php" "Text Domain: ' . escapeshellarg( $slug ) . '" .' ) && ! shell_exec( 'grep -r --include "*.php" "\bload_plugin_textdomain\b" .' ) ) |
| 2196 | return false; |
| 2197 | |
| 2198 | if ( !class_exists( 'PotExtMeta' ) ) |
| 2199 | require_once( __DIR__ . '/i18n-tools/pot-ext-meta.php' ); |
| 2200 | |
| 2201 | // Create pot file from code |
| 2202 | $pot_file = "./tmp-{$slug}.pot"; // Using tmp- prefix in case a plugin has $slug.pot committed |
| 2203 | $makepot = new MakePOT; |
| 2204 | |
| 2205 | if ( ! $makepot->wp_plugin( '.', $pot_file, $slug ) || ! file_exists( $pot_file ) ) |
| 2206 | return false; |
| 2207 | |
| 2208 | // DEBUG |
| 2209 | // system( "cat {$pot_file}" ); |
| 2210 | |
| 2211 | $this->import_to_glotpress_project( $slug, $branch, $pot_file ); |
| 2212 | |
| 2213 | chdir( $old_cwd ); |
| 2214 | return true; |
| 2215 | } |
| 2216 | |
| 2217 | function process_readme_i18n( $path_rel, $branch = 'dev' ) { |
| 2218 | $slug = preg_replace( '|^/?([^/]+)/?.+?$|', '\1', $path_rel ); |
| 2219 | |
| 2220 | if ( empty( $slug ) || !preg_match( '/^[a-z0-9-]+$/i', $slug ) ) |
| 2221 | return false; |
| 2222 | |
| 2223 | // DEBUG: in_array as separate check because we'll start the program with a finite list of plugins |
| 2224 | // TODO: remove when we launch for all plugins. |
| 2225 | if ( !in_array( $slug, $this->translated_plugins ) ) |
| 2226 | return false; |
| 2227 | |
| 2228 | $export_path = $this->create_export( $path_rel ); |
| 2229 | |
| 2230 | if ( empty( $export_path ) || !is_dir( $export_path ) ) |
| 2231 | return false; |
| 2232 | |
| 2233 | $old_cwd = getcwd(); |
| 2234 | chdir( $export_path ); |
| 2235 | |
| 2236 | $readme = $this->parse_readme_in( $path_rel ); |
| 2237 | |
| 2238 | if ( !class_exists( 'PO' ) ) |
| 2239 | require_once( __DIR__ . '/i18n-tools/pomo/po.php' ); |
| 2240 | |
| 2241 | $pot = new PO; |
| 2242 | |
| 2243 | foreach ( array( 'name', 'license', 'short_description' ) as $key ) { |
| 2244 | $readme[ $key ] = trim( $readme[ $key ] ) ; |
| 2245 | } |
| 2246 | |
| 2247 | // If empty or "sketchy", get the plugin name form the PHP file's headers |
| 2248 | if ( empty( $readme['name'] ) || 'Plugin Name' == trim( $readme['name'] ) ) { |
| 2249 | // -o in grep will make sure we don't get comments opening delimiters (//, /*) or spaces as part of string |
| 2250 | $name_from_php = trim( shell_exec( 'grep -o "\bPlugin Name:.*" ' . escapeshellarg( $slug ) . '.php' ) ); |
| 2251 | // Remove the header label |
| 2252 | $name_from_php = str_replace( 'Plugin Name:', '', $name_from_php ); |
| 2253 | // Do clean out potential comment closing delimiter (*/) out of string |
| 2254 | $name_from_php = preg_replace( '|^(.+)[\s]+?\*/$|', '\1', $name_from_php ); |
| 2255 | // Use trimmed results as plugin name |
| 2256 | $readme['name'] = trim( $name_from_php ); |
| 2257 | } |
| 2258 | |
| 2259 | if ( !empty( $readme['name'] ) ) { |
| 2260 | $pot->add_entry( new Translation_Entry ( array( |
| 2261 | 'singular' => $readme['name'], |
| 2262 | 'extracted_comments' => 'Plugin/theme name.', |
| 2263 | ) ) ); |
| 2264 | } |
| 2265 | |
| 2266 | if ( !empty( $readme['license'] ) ) { |
| 2267 | $pot->add_entry( new Translation_Entry ( array( |
| 2268 | 'singular' => $readme['license'], |
| 2269 | 'extracted_comments' => 'License.', |
| 2270 | ) ) ); |
| 2271 | } |
| 2272 | |
| 2273 | if ( !empty( $readme['short_description'] ) ) { |
| 2274 | $pot->add_entry( new Translation_Entry ( array( |
| 2275 | 'singular' => $readme['short_description'], |
| 2276 | 'extracted_comments' => 'Short description.', |
| 2277 | ) ) ); |
| 2278 | } |
| 2279 | |
| 2280 | if ( !empty( $readme['screenshots'] ) ) { |
| 2281 | foreach ( $readme['screenshots'] as $sshot_key => $sshot_desc ) { |
| 2282 | $sshot_desc = trim( $sshot_desc ); |
| 2283 | $pot->add_entry( new Translation_Entry ( array( |
| 2284 | 'singular' => $sshot_desc, |
| 2285 | 'extracted_comments' => 'Screenshot description.', |
| 2286 | ) ) ); |
| 2287 | } |
| 2288 | |
| 2289 | } |
| 2290 | |
| 2291 | if ( empty( $readme['sections'] ) ) |
| 2292 | $readme['sections'] = array(); |
| 2293 | |
| 2294 | // Adding remaining content as a section so it's processed by the same loop below |
| 2295 | if ( !empty( $readme['remaining_content'] ) ) |
| 2296 | $readme['sections']['remaining_content'] = $readme['remaining_content']; |
| 2297 | |
| 2298 | $strings = array(); |
| 2299 | |
| 2300 | foreach ( $readme['sections'] as $section_key => $section_text ) { |
| 2301 | if ( 'screenshots' == $section_key ) |
| 2302 | continue; |
| 2303 | |
| 2304 | /* |
| 2305 | * Scanned tags based on block elements found in Automattic_Readme::filter_text() $allowed. |
| 2306 | * Scanning H3/4, li, p and blockquote. Other tags are ignored in strings (a, strong, cite, etc). |
| 2307 | * Processing notes: |
| 2308 | * * Don't normalize/modify original text, will be used as a search pattern in original doc in some use-cases. |
| 2309 | * * Using regexes over XML parsing for performance reasons, could move to the latter for more accuracy. |
| 2310 | */ |
| 2311 | |
| 2312 | if ( 'changelog' !== $section_key ) { // No need to scan non-translatable version headers in changelog |
| 2313 | if ( preg_match_all( '|<h[3-4]+[^>]*>(.+)</h[3-4]+>|', $section_text, $matches ) ) { |
| 2314 | if ( !empty( $matches[1] ) ) { |
| 2315 | foreach ( $matches[1] as $text ) { |
| 2316 | $strings = $this->handle_translator_comment( $strings, $text, "{$section_key} header" ); |
| 2317 | } |
| 2318 | } |
| 2319 | } |
| 2320 | } |
| 2321 | |
| 2322 | if ( preg_match_all( '|<li>(.+)</li>|', $section_text, $matches ) ) { |
| 2323 | if ( !empty( $matches[1] ) ) { |
| 2324 | foreach ( $matches[1] as $text ) { |
| 2325 | $strings = $this->handle_translator_comment( $strings, $text, "{$section_key} list item" ); |
| 2326 | } |
| 2327 | } |
| 2328 | } |
| 2329 | |
| 2330 | if ( preg_match_all( '|<p>(.+)</p>|', $section_text, $matches ) ) { |
| 2331 | if ( !empty( $matches[1] ) ) { |
| 2332 | foreach ( $matches[1] as $text ) { |
| 2333 | $strings = $this->handle_translator_comment( $strings, $text, "{$section_key} paragraph" ); |
| 2334 | } |
| 2335 | } |
| 2336 | } |
| 2337 | |
| 2338 | if ( preg_match_all( '|<blockquote>(.+)</blockquote>|', $section_text, $matches ) ) { |
| 2339 | if ( !empty( $matches[1] ) ) { |
| 2340 | foreach ( $matches[1] as $text ) { |
| 2341 | $strings = $this->handle_translator_comment( $strings, $text, "{$section_key} block quote" ); |
| 2342 | } |
| 2343 | } |
| 2344 | } |
| 2345 | } |
| 2346 | |
| 2347 | foreach ( $strings as $text => $comments ) { |
| 2348 | $pot->add_entry( new Translation_Entry ( array( |
| 2349 | 'singular' => $text, |
| 2350 | 'extracted_comments' => 'Found in ' . implode( $comments, ", " ) . '.', |
| 2351 | ) ) ); |
| 2352 | } |
| 2353 | |
| 2354 | $pot_file = "./tmp-{$slug}-readme.pot"; |
| 2355 | $pot->export_to_file( $pot_file ); |
| 2356 | |
| 2357 | // DEBUG |
| 2358 | // system( "cat {$pot_file}" ); |
| 2359 | |
| 2360 | // import to GlotPress, dev or stable |
| 2361 | $this->import_to_glotpress_project( $slug, "{$branch}-readme", $pot_file ); |
| 2362 | |
| 2363 | chdir( $old_cwd ); |
| 2364 | return true; |
| 2365 | } |
| 2366 | |
| 2367 | function handle_translator_comment( $array, $key, $val ) { |
| 2368 | $val = trim( preg_replace( '/[^a-z0-9]/i', ' ', $val ) ); // cleanup key names for display |
| 2369 | if ( empty( $array[ $key ] ) ) { |
| 2370 | $array[ $key ] = array( $val ); |
| 2371 | } else if ( !in_array( $val, $array[ $key ] ) ) { |
| 2372 | $array[ $key ][] = $val; |
| 2373 | } |
| 2374 | return $array; |
| 2375 | } |
| 2376 | |
| 2377 | function import_to_glotpress_project( $project, $branch, $file ) { |
| 2378 | $cmd = 'php ' . __DIR__ . '/../../../translate/glotpress/scripts/import-originals.php -o po -p ' . escapeshellarg( "wp-plugins/{$project }/{$branch}" ) . ' -f ' . escapeshellarg( $file ); |
| 2379 | // DEBUG |
| 2380 | // var_dump( $cmd ); |
| 2381 | system( $cmd ); |
| 2382 | // Note: this will only work if the GlotPress project/sub-projects exist. To be improved, or must insure they do. |
| 2383 | } |
| 2384 | |