Index: trunk/extensions/Translate/Translate.alias.php |
— | — | @@ -17,6 +17,7 @@ |
18 | 18 | 'TranslationStats' => array( 'TranslationStats' ), |
19 | 19 | 'Translations' => array( 'Translations' ), |
20 | 20 | 'LanguageStats' => array( 'LanguageStats' ), |
| 21 | + 'PageTranslation' => array( 'PageTranslation' ), |
21 | 22 | ); |
22 | 23 | |
23 | 24 | /** Afrikaans (Afrikaans) */ |
Index: trunk/extensions/Translate/Translate.css |
— | — | @@ -89,4 +89,23 @@ |
90 | 90 | font-family: monospace; |
91 | 91 | background-color: #F3F3FF; |
92 | 92 | border: 1px solid black; |
| 93 | +} |
| 94 | + |
| 95 | +.mw-pt-languages table { |
| 96 | + border: 1px solid rgb(170, 170, 170); |
| 97 | + background: rgb(246, 249, 237) none repeat scroll 0% 0%; |
| 98 | + border-collapse: collapse; |
| 99 | + line-height: 1.2; |
| 100 | + width: 100%; |
| 101 | +} |
| 102 | + |
| 103 | +.mw-pt-languages-label { |
| 104 | + border-right: 1px solid rgb(170, 170, 170); |
| 105 | + padding: 0.5em; |
| 106 | + background: rgb(238, 243, 226) none repeat scroll 0% 0%; |
| 107 | + width: 200px; |
| 108 | +} |
| 109 | + |
| 110 | +.mw-pt-languages-list { |
| 111 | + padding: 0.5em; |
93 | 112 | } |
\ No newline at end of file |
Index: trunk/extensions/Translate/scripts/cli.inc |
— | — | @@ -12,6 +12,7 @@ |
13 | 13 | |
14 | 14 | $dir = dirname( __FILE__ ); $IP = "$dir/../../.."; |
15 | 15 | @include("$dir/../../CorePath.php"); // Allow override |
| 16 | +define( 'TRANSLATE_CLI', 1 ); |
16 | 17 | require_once( "$IP/maintenance/commandLine.inc" ); |
17 | 18 | |
18 | 19 | function STDOUT( $str, $channel = null, $force = false ) { |
Index: trunk/extensions/Translate/PageTranslation.i18n.php |
— | — | @@ -0,0 +1,53 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Translations of Page Translation feature of Translate extension. |
| 5 | + * |
| 6 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
| 7 | + */ |
| 8 | + |
| 9 | +$messages = array(); |
| 10 | + |
| 11 | +/** English |
| 12 | + * @author Nike |
| 13 | + */ |
| 14 | +$messages['en'] = array( |
| 15 | + 'pagetranslation' => 'Page translation', |
| 16 | + 'right-pagetranslate' => 'Mark versions of pages for translation', |
| 17 | + 'tpt-section' => 'Section:', |
| 18 | + 'tpt-section-new' => 'New section:', |
| 19 | + |
| 20 | + 'tpt-diff-old' => 'Previous text', |
| 21 | + 'tpt-diff-new' => 'New text', |
| 22 | + 'tpt-submit' => 'Mark this version for translation', |
| 23 | + |
| 24 | + # Specific page on the special page |
| 25 | + 'tpt-badtitle' => 'Page name given ($1) is not a valid title', |
| 26 | + 'tpt-oldrevision' => '$2 is not the latest version of the page $1. Only latest versions can be marked up for translating.', |
| 27 | + 'tpt-notsuitable' => 'Page $1 is not suitable for translation. Please make sure it has <nowiki><translate></nowiki>-tags and has a valid syntax.', |
| 28 | + 'tpt-saveok' => '$1 has been marked up for translation with $2 translatable sections. The page can now be <span class="plainlinks">[$3 translated]</span>.', |
| 29 | + 'tpt-badsect' => '"$1" is not a valid name for section $2.', |
| 30 | + 'tpt-deletedsections' => 'The following sections wil no longer be used', |
| 31 | + 'tpt-showpage-intro' => 'Below are listed new, existing and deleted sections. Before marking this version for translation, please check that the changes to sections are minimised to avoid unnecessary work for translations.', |
| 32 | + 'tpt-mark-summary' => 'Marked this version for translation', |
| 33 | + 'tpt-edit-failed' => 'Could not update the page: $1', |
| 34 | + 'tpt-insert-failed' => 'Could not add sections to the database.', |
| 35 | + 'tpt-already-marked' => 'The latest version of this page is already marked for translation.', |
| 36 | + |
| 37 | + # Page list on the special page |
| 38 | + 'tpt-old-pages' => 'Some version of these pages have been marked for translation.', |
| 39 | + 'tpt-new-pages' => 'These pages contain text with translation tags, but no version of these pages are currently marked for translation.', |
| 40 | + 'tpt-rev-latest' => 'latest version', |
| 41 | + 'tpt-rev-old' => 'version $1', |
| 42 | + 'tpt-rev-mark-new' => 'mark this version for translation', |
| 43 | + 'tpt-translate-this' => 'translate this page', |
| 44 | + |
| 45 | + # Source and translation page headers |
| 46 | + 'translate-tag-translate-link-desc' => 'Translate this page', |
| 47 | + 'translate-tag-markthis' => 'Mark this page for translation', |
| 48 | + 'translate-tag-legend' => 'Legend:', |
| 49 | + 'translate-tag-legend-fallback' => 'Translation in other language', |
| 50 | + 'translate-tag-legend-fuzzy' => 'Outdated translation', |
| 51 | + |
| 52 | + 'tpt-target-page' => 'This page cannot be updated manually. This page is a translation of page [[$1]] and the translation can be updated using [$2 the translation tool].', |
| 53 | + 'tpt-unknown-page' => 'This namespace is reserved for content page translations. The page you are trying to edit does not seem to correspond any page marked for translation.' |
| 54 | +); |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/PageTranslation.i18n.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 55 | + native |
Index: trunk/extensions/Translate/Translate.i18n.php |
— | — | @@ -158,11 +158,6 @@ |
159 | 159 | 'translate-tag-category' => 'Translatable pages', |
160 | 160 | 'translate-tag-page-desc' => 'Translation of the wiki page [[:$1]].', |
161 | 161 | |
162 | | - 'translate-tag-translate-link-desc' => 'Translate this page', |
163 | | - 'translate-tag-legend' => 'Legend:', |
164 | | - 'translate-tag-legend-fallback' => 'Translation in other language', |
165 | | - 'translate-tag-legend-fuzzy' => 'Outdated translation', |
166 | | - |
167 | 162 | 'translate-tag-fuzzy-comment' => 'Definition changed by [[User:$1|$1]] in revision $2.', |
168 | 163 | 'translate-tag-fuzzy-reason' => 'Definition changed by "$1" with comment "$3" in $2.', |
169 | 164 | |
Index: trunk/extensions/Translate/TranslateUtils.php |
— | — | @@ -44,7 +44,6 @@ |
45 | 45 | */ |
46 | 46 | public static function fillContents( MessageCollection $messages, |
47 | 47 | array $namespaces ) { |
48 | | - wfMemIn( __METHOD__ ); wfProfileIn( __METHOD__ ); |
49 | 48 | |
50 | 49 | $titles = array(); |
51 | 50 | foreach ( $messages->keys() as $key ) { |
— | — | @@ -64,7 +63,6 @@ |
65 | 64 | } |
66 | 65 | } |
67 | 66 | |
68 | | - wfProfileOut( __METHOD__ ); wfMemOut( __METHOD__ ); |
69 | 67 | } |
70 | 68 | |
71 | 69 | public static function getMessageContent( $key, $language, |
— | — | @@ -82,7 +80,6 @@ |
83 | 81 | * @param $namespace Mixed: the number of the namespace to look in for. |
84 | 82 | */ |
85 | 83 | public static function getContents( $titles, $namespace ) { |
86 | | - wfMemIn( __METHOD__ ); wfProfileIn( __METHOD__ ); |
87 | 84 | $dbr = wfGetDB( DB_SLAVE ); |
88 | 85 | $rows = $dbr->select( array( 'page', 'revision', 'text' ), |
89 | 86 | array( 'page_title', 'old_text', 'old_flags', 'rev_user_text' ), |
— | — | @@ -106,11 +103,9 @@ |
107 | 104 | $rows->free(); |
108 | 105 | |
109 | 106 | return $titles; |
110 | | - wfProfileOut( __METHOD__ ); wfMemOut( __METHOD__ ); |
111 | 107 | } |
112 | 108 | |
113 | 109 | public static function translationChanges( $hours = 24 ) { |
114 | | - wfMemIn( __METHOD__ ); |
115 | 110 | global $wgTranslateMessageNamespaces; |
116 | 111 | |
117 | 112 | $dbr = wfGetDB( DB_SLAVE ); |
— | — | @@ -137,7 +132,6 @@ |
138 | 133 | $rows[] = $row; |
139 | 134 | } |
140 | 135 | $dbr->freeResult( $res ); |
141 | | - wfMemOut( __METHOD__ ); |
142 | 136 | return $rows; |
143 | 137 | } |
144 | 138 | |
— | — | @@ -256,7 +250,6 @@ |
257 | 251 | } |
258 | 252 | |
259 | 253 | public static function getLanguageName( $code, $native = false, $language = 'en' ) { |
260 | | - wfMemIn( __METHOD__ ); |
261 | 254 | if ( !$native && is_callable( array( 'LanguageNames', 'getNames' ) ) ) { |
262 | 255 | $languages = LanguageNames::getNames( $language , |
263 | 256 | LanguageNames::FALLBACK_NORMAL, |
— | — | @@ -279,12 +272,10 @@ |
280 | 273 | break; |
281 | 274 | } |
282 | 275 | $code = implode( '-', $parts ); |
283 | | - wfMemOut( __METHOD__ ); |
284 | 276 | return isset( $languages[$code] ) ? $languages[$code] . $suffix : false; |
285 | 277 | } |
286 | 278 | |
287 | 279 | public static function languageSelector( $language, $selectedId ) { |
288 | | - wfMemIn( __METHOD__ ); |
289 | 280 | global $wgLang; |
290 | 281 | if ( is_callable( array( 'LanguageNames', 'getNames' ) ) ) { |
291 | 282 | $languages = LanguageNames::getNames( $language, |
— | — | @@ -301,7 +292,6 @@ |
302 | 293 | foreach ( $languages as $code => $name ) { |
303 | 294 | $selector->addOption( "$code - $name", $code ); |
304 | 295 | } |
305 | | - wfMemOut( __METHOD__ ); |
306 | 296 | return $selector->getHTML(); |
307 | 297 | } |
308 | 298 | |
— | — | @@ -316,7 +306,6 @@ |
317 | 307 | } |
318 | 308 | |
319 | 309 | public static function messageIndex() { |
320 | | - wfMemIn( __METHOD__ ); |
321 | 310 | $keyToGroup = array(); |
322 | 311 | if ( file_exists( TRANSLATE_INDEXFILE ) ) { |
323 | 312 | $keyToGroup = unserialize( file_get_contents( TRANSLATE_INDEXFILE ) ); |
— | — | @@ -324,7 +313,6 @@ |
325 | 314 | wfDebug( __METHOD__ . ": Message index missing." ); |
326 | 315 | } |
327 | 316 | |
328 | | - wfMemOut( __METHOD__ ); |
329 | 317 | return $keyToGroup; |
330 | 318 | } |
331 | 319 | |
Index: trunk/extensions/Translate/revtags.sql |
— | — | @@ -0,0 +1,27 @@ |
| 2 | +-- These tables could go into core someday |
| 3 | + |
| 4 | +-- Revision tag type list |
| 5 | +CREATE TABLE /*$wgDBprefix*/revtag_type ( |
| 6 | + rtt_id int not null primary key auto_increment, |
| 7 | + rtt_name varbinary(60) not null |
| 8 | +) /*$wgDBTableOptions*/; |
| 9 | +CREATE UNIQUE INDEX /*i*/rtt_name ON /*$wgDBprefix*/revtag_type (rtt_name); |
| 10 | + |
| 11 | +-- Revision tags |
| 12 | +CREATE TABLE /*$wgDBprefix*/revtag ( |
| 13 | + -- Link to revtag_type.rtt_id |
| 14 | + rt_type int not null, |
| 15 | + |
| 16 | + -- Link to page.page_id |
| 17 | + rt_page int not null, |
| 18 | + |
| 19 | + -- Link to revision.rev_id |
| 20 | + rt_revision int not null, |
| 21 | + |
| 22 | + rt_value blob null |
| 23 | +) /*$wgDBTableOptions*/; |
| 24 | +-- Index for finding all revisions in a page with a given tag |
| 25 | +CREATE INDEX /*i*/rt_type_page_revision ON /*$wgDBprefix*/revtag |
| 26 | +(rt_type, rt_page, rt_revision); |
| 27 | +-- Index for finding the tags on a given revision |
| 28 | +CREATE INDEX /*i*/rt_revision_type ON /*$wgDBprefix*/revtag (rt_revision, rt_type); |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/revtags.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 29 | + native |
Index: trunk/extensions/Translate/MessageGroups.php |
— | — | @@ -593,12 +593,28 @@ |
594 | 594 | throw new MWException( 'Invalid title' ); |
595 | 595 | } |
596 | 596 | $this->title = $title; |
597 | | - $this->namespaces = array( $title->getNamespace(), $title->getNamespace() + 1 ); |
| 597 | + $this->namespaces = array( NS_TRANSLATIONS, NS_TRANSLATIONS_TALK ); |
598 | 598 | |
599 | 599 | } |
600 | 600 | |
601 | 601 | public function getDefinitions() { |
602 | | - return TranslateTag::parseSectionDefinitions( $this->title, $this->namespaces ); |
| 602 | + $dbr = wfGetDB( DB_SLAVE ); |
| 603 | + $tables = 'translate_sections'; |
| 604 | + $vars = array( 'trs_key', 'trs_text' ); |
| 605 | + $conds = array( 'trs_page' => $this->title->getArticleId() ); |
| 606 | + $res = $dbr->select( $tables, $vars, $conds, __METHOD__ ); |
| 607 | + |
| 608 | + $defs = array(); |
| 609 | + $prefix = $this->title->getPrefixedText() . '/'; |
| 610 | + foreach ( $res as $r ) { |
| 611 | + $defs[$r->trs_key] = $r->trs_text; |
| 612 | + } |
| 613 | + // Some hacks to get nice order for the messages |
| 614 | + ksort( $defs ); |
| 615 | + $new_defs = array(); |
| 616 | + foreach ( $defs as $k => $v ) $new_defs[$prefix.$k] = $v; |
| 617 | + |
| 618 | + return $new_defs; |
603 | 619 | } |
604 | 620 | |
605 | 621 | public function load( $code ) { |
— | — | @@ -638,20 +654,26 @@ |
639 | 655 | $a->addAll(); |
640 | 656 | } |
641 | 657 | |
642 | | - global $wgTranslateCategory, $wgTranslateCC; |
643 | | -/* wfLoadExtensionMessages( 'Translate' ); |
644 | | - $cat = Category::newFromName( wfMsgForContent( 'translate-tag-category' ) ); |
645 | | - $titles = $cat->getMembers(); |
646 | | - foreach ( $titles as $t ) { |
647 | | - $title = $t->getPrefixedText(); |
648 | | - $id = "page|$title"; |
649 | | - $wgTranslateCC[$id] = new WikiPageMessageGroup( $id, $title ); |
650 | | - $wgTranslateCC[$id]->setLabel( $title ); |
651 | | - $wgTranslateCC[$id]->setDescription( wfMsgNoTrans( 'translate-tag-page-desc', $title ) ); |
| 658 | + global $wgTranslateCC; |
652 | 659 | |
653 | | - }*/ |
| 660 | + global $wgEnablePageTranslation; |
| 661 | + if ( $wgEnablePageTranslation ) { |
| 662 | + $dbr = wfGetDB( DB_SLAVE ); |
| 663 | + |
| 664 | + $tables = array( 'page', 'revtag', 'revtag_type' ); |
| 665 | + $vars = array( 'page_id', 'page_namespace', 'page_title', 'rt_revision' ); |
| 666 | + $conds = array( 'page_id=rt_page', 'rtt_id=rt_type', 'rtt_name' => 'tp:mark' ); |
| 667 | + $options = array( 'GROUP BY' => 'page_id' ); |
| 668 | + $res = $dbr->select( $tables, $vars, $conds, __METHOD__ ); |
| 669 | + foreach ( $res as $r ) { |
| 670 | + $title = Title::makeTitle( $r->page_namespace, $r->page_title )->getPrefixedText(); |
| 671 | + $id = "page|$title"; |
| 672 | + $wgTranslateCC[$id] = new WikiPageMessageGroup( $id, $title ); |
| 673 | + $wgTranslateCC[$id]->setLabel( $title ); |
| 674 | + $wgTranslateCC[$id]->setDescription( wfMsgNoTrans( 'translate-tag-page-desc', $title ) ); |
| 675 | + } |
| 676 | + } |
654 | 677 | |
655 | | - global $wgTranslateCC; |
656 | 678 | wfRunHooks( 'TranslatePostInitGroups', array( &$wgTranslateCC ) ); |
657 | 679 | $loaded = true; |
658 | 680 | } |
Index: trunk/extensions/Translate/tag/Utils.php |
— | — | @@ -1,180 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class TranslateTagUtils { |
5 | | - const T_SOURCE = 1; |
6 | | - const T_TRANSLATION = 2; |
7 | | - const T_ANY = 3; |
8 | | - |
9 | | - const STATUSEXPIRY = 600; // in seconds |
10 | | - |
11 | | - public static function isTagPage( Title $title, $type = self::T_ANY ) { |
12 | | - list( $key, $code ) = self::keyAndCode( $title ); |
13 | | - |
14 | | - if ( $type === self::T_SOURCE && $code !== false ) return false; |
15 | | - if ( $type === self::T_TRANSLATION && $code === false ) return false; |
16 | | - |
17 | | - |
18 | | - static $members = null; |
19 | | - if ( $members === null ) { |
20 | | - wfLoadExtensionMessages( 'Translate' ); |
21 | | - $cat = Category::newFromName( wfMsgForContent( 'translate-tag-category' ) ); |
22 | | - $members = $cat->getMembers(); |
23 | | - } |
24 | | - |
25 | | - foreach ( $members as $member ) { |
26 | | - if ( $member->getNamespace() === $title->getNamespace() ) { |
27 | | - if ( $member->getDBkey() === $key ) return true; |
28 | | - } |
29 | | - } |
30 | | - return false; |
31 | | - } |
32 | | - |
33 | | - public static function keyAndCode( Title $title ) { |
34 | | - $dbkey = $title->getDBkey(); |
35 | | - $code = false; |
36 | | - |
37 | | - $pos = strrpos( $dbkey, '/' ); |
38 | | - if ( $pos !== false ) { |
39 | | - $code = substr( $dbkey, $pos + 1 ); |
40 | | - $key = substr( $dbkey, 0, $pos ); |
41 | | - if ( self::isValidCode( $code ) ) { |
42 | | - return array( $key, $code ); |
43 | | - } |
44 | | - } |
45 | | - |
46 | | - return array( $dbkey, false ); |
47 | | - } |
48 | | - |
49 | | - public static function isValidCode( $code ) { |
50 | | - $codes = Language::getLanguageNames(); |
51 | | - return isset( $codes[$code] ); |
52 | | - } |
53 | | - |
54 | | - /** |
55 | | - * Returns a database resource which can be looped and text can be fetched via |
56 | | - * Revision::getRevisionText( $row ). |
57 | | - */ |
58 | | - public static function getPageContent( $namespace, $pages ) { |
59 | | - $dbr = wfGetDB( DB_SLAVE ); |
60 | | - $rows = $dbr->select( array( 'page', 'revision', 'text' ), |
61 | | - array( 'page_title', 'old_text', 'old_flags' ), |
62 | | - array( |
63 | | - 'page_is_redirect' => 0, |
64 | | - 'page_namespace' => $namespace, |
65 | | - 'page_latest=rev_id', |
66 | | - 'rev_text_id=old_id', |
67 | | - 'page_title' => $pages, |
68 | | - ), |
69 | | - __METHOD__ |
70 | | - ); |
71 | | - |
72 | | - return $rows; |
73 | | - } |
74 | | - |
75 | | - public static function getTagPageSource( Title $title ) { |
76 | | - $text = null; |
77 | | - $res = self::getPageContent( $title->getNamespace(), $title->getDBkey() ); |
78 | | - |
79 | | - foreach ( $res as $_ ) { |
80 | | - $text = Revision::getRevisionText( $_ ); |
81 | | - break; |
82 | | - } |
83 | | - |
84 | | - return $text; |
85 | | - } |
86 | | - |
87 | | - public static function codefyTitle( Title $title, $code ) { |
88 | | - global $wgContLang; |
89 | | - $namespace = $title->getNamespace(); |
90 | | - $dbkey = $title->getDBkey(); |
91 | | - if ( $code !== $wgContLang->getCode() ) $dbkey .= "/$code"; |
92 | | - return Title::makeTitle( $namespace, "$dbkey" ); |
93 | | - } |
94 | | - |
95 | | - public static function deCodefy( Title $title ) { |
96 | | - // Don't remove trail if source page |
97 | | - $type = self::T_SOURCE; |
98 | | - if ( self::isTagPage( $title, $type ) ) return $title; |
99 | | - |
100 | | - list( , $code ) = self::keyAndCode( $title ); |
101 | | - $namespace = $title->getNamespace(); |
102 | | - $dbkey = $title->getDBkey(); |
103 | | - $suflen = strlen( $code ); |
104 | | - if ( $suflen ) $dbkey = substr( $dbkey, 0, - ( $suflen + 1 ) ); |
105 | | - return Title::makeTitle( $namespace, $dbkey ); |
106 | | - } |
107 | | - |
108 | | - public static function getTranslationPercent( $pages, $code, $namespace ) { |
109 | | - $pages = self::mapAppend( $pages, "/$code" ); |
110 | | - $n = count( $pages ); |
111 | | - $sectionSize = 1 / $n; |
112 | | - $total = 0; |
113 | | - |
114 | | - $res = self::getPageContent( $namespace, $pages ); |
115 | | - foreach ( $res as $_ ) { |
116 | | - $text = Revision::getRevisionText( $_ ); |
117 | | - if ( strpos( $text, TRANSLATE_FUZZY ) !== false ) { |
118 | | - $total += $sectionSize / 2; |
119 | | - } else { |
120 | | - $total += $sectionSize; |
121 | | - } |
122 | | - } |
123 | | - |
124 | | - return $total; |
125 | | - } |
126 | | - |
127 | | - public static function getPercentages( Title $title ) { |
128 | | - // Check if relevant at all |
129 | | - if ( !TranslateTagUtils::isTagPage( $title ) ) return false; |
130 | | - |
131 | | - // Normalise title, just to be sure |
132 | | - $title = self::deCodefy( $title ); |
133 | | - |
134 | | - // Check the memory cache, as this is very slow to calculate |
135 | | - global $wgMemc; |
136 | | - $memcKey = wfMemcKey( 'translate', 'status', $title->getPrefixedText() ); |
137 | | - $cache = $wgMemc->get( $memcKey ); |
138 | | - if ( is_array( $cache ) ) return $cache; |
139 | | - |
140 | | - // Fetch the available translation pages from database |
141 | | - $dbr = wfGetDB( DB_SLAVE ); |
142 | | - $likePattern = $dbr->escapeLike( $title->getDBkey() ) . '/%%'; |
143 | | - $res = $dbr->select( |
144 | | - 'page', |
145 | | - array( 'page_namespace', 'page_title' ), |
146 | | - array( |
147 | | - 'page_namespace' => $title->getNamespace(), |
148 | | - "page_title LIKE '$likePattern'" |
149 | | - ), __METHOD__ ); |
150 | | - |
151 | | - $titles = TitleArray::newFromResult( $res ); |
152 | | - |
153 | | - // Calculate percentages for the available translations |
154 | | - $tag = TranslateTag::newFromTitle( $title ); |
155 | | - $pages = $tag->getSectionPages(); |
156 | | - $namespace = $tag->getNamespace( $title ); |
157 | | - |
158 | | - |
159 | | - $temp = array(); |
160 | | - |
161 | | - |
162 | | - $type = TranslateTagUtils::T_TRANSLATION; |
163 | | - foreach ( $titles as $_ ) { |
164 | | - if ( !TranslateTagUtils::isTagPage( $_, $type ) ) continue; |
165 | | - list( , $code ) = TranslateTagUtils::keyAndcode( $_ ); |
166 | | - $percent = TranslateTagUtils::getTranslationPercent( $pages, $code, $namespace ); |
167 | | - $temp[$code] = $percent; |
168 | | - } |
169 | | - $temp['en'] = '1'; |
170 | | - |
171 | | - // Ideally there would be some kind of purging here |
172 | | - $wgMemc->set( $memcKey, $temp, self::STATUSEXPIRY ); |
173 | | - return $temp; |
174 | | - } |
175 | | - |
176 | | - public static function mapAppend( array $array, $suffix ) { |
177 | | - $function = create_function( '$_', "return \$_ . '$suffix';" ); |
178 | | - return array_map( $function, $array ); |
179 | | - } |
180 | | - |
181 | | -} |
Index: trunk/extensions/Translate/tag/FuzzyJob.php |
— | — | @@ -1,93 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * Job for making translation fuzzy when the definition changes. |
6 | | - * |
7 | | - * @addtogroup Extensions |
8 | | - * |
9 | | - * @author Niklas Laxström |
10 | | - * @copyright Copyright © 2008, Niklas Laxström |
11 | | - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
12 | | - */ |
13 | | -/* |
14 | | - * Based on Tim Starling's DoubleRedirectJob.php |
15 | | - */ |
16 | | -class FuzzyJob extends Job { |
17 | | - static $user; |
18 | | - |
19 | | - public static function fuzzyPages( $reason, $comment, Title $prefix ) { |
20 | | - $dbr = wfGetDB( DB_SLAVE ); |
21 | | - $likePattern = $dbr->escapeLike( $prefix->getDBkey() ) . '/%%'; |
22 | | - |
23 | | - $res = $dbr->select( |
24 | | - 'page', |
25 | | - array( 'page_namespace', 'page_title' ), |
26 | | - array( |
27 | | - 'page_namespace' => $prefix->getNamespace(), |
28 | | - "page_title LIKE '$likePattern'" |
29 | | - ), __METHOD__ ); |
30 | | - // Nothind to update |
31 | | - if ( !$res->numRows() ) return; |
32 | | - |
33 | | - $jobs = array(); |
34 | | - foreach ( $res as $row ) { |
35 | | - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); |
36 | | - // Should not happen, but who knows |
37 | | - if ( !$title ) continue; |
38 | | - |
39 | | - $jobs[] = new self( $title, array( |
40 | | - 'reason' => $reason, |
41 | | - 'comment' => $comment ) ); |
42 | | - } |
43 | | - Job::batchInsert( $jobs ); |
44 | | - } |
45 | | - |
46 | | - function __construct( $title, $params = false, $id = 0 ) { |
47 | | - parent::__construct( __CLASS__, $title, $params, $id ); |
48 | | - $this->reason = $params['reason']; |
49 | | - $this->comment = $params['comment']; |
50 | | - $this->removeDuplicates = false; |
51 | | - } |
52 | | - |
53 | | - function run() { |
54 | | - $targetRev = Revision::newFromTitle( $this->title ); |
55 | | - if ( !$targetRev ) { |
56 | | - wfDebug( __METHOD__ . ": target page deleted, ignoring\n" ); |
57 | | - return true; |
58 | | - } |
59 | | - $text = $targetRev->getText(); |
60 | | - |
61 | | - # Add fuzzy tag if there isn't already one |
62 | | - if ( strpos( $text, TRANSLATE_FUZZY ) === false ) { |
63 | | - $text = TRANSLATE_FUZZY . $text; |
64 | | - } |
65 | | - |
66 | | - # Add a comment to help translators to identify what has changed |
67 | | - $text .= "\n" . $this->reason; |
68 | | - |
69 | | - # Save it |
70 | | - global $wgUser; |
71 | | - $oldUser = $wgUser; |
72 | | - $wgUser = $this->getUser(); |
73 | | - $article = new Article( $this->title ); |
74 | | - $article->doEdit( $text, $this->comment, EDIT_UPDATE | EDIT_FORCE_BOT ); |
75 | | - $wgUser = $oldUser; |
76 | | - |
77 | | - return true; |
78 | | - } |
79 | | - |
80 | | - /** |
81 | | - * Get a user object for doing edits, from a request-lifetime cache |
82 | | - */ |
83 | | - function getUser() { |
84 | | - if ( !self::$user ) { |
85 | | - global $wgTranslateFuzzyBotName; |
86 | | - self::$user = User::newFromName( $wgTranslateFuzzyBotName, false ); |
87 | | - if ( !self::$user->isLoggedIn() ) { |
88 | | - self::$user->addToDatabase(); |
89 | | - } |
90 | | - } |
91 | | - return self::$user; |
92 | | - } |
93 | | -} |
94 | | - |
Index: trunk/extensions/Translate/tag/Hooks.php |
— | — | @@ -1,224 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class TranslateTagHooks { |
5 | | - |
6 | | - public static function disableEdit( $title, $user, $action, &$errors ) { |
7 | | - if ( $action !== 'edit' ) return true; |
8 | | - |
9 | | - $type = TranslateTagUtils::T_TRANSLATION; |
10 | | - if ( TranslateTagUtils::isTagPage( $title, $type ) ) { |
11 | | - wfLoadExtensionMessages( 'Translate' ); |
12 | | - |
13 | | - $sourceTitle = Title::makeTitle( |
14 | | - $title->getNamespace(), |
15 | | - // TODO: breaks if the page being translated is a subpage |
16 | | - $title->getBaseText() |
17 | | - ); |
18 | | - |
19 | | - list( , $code ) = TranslateTagUtils::keyAndcode( $title ); |
20 | | - $par = array( |
21 | | - 'group' => 'page|' . $sourceTitle->getPrefixedText(), |
22 | | - 'language' => $code, |
23 | | - 'task' => 'view', |
24 | | - ); |
25 | | - $translate = SpecialPage::getTitleFor( 'Translate' ); |
26 | | - |
27 | | - $errors[] = array( 'translate-tag-noedit', |
28 | | - $sourceTitle->getFullURL( 'action=edit' ), |
29 | | - $translate->getFullUrl( wfArrayToCgi( $par ) ) |
30 | | - ); |
31 | | - return false; |
32 | | - } |
33 | | - |
34 | | - return true; |
35 | | - } |
36 | | - |
37 | | - public static function renderTagPage( $parser, &$text, $state ) { |
38 | | - $title = $parser->getTitle(); |
39 | | - list( , $code ) = TranslateTagUtils::keyAndcode( $title ); |
40 | | - |
41 | | - if ( strpos( $text, '</translate>' ) !== false ) { |
42 | | - $tag = TranslateTag::getInstance(); |
43 | | - $text = $tag->renderPage( $text, $title ); |
44 | | - |
45 | | - wfLoadExtensionMessages( 'Translate' ); |
46 | | - $cat = wfMsgForContent( 'translate-tag-category' ); |
47 | | - $text .= " [[Category:$cat]]"; |
48 | | - $text = $tag->getHeader( $title ) . $text; |
49 | | - } elseif ( strpos( $text, '<translate />' ) !== false ) { |
50 | | - $text = str_replace( '<translate />', '', $text ); |
51 | | - |
52 | | - list( , $code ) = TranslateTagUtils::keyAndCode( $title ); |
53 | | - $title = TranslateTagUtils::deCodefy( $title ); |
54 | | - |
55 | | - $tag = TranslateTag::getInstance(); |
56 | | - $text = $tag->renderPage( $text, $title ); |
57 | | - $text = $tag->getHeader( $title, $code ) . $text; |
58 | | - |
59 | | - $lobj = Language::factory( $code ); |
60 | | - $parser->mOptions->setTargetLanguage( $lobj ); |
61 | | - } |
62 | | - |
63 | | - return true; |
64 | | - } |
65 | | - |
66 | | - public static function injectCss( $outputpage, $text ) { |
67 | | - TranslateUtils::injectCSS(); |
68 | | - return true; |
69 | | - } |
70 | | - |
71 | | - public static function onSave( $article, $user, $text, $summary, $minor, |
72 | | - $_, $_, $flags, $revision ) { |
73 | | - global $wgTranslateFuzzyBotName; |
74 | | - |
75 | | - // Do not trigger renders for bots |
76 | | - // It is more efficient to update |
77 | | - if ( $user->getName() === $wgTranslateFuzzyBotName ) return true; |
78 | | - |
79 | | - $flags |= EDIT_SUPPRESS_RC; // No point listing them twice |
80 | | - $flags &= ~EDIT_NEW & ~EDIT_UPDATE; // We don't know |
81 | | - |
82 | | - $title = $article->getTitle(); |
83 | | - self::updateTranslationChange( $title, $flags, $summary ); |
84 | | - self::updateSourceChange( $title, $flags, $summary ); |
85 | | - |
86 | | - return true; |
87 | | - |
88 | | - } |
89 | | - |
90 | | - public static function updateTranslationChange( Title $translation, $flags, $summary ) { |
91 | | - list( $key, $code ) = TranslateTagUtils::keyAndcode( $translation ); |
92 | | - |
93 | | - // Figure out group |
94 | | - $namespace = $translation->getNamespace(); |
95 | | - $groupKey = TranslateUtils::messageKeyToGroup( $namespace, $key ); |
96 | | - $group = MessageGroups::getGroup( $groupKey ); |
97 | | - if ( !$group instanceof WikiPageMessageGroup ) return; |
98 | | - |
99 | | - $source = $group->title; |
100 | | - $target = TranslateTagUtils::codefyTitle( $group->title, $code ); |
101 | | - |
102 | | - global $wgUser; |
103 | | - $user = $wgUser->getName(); |
104 | | - RenderJob::renderPage( $source, $target, $user, $summary, $flags, true ); |
105 | | - } |
106 | | - |
107 | | - public static function updateSourceChange( Title $source, $flags, $summary ) { |
108 | | - global $wgUser; |
109 | | - $user = $wgUser->getName(); |
110 | | - |
111 | | - $type = TranslateTagUtils::T_SOURCE; |
112 | | - |
113 | | - if ( !TranslateTagUtils::isTagPage( $source, $type ) ) return true; |
114 | | - |
115 | | - $dbr = wfGetDB( DB_SLAVE ); |
116 | | - $likePattern = $dbr->escapeLike( $source->getDBkey() ) . '/%%'; |
117 | | - $res = $dbr->select( |
118 | | - 'page', |
119 | | - array( 'page_namespace', 'page_title' ), |
120 | | - array( |
121 | | - 'page_namespace' => $source->getNamespace(), |
122 | | - "page_title LIKE '$likePattern'" |
123 | | - ), __METHOD__ ); |
124 | | - |
125 | | - $titles = TitleArray::newFromResult( $res ); |
126 | | - $type = TranslateTagUtils::T_TRANSLATION; |
127 | | - foreach ( $titles as $target ) { |
128 | | - if ( !TranslateTagUtils::isTagPage( $target, $type ) ) continue; |
129 | | - RenderJob::renderPage( $source, $target, $user, $summary, $flags ); |
130 | | - } |
131 | | - } |
132 | | - |
133 | | - public static function addSidebar( $out, $tpl ) { |
134 | | - $title = TranslateTagUtils::deCodefy( $out->mTitle ); |
135 | | - $status = TranslateTagUtils::getPercentages( $title ); |
136 | | - if ( !$status ) return true; |
137 | | - |
138 | | - global $wgLang; |
139 | | - |
140 | | - // Sort by translation percentage |
141 | | - arsort( $status, SORT_NUMERIC ); |
142 | | - |
143 | | - foreach ( $status as $code => $percent ) { |
144 | | - $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() ); |
145 | | - $percent = $wgLang->formatNum( round( 100 * $percent ) ); |
146 | | - $label = "$name ($percent%)"; |
147 | | - |
148 | | - $_title = TranslateTagUtils::codefyTitle( $title, $code ); |
149 | | - |
150 | | - $items[] = array( |
151 | | - 'text' => $label, |
152 | | - 'href' => $_title->getFullURL(), |
153 | | - 'id' => 'foo', |
154 | | - ); |
155 | | - } |
156 | | - |
157 | | - $sidebar = $out->buildSidebar(); |
158 | | - $sidebar['TRANSLATIONS'] = $items; |
159 | | - |
160 | | - $tpl->set( 'sidebar', $sidebar ); |
161 | | - |
162 | | - return true; |
163 | | - } |
164 | | - |
165 | | - public static function languages( $data, $params, $parser ) { |
166 | | - $title = TranslateTagUtils::deCodefy( $parser->getTitle() ); |
167 | | - $status = TranslateTagUtils::getPercentages( $title ); |
168 | | - if ( !$status ) return ''; |
169 | | - |
170 | | - global $wgLang; |
171 | | - |
172 | | - // Sort by language code |
173 | | - ksort( $status ); |
174 | | - |
175 | | - // $lobj = $parser->getFunctionLang(); |
176 | | - $sk = $parser->mOptions->getSkin(); |
177 | | - $legend = wfMsg( 'otherlanguages' ); |
178 | | - |
179 | | - global $wgTranslateCssLocation; |
180 | | - |
181 | | - $languages = array(); |
182 | | - foreach ( $status as $code => $percent ) { |
183 | | - $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() ); |
184 | | - |
185 | | - $percent *= 100; |
186 | | - if ( $percent < 20 ) $image = 1; |
187 | | - elseif ( $percent < 40 ) $image = 2; |
188 | | - elseif ( $percent < 60 ) $image = 3; |
189 | | - elseif ( $percent < 80 ) $image = 4; |
190 | | - else $image = 5; |
191 | | - |
192 | | - $percent = Xml::element( 'img', array( |
193 | | - 'src' => "$wgTranslateCssLocation/images/prog-$image.png" |
194 | | - ) ); |
195 | | - $label = "$name $percent"; |
196 | | - |
197 | | - $_title = TranslateTagUtils::codefyTitle( $title, $code ); |
198 | | - $languages[] = $sk->link( $_title, $label ); |
199 | | - } |
200 | | - |
201 | | - $languages = implode( ' • ', $languages ); |
202 | | - |
203 | | - return <<<FOO |
204 | | -<div class="LanguageLinks"> |
205 | | -<table style="border: 1px solid rgb(170, 170, 170); background: rgb(246, 249, 237) none repeat scroll 0% 0%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; border-collapse: collapse; line-height: 1.2;" border="0" cellpadding="2" cellspacing="0" rules="all" width="100%"> |
206 | | - |
207 | | -<tbody><tr valign="top"> |
208 | | -<td style="border-right: 1px solid rgb(170, 170, 170); padding: 0.5em; background: rgb(238, 243, 226) none repeat scroll 0% 0%; width: 200px; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"><b>$legend:</b> |
209 | | - |
210 | | -</td><td style="padding: 0.5em;">$languages</td></tr></tbody></table> |
211 | | -</div> |
212 | | -FOO; |
213 | | - } |
214 | | - |
215 | | - public static function onTemplate( $parser, &$title, &$skip, &$id ) { |
216 | | - global $wgLang; |
217 | | - $type = TranslateTagUtils::T_SOURCE; |
218 | | - if ( TranslateTagUtils::isTagPage( $title, $type ) ) { |
219 | | - $newtitle = TranslateTagUtils::codefyTitle( $title, $wgLang->getCode() ); |
220 | | - if ( $newtitle->exists() ) $title = $newtitle; |
221 | | - } |
222 | | - return true; |
223 | | - } |
224 | | - |
225 | | -} |
\ No newline at end of file |
Index: trunk/extensions/Translate/tag/Tag.php |
— | — | @@ -1,476 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * Code for handling pages marked with <translate> tag. This class does parsing. |
6 | | - * |
7 | | - * Section: piece of text, separated usually with two new lines, that is used as |
8 | | - * an single translation unit for change tracking and so on. |
9 | | - * Occurance: contents of <translate>..</translate>, which there can be many on |
10 | | - * one page. |
11 | | - * |
12 | | - * @addtogroup Extensions |
13 | | - * |
14 | | - * @author Niklas Laxström |
15 | | - * @copyright Copyright © 2008, Niklas Laxström |
16 | | - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
17 | | - */ |
18 | | -class TranslateTag { |
19 | | - /** Counter for nth <translate>..</translate> invocation to know to which |
20 | | - * occurance each section belongs to, when doing save-time parsing. */ |
21 | | - var $invocation = 0; |
22 | | - |
23 | | - // Deprecated, TODO: write a suitable factory functions for replacement |
24 | | - public static function getInstance() { |
25 | | - $obj = new self; |
26 | | - // $obj->reset(); |
27 | | - return $obj; |
28 | | - } |
29 | | - |
30 | | - /** Factory for creating a new instance from Title object */ |
31 | | - public static function newFromTitle( Title $title ) { |
32 | | - $obj = new self(); |
33 | | - $obj->invocation = 0; |
34 | | - $obj->title = $title; |
35 | | - |
36 | | - $text = TranslateTagUtils::getTagPageSource( $title ); |
37 | | - |
38 | | - $cb = array( $obj, 'parseMetadata' ); |
39 | | - preg_replace_callback( self::METADATA, $cb, $text ); |
40 | | - return $obj; |
41 | | - } |
42 | | - |
43 | | - /** Factory for creating a new instance from occurance */ |
44 | | - public static function newFromTagContents( &$text, $code ) { |
45 | | - $obj = new self(); |
46 | | - $obj->invocation = - 1; |
47 | | - $obj->title = null; |
48 | | - $obj->renderCode = $code; |
49 | | - |
50 | | - $cb = array( $obj, 'parseMetadata' ); |
51 | | - preg_replace_callback( self::METADATA, $cb, $text ); |
52 | | - return $obj; |
53 | | - } |
54 | | - |
55 | | - // TODO: Move to hook or utils? |
56 | | - // Remember to to use TranslateUtils::injectCSS() |
57 | | - public function getHeader( Title $title, $code = false ) { |
58 | | - global $wgLang; |
59 | | - $par = array( |
60 | | - 'group' => 'page|' . $title->getPrefixedText(), |
61 | | - 'language' => $code ? $code : $wgLang->getCode(), |
62 | | - 'task' => 'view' |
63 | | - ); |
64 | | - $translate = SpecialPage::getTitleFor( 'Translate' ); |
65 | | - $link = $translate->getFullUrl( wfArrayToCgi( $par ) ); |
66 | | - |
67 | | - wfLoadExtensionMessages( 'Translate' ); |
68 | | - $linkDesc = wfMsgNoTrans( 'translate-tag-translate-link-desc' ); |
69 | | - $legendText = wfMsgNoTrans( 'translate-tag-legend' ); |
70 | | - $legendOther = wfMsgNoTrans( 'translate-tag-legend-fallback' ); |
71 | | - $legendFuzzy = wfMsgNoTrans( 'translate-tag-legend-fuzzy' ); |
72 | | - |
73 | | - |
74 | | - $legend = "<div style=\"font-size: x-small\">"; |
75 | | - $legend .= "<span class='plainlinks'>[$link $linkDesc]</span> "; |
76 | | - if ( $code ) { |
77 | | - $legend .= " | $legendText <span class=\"mw-translate-other\">$legendOther</span>"; |
78 | | - $legend .= " <span class=\"mw-translate-fuzzy\">$legendFuzzy</span>"; |
79 | | - } |
80 | | - // TODO: the following text will of course be removed :) |
81 | | - $legend .= ' | This page is translatable using the experimental wiki page translation feature.</div>'; |
82 | | - $legend .= "\n----\n"; |
83 | | - return $legend; |
84 | | - } |
85 | | - |
86 | | - // Some regexps |
87 | | - const METADATA = '~\n?<!--TS(.*?)-->\n?~us'; |
88 | | - const PATTERN_COMMENT = '~\n?<!--T[=:;](.*?)-->\n?~u'; |
89 | | - const PATTERN_TAG = '~(<translate>)\n?(.+?)(</translate>)~us'; |
90 | | - |
91 | | - // Renders a translation page to given language |
92 | | - public function renderPage( $text, Title $title, $code = false ) { |
93 | | - $this->renderTitle = $title; |
94 | | - $this->renderCode = $code; |
95 | | - $cb = array( $this, 'parseMetadata' ); |
96 | | - preg_replace_callback( self::METADATA, $cb, $text ); |
97 | | - |
98 | | - $cb = array( $this, 'replaceTagCb' ); |
99 | | - $text = StringUtils::delimiterReplaceCallback( '<translate>', '</translate>', $cb, $text ); |
100 | | - |
101 | | - return preg_replace_callback( self::PATTERN_TAG, $cb, $text ); |
102 | | - } |
103 | | - |
104 | | - public function replaceTagCb( $matches ) { |
105 | | - return $this->replaceTag( $matches[1] ); |
106 | | - } |
107 | | - |
108 | | - /** |
109 | | - * Replaces sections with translations if available, and substitutes variables |
110 | | - */ |
111 | | - public function replaceTag( $input ) { |
112 | | - $regex = $this->getSectionRegex(); |
113 | | - $matches = array(); |
114 | | - preg_match_all( $regex, $input, $matches, PREG_SET_ORDER ); |
115 | | - foreach ( $matches as $match ) { |
116 | | - $key = $match['id']; |
117 | | - $section = $match['section']; |
118 | | - |
119 | | - $translation = null; |
120 | | - if ( $this->renderCode ) { |
121 | | - $translation = $this->getContents( $this->renderTitle, $key, $this->renderCode ); |
122 | | - } |
123 | | - |
124 | | - if ( $translation !== null ) { |
125 | | - $vars = $this->extractVariablesFromSection( $section ); |
126 | | - foreach ( $vars as $v ) { |
127 | | - list( $search, $replace ) = $v; |
128 | | - $translation = str_replace( $search, $replace, $translation ); |
129 | | - } |
130 | | - |
131 | | - // Inject the translation in the source by replacing the definition with it |
132 | | - $input = str_replace( $section, $translation, $input ); |
133 | | - } else { |
134 | | - // Do in-place replace of variables, copy to keep $section intact for |
135 | | - // the replace later |
136 | | - $replace = $section; |
137 | | - // Replace with newline to avoid eating the newline after header |
138 | | - // and it doesn't hurt because surrounding is trimmed for inlines |
139 | | - $replace = preg_replace( self::PATTERN_COMMENT, "\n", $replace ); |
140 | | - $this->extractVariablesFromSection( $replace, 'replace' ); |
141 | | - |
142 | | - if ( $this->renderCode ) { |
143 | | - $replace = $this->wrapAndClean( 'mw-translate-other', $replace ); |
144 | | - } |
145 | | - $input = str_replace( $section, $replace, $input ); |
146 | | - $input = str_replace( $match['holder'], '', $input ); |
147 | | - } |
148 | | - } |
149 | | - |
150 | | - // Clean any comments there may be left |
151 | | - $input = preg_replace( self::PATTERN_PLACEHOLDER, '', $input ); |
152 | | - $input = preg_replace( self::METADATA, '', $input ); |
153 | | - |
154 | | - return trim( $input ); |
155 | | - |
156 | | - } |
157 | | - |
158 | | - public function extractVariablesFromSection( &$text, $subst = false ) { |
159 | | - $regex = '~<tvar(?:\|(?P<id>[^>]+))>(?P<value>.*?)</>~u'; |
160 | | - $matches = array(); |
161 | | - // Quick return |
162 | | - if ( !preg_match_all( $regex, $text, $matches, PREG_SET_ORDER ) ) return array(); |
163 | | - |
164 | | - // Extracting |
165 | | - $vars = array(); |
166 | | - foreach ( $matches as $match ) { |
167 | | - $id = $match['id']; // Default to provided id |
168 | | - // But if it isn't provided, autonumber them from one onwards |
169 | | - if ( $id === '' ) $id = count( $vars ) ? max( array_keys( $vars ) ) + 1 : 1; |
170 | | - // Index by id, for above to work. |
171 | | - // Store array or replace, replacement for easy replace afterwards |
172 | | - $vars[$id] = array( '$' . $id, $match['value'] ); |
173 | | - // If requested, subst them immediately |
174 | | - if ( $subst === 'replace' ) |
175 | | - $text = str_replace( $match[0], $match['value'], $text ); |
176 | | - elseif ( $subst === 'holder' ) |
177 | | - $text = str_replace( $match[0], '$' . $id, $text ); |
178 | | - } |
179 | | - |
180 | | - return $vars; |
181 | | - } |
182 | | - |
183 | | - /** |
184 | | - * Fetches translation for a section. |
185 | | - */ |
186 | | - public function getContents( Title $title, $key, $code ) { |
187 | | - global $wgContLang; |
188 | | - |
189 | | - $namespace = $this->getNamespace( $title ); |
190 | | - $sectionPageName = $this->getTranslationPage( $title, $key, $code ); |
191 | | - $sectionTitle = Title::makeTitle( $namespace, $sectionPageName ); |
192 | | - if ( !$sectionTitle ) throw new MWException( 'bad title' ); |
193 | | - |
194 | | - $revision = Revision::loadFromTitle( wfGetDB( DB_SLAVE ), $sectionTitle ); |
195 | | - if ( $revision ) { |
196 | | - $translation = $revision->getText(); |
197 | | - if ( strpos( $translation, TRANSLATE_FUZZY ) !== false ) { |
198 | | - $translation = str_replace( TRANSLATE_FUZZY, '', $translation ); |
199 | | - $translation = $this->wrapAndClean( 'mw-translate-fuzzy', $translation ); |
200 | | - } |
201 | | - |
202 | | - return $translation; |
203 | | - } |
204 | | - |
205 | | - return null; |
206 | | - } |
207 | | - |
208 | | - public function wrapAndClean( $class, $text ) { |
209 | | - $text = trim( $text ); |
210 | | - |
211 | | - $tag = 'div'; |
212 | | - $sep = "\n"; |
213 | | - if ( strpos( $text, "\n" ) === false && strpos( $text, '==' ) === false ) { |
214 | | - $tag = 'span'; |
215 | | - $sep = ''; |
216 | | - } |
217 | | - |
218 | | - $class = htmlspecialchars( $class ); |
219 | | - |
220 | | - return "<$tag class=\"$class\">$sep$text$sep</$tag>"; |
221 | | - } |
222 | | - |
223 | | - const PATTERN_SECTION = '~(<!--T:[^-]+-->)(.*?)<!--T;-->~us'; |
224 | | - const PATTERN_PLACEHOLDER = '~<!--T:[^-/]+/?-->~us'; |
225 | | - |
226 | | - public function getSectionRegex( $taggedOnly = true ) { |
227 | | - $id = '(:? *(?P<holder><!--T:(?P<id>[^-/]+)-->)\n?)'; |
228 | | - $end = '(?P<trail>\n{2,}|\s*\z)'; |
229 | | - $text = '(?Us:[^\n].*)'; |
230 | | - $header = '(?m:(?P<header>(?>={1,6}).+={1,6})[ |\n]?)'; |
231 | | - |
232 | | - if ( $taggedOnly ) { |
233 | | - $regex = "(?P<section>$header?$id$text?)$end"; |
234 | | - } else { |
235 | | - $regex = "(?P<section>$header?$id?$text?)$end"; |
236 | | - // $regex = $text; |
237 | | - } |
238 | | - |
239 | | - return "~$regex~u"; |
240 | | - } |
241 | | - |
242 | | - // Deprecated |
243 | | - public function reset() { |
244 | | - $this->sections = array(); |
245 | | - $this->placeholders = array(); |
246 | | - $this->invocation = 0; |
247 | | - } |
248 | | - |
249 | | - public static function parseSectionDefinitions( Title $title, array &$namespaces ) { |
250 | | - $obj = self::getInstance(); |
251 | | - |
252 | | - $defs = array(); |
253 | | - |
254 | | - $revision = Revision::newFromTitle( $title ); |
255 | | - $pagecontents = $revision->getText(); |
256 | | - |
257 | | - $cb = array( $obj, 'parseMetadata' ); |
258 | | - preg_replace_callback( self::METADATA, $cb, $pagecontents ); |
259 | | - |
260 | | - $matches = array(); |
261 | | - preg_match_all( $obj->getSectionRegex(), $pagecontents, $matches, PREG_SET_ORDER ); |
262 | | - foreach ( $matches as $match ) { |
263 | | - $key = $match['id']; |
264 | | - |
265 | | - $contents = preg_replace( self::PATTERN_COMMENT, "\n", $match['section'] ); |
266 | | - $contents = trim( $contents ); |
267 | | - $key = $obj->getTranslationPage( $title, $key ); |
268 | | - $obj->extractVariablesFromSection( $contents, 'holder' ); |
269 | | - $defs[$key] = $contents; |
270 | | - } |
271 | | - |
272 | | - $ns = $obj->getNamespace( $title ); |
273 | | - $namespaces = array( $ns, $ns + 1 ); |
274 | | - |
275 | | - return $defs; |
276 | | - |
277 | | - } |
278 | | - |
279 | | - /** Use this to get the location of translations */ |
280 | | - public function getTranslationPage( Title $title, $key, $code = false ) { |
281 | | - global $wgTranslateTagTranslationLocation; |
282 | | - list( , $format ) = $wgTranslateTagTranslationLocation; |
283 | | - |
284 | | - // Some data |
285 | | - $ns = $title->getNsText(); |
286 | | - $page = $title->getDBkey(); |
287 | | - $fullname = $title->getPrefixedDBkey(); |
288 | | - $snippet = $this->sections[$key]['page']; |
289 | | - |
290 | | - $search = array( '$NS', '$PAGE', '$FULLNAME', '$KEY', '$SNIPPET' ); |
291 | | - $replace = array( $ns, $page, $fullname, $key, $snippet ); |
292 | | - $pagename = str_replace( $search, $replace, $format ); |
293 | | - if ( $code !== false ) $pagename .= "/$code"; |
294 | | - |
295 | | - return $pagename; |
296 | | - } |
297 | | - |
298 | | - /** Use this to get the namespace for page names provided with |
299 | | - * getTranslationPage |
300 | | - */ |
301 | | - public function getNamespace( Title $title ) { |
302 | | - global $wgTranslateTagTranslationLocation; |
303 | | - list( $nsId, ) = $wgTranslateTagTranslationLocation; |
304 | | - if ( $nsId === null ) $nsId = $title->getNamespace(); |
305 | | - return $nsId; |
306 | | - } |
307 | | - |
308 | | - // Initiate fuzzyjobs on changed sections |
309 | | - public function onArticleSaveComplete( |
310 | | - $article, $user, $text, $summary, $isminor, $_, $_, $flags, $revision |
311 | | - ) { |
312 | | - if ( $revision === null || $isminor ) return true; |
313 | | - |
314 | | - $namespace = $this->getNamespace( $article->getTitle() ); |
315 | | - |
316 | | - foreach ( $this->changed as $key ) { |
317 | | - $page = $this->getTranslationPage( $article->getTitle(), $key ); |
318 | | - $title = Title::makeTitle( $namespace, $page ); |
319 | | - if ( !$title ) continue; |
320 | | - |
321 | | - $summary = str_replace( '-->', '-- >', $summary ); |
322 | | - |
323 | | - $url = $article->getTitle()->getFullUrl( 'diff=' . $revision->getId() ); |
324 | | - $reason = wfMsgForContent( 'translate-tag-fuzzy-reason', $user->getName(), $url, $summary ); |
325 | | - $reason = "<!-- $reason -->"; |
326 | | - $comment = wfMsgForContent( 'translate-tag-fuzzy-comment', $user->getName(), $revision->getId() ); |
327 | | - |
328 | | - FuzzyJob::fuzzyPages( $reason, $comment, $title ); |
329 | | - } |
330 | | - return true; |
331 | | - } |
332 | | - |
333 | | - public static function save( $article, $user, &$text, $summary, $isminor, $iswatch, $section ) { |
334 | | - // Quick escape on normal pages |
335 | | - if ( strpos( $text, '</translate>' ) === false ) return true; |
336 | | - |
337 | | - $obj = self::getInstance(); |
338 | | - $obj->reset(); |
339 | | - |
340 | | - // Parse existing section mappings |
341 | | - $obj->invocation = 0; |
342 | | - $cb = array( $obj, 'parseMetadata' ); |
343 | | - $text = preg_replace_callback( self::METADATA, $cb, $text ); |
344 | | - |
345 | | - $obj->changed = array(); |
346 | | - $obj->invocation = 0; |
347 | | - $cb = array( $obj, 'saveCb' ); |
348 | | - $text = preg_replace_callback( self::PATTERN_TAG, $cb, $text ); |
349 | | - |
350 | | - // Trim trailing whitespace. It is not allowed in wikitext and shows up in |
351 | | - // diffs |
352 | | - $text = rtrim( $text ); |
353 | | - |
354 | | - if ( count( $obj->changed ) ) { |
355 | | - // Register fuzzier |
356 | | - // We need to do it later, so that we know the revision number |
357 | | - global $wgHooks; |
358 | | - $wgHooks['ArticleSaveComplete'][] = $obj; |
359 | | - } |
360 | | - |
361 | | - return true; |
362 | | - } |
363 | | - |
364 | | - public function saveCb( $matches ) { |
365 | | - |
366 | | - $data = $matches[2]; |
367 | | - |
368 | | - // Add sections to unsectioned data |
369 | | - $cb = array( $this, 'saveCbSectionCb' ); |
370 | | - $regex = $this->getSectionRegex( false ); |
371 | | - $data = preg_replace_callback( $regex, $cb, $data ); |
372 | | - |
373 | | - // Add two newlines before metadata so that it wont be parsed as a part of |
374 | | - // the section it is after |
375 | | - $output = $matches[1] . "\n" . $data . "\n\n"; |
376 | | - $output .= $this->outputMetadata(); |
377 | | - $output .= $matches[3]; |
378 | | - |
379 | | - $this->invocation++; |
380 | | - return $output; |
381 | | - } |
382 | | - |
383 | | - public function parseMetadata( $data ) { |
384 | | - $matches = array(); |
385 | | - preg_match_all( '~^(.*?)\|(.*?)\|(.*?)$~umD', $data[1], $matches, PREG_SET_ORDER ); |
386 | | - foreach ( $matches as $match ) { |
387 | | - $this->sections[$match[1]] = array( |
388 | | - 'hash' => $match[2], |
389 | | - 'page' => $match[3], |
390 | | - 'invo' => $this->invocation, |
391 | | - ); |
392 | | - } |
393 | | - |
394 | | - $this->invocation++; |
395 | | - return ''; |
396 | | - } |
397 | | - |
398 | | - /** |
399 | | - * Returns list of translation sections in an array. |
400 | | - * @param $code language code for the pages. |
401 | | - */ |
402 | | - public function getSectionPages() { |
403 | | - $pages = array(); |
404 | | - foreach ( array_keys( $this->sections ) as $key ) { |
405 | | - $pages[] = $this->getTranslationPage( $this->title, $key ); |
406 | | - } |
407 | | - return $pages; |
408 | | - } |
409 | | - |
410 | | - public function outputMetadata() { |
411 | | - $s = "<!--TS\n"; |
412 | | - foreach ( $this->sections as $key => $section ) { |
413 | | - if ( $section['invo'] !== $this->invocation ) continue; |
414 | | - $s .= "$key|{$section['hash']}|{$section['page']}\n"; |
415 | | - } |
416 | | - $s .= "-->\n"; |
417 | | - return $s; |
418 | | - } |
419 | | - |
420 | | - public function saveCbSectionCb( array $matches ) { |
421 | | - // Have to do rematch, because this is stupid |
422 | | - preg_match( $this->getSectionRegex( false ), $matches[0], $match ); |
423 | | - $section = $match['section']; |
424 | | - |
425 | | - if ( trim( $match[0] ) === '' ) return $match[0]; |
426 | | - |
427 | | - if ( $match['holder'] !== '' ) { |
428 | | - $key = $match['id']; |
429 | | - $newhash = self::hash( $match['section'] ); |
430 | | - $oldhash = $this->sections[$key]['hash']; |
431 | | - |
432 | | - |
433 | | - if ( $newhash !== $oldhash ) { |
434 | | - $this->changed[] = $key; |
435 | | - } |
436 | | - |
437 | | - $page = @$this->sections[$key]['page']; |
438 | | - // Create page, unless it is already choosen |
439 | | - if ( $page === null ) $page = TranslateUtils::snippet( $section, 30 ); |
440 | | - |
441 | | - $array = array( |
442 | | - 'hash' => $newhash, |
443 | | - 'invo' => $this->invocation, |
444 | | - 'page' => $page, |
445 | | - ); |
446 | | - |
447 | | - // Update data |
448 | | - $this->sections[$key] = $array; |
449 | | - |
450 | | - return $match[0]; |
451 | | - } |
452 | | - |
453 | | - if ( empty( $this->sections ) ) $key = 0; |
454 | | - else $key = max( array_keys( $this->sections ) ); |
455 | | - |
456 | | - $this->sections[++$key] = array( |
457 | | - 'hash' => self::hash( $section ), |
458 | | - 'page' => TranslateUtils::snippet( $section, 30 ), |
459 | | - 'invo' => $this->invocation, |
460 | | - ); |
461 | | - |
462 | | - $holder = "<!--T:$key-->"; |
463 | | - |
464 | | - if ( $match['header'] !== '' ) { |
465 | | - $section = str_replace( $match['header'], $match['header'] . ' ' . $holder, $section ); |
466 | | - } else { |
467 | | - $section = $holder . "\n" . $section; |
468 | | - } |
469 | | - |
470 | | - return $section . $match['trail']; |
471 | | - } |
472 | | - |
473 | | - public static function hash( $contents ) { |
474 | | - return sha1( trim( $contents ) ); |
475 | | - } |
476 | | - |
477 | | -} |
\ No newline at end of file |
Index: trunk/extensions/Translate/tag/SpecialPageTranslation.php |
— | — | @@ -0,0 +1,329 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * A special page for marking revisions of pages for translation. |
| 5 | + * |
| 6 | + * @author Niklas Laxström |
| 7 | + * @copyright Copyright © 2009 Niklas Laxström |
| 8 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
| 9 | + */ |
| 10 | +class SpecialPageTranslation extends SpecialPage { |
| 11 | + function __construct() { |
| 12 | + SpecialPage::SpecialPage( 'PageTranslation' ); |
| 13 | + } |
| 14 | + |
| 15 | + /** |
| 16 | + * Access point for this special page. |
| 17 | + * GLOBALS: $wgHooks, $wgOut. |
| 18 | + */ |
| 19 | + public function execute( $parameters ) { |
| 20 | + wfLoadExtensionMessages( 'PageTranslation' ); |
| 21 | + $this->setHeaders(); |
| 22 | + |
| 23 | + global $wgRequest, $wgOut, $wgUser; |
| 24 | + $this->user = $wgUser; |
| 25 | + |
| 26 | + $target = $wgRequest->getText( 'target', $parameters ); |
| 27 | + $revision = $wgRequest->getText( 'revision', 0 ); |
| 28 | + |
| 29 | + // No specific page or invalid input |
| 30 | + $title = Title::newFromText( $target ); |
| 31 | + if ( !$title ) { |
| 32 | + if ( $target !== '' ) { |
| 33 | + $wgOut->addWikiMsg( 'tpt-badtitle' ); |
| 34 | + } else { |
| 35 | + $this->listPages(); |
| 36 | + } |
| 37 | + return; |
| 38 | + } |
| 39 | + |
| 40 | + // Check permissions |
| 41 | + if ( !$this->user->isAllowed( 'pagetranslation' ) ) { |
| 42 | + $wgOut->permissionRequired( 'pagetranslation' ); |
| 43 | + return; |
| 44 | + } |
| 45 | + |
| 46 | + // We are processing some specific page |
| 47 | + if ( $revision === '0' ) { |
| 48 | + $revision = $title->getLatestRevID(); |
| 49 | + } elseif ( $revision !== $title->getLatestRevID() ) { |
| 50 | + $wgOut->addWikiMsg( 'tpt-oldrevision', $title->getPrefixedText(), $revision ); |
| 51 | + return; |
| 52 | + } |
| 53 | + |
| 54 | + $page = TranslatablePage::newFromRevision( $title, $revision ); |
| 55 | + |
| 56 | + if ( !$page instanceof TranslatablePage ) { |
| 57 | + $wgOut->addWikiMsg( 'tpt-notsuitable', $title->getPrefixedText(), $revision ); |
| 58 | + $this->listPages(); |
| 59 | + return; |
| 60 | + } |
| 61 | + |
| 62 | + $lastrev = $page->getMarkedTag(); |
| 63 | + if ( $lastrev !== false && $lastrev === $revision ) { |
| 64 | + $wgOut->addWikiMsg( 'tpt-already-marked' ); |
| 65 | + $this->listPages(); |
| 66 | + return; |
| 67 | + } |
| 68 | + |
| 69 | + // This will modify the sections to include name property |
| 70 | + $error = false; |
| 71 | + $sections = $this->checkInput( $page, &$error ); |
| 72 | + // Non-fatal error which prevents saving |
| 73 | + if ( $error === false && $wgRequest->wasPosted() ) { |
| 74 | + $err = $this->markForTranslation( $page, $sections ); |
| 75 | + if ( $err ) { |
| 76 | + call_user_func_array( array($wgOut, 'addWikiMsg' ), $err ); |
| 77 | + } else { |
| 78 | + $this->showSuccess( $page ); |
| 79 | + $this->listPages(); |
| 80 | + } |
| 81 | + return; |
| 82 | + } |
| 83 | + $this->showPage( $page, $sections ); |
| 84 | + } |
| 85 | + |
| 86 | + public function showSuccess( TranslatablePage $page ) { |
| 87 | + global $wgOut, $wgLang; |
| 88 | + |
| 89 | + $titleText = $page->getTitle()->getPrefixedText(); |
| 90 | + $num = $wgLang->formatNum( $page->getParse()->countSections() ); |
| 91 | + $link = SpecialPage::getTitleFor( 'Translate' )->getFullUrl( |
| 92 | + array( 'group' => 'page|' . $page->getTitle()->getPrefixedText() ) ); |
| 93 | + $wgOut->addWikiMsg( 'tpt-saveok', $titleText, $num, $link ); |
| 94 | + } |
| 95 | + |
| 96 | + public function loadPagesFromDB() { |
| 97 | + $dbr = wfGetDB( DB_SLAVE ); |
| 98 | + $tables = array( 'page', 'revtag_type', 'revtag' ); |
| 99 | + $vars = array( 'page_id', 'page_title', 'page_namespace', 'page_latest', 'rt_revision', 'rtt_name' ); |
| 100 | + $conds = array( |
| 101 | + 'page_id=rt_page', |
| 102 | + 'rt_type=rtt_id', |
| 103 | + 'rtt_name' => array( 'tp:mark', 'tp:tag' ), |
| 104 | + ); |
| 105 | + $res = $dbr->select( $tables, $vars, $conds, __METHOD__ ); |
| 106 | + return $res; |
| 107 | + } |
| 108 | + |
| 109 | + public function listPages() { |
| 110 | + global $wgOut; |
| 111 | + |
| 112 | + $res = $this->loadPagesFromDB(); |
| 113 | + if ( !$res->numRows() ) { |
| 114 | + $wgOut->addWikiMsg( 'tpt-list-nopages' ); |
| 115 | + return; |
| 116 | + } |
| 117 | + |
| 118 | + $old = $new = array(); |
| 119 | + foreach ( $res as $r ) { |
| 120 | + if ( $r->rtt_name === 'tp:mark' ) { |
| 121 | + $old[$r->page_id] = array( |
| 122 | + $r->rt_revision, |
| 123 | + Title::newFromRow( $r ) |
| 124 | + ); |
| 125 | + } elseif ( $r->rtt_name === 'tp:tag' ) { |
| 126 | + $new[$r->page_id] = array( |
| 127 | + $r->rt_revision, |
| 128 | + Title::newFromRow( $r ) |
| 129 | + ); |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + // Pages may have both tags, ignore the transtag if both |
| 134 | + foreach( array_keys($old) as $k ) unset($new[$k]); |
| 135 | + |
| 136 | + if ( count($old) ) { |
| 137 | + $wgOut->addWikiMsg( 'tpt-old-pages' ); |
| 138 | + $wgOut->addHTML( '<ol>' ); |
| 139 | + foreach ( $old as $o ) { |
| 140 | + list( $rev, $title ) = $o; |
| 141 | + $link = $this->user->getSkin()->link( $title ); |
| 142 | + $acts = $this->actionLinks( $title, $rev, 'old' ); |
| 143 | + $wgOut->addHTML( "<li>$link ($acts) </li>" ); |
| 144 | + } |
| 145 | + $wgOut->addHTML( '</ol>' ); |
| 146 | + } |
| 147 | + |
| 148 | + if ( count($new) ) { |
| 149 | + $wgOut->addWikiMsg( 'tpt-new-pages' ); |
| 150 | + $wgOut->addHTML( '<ol>' ); |
| 151 | + foreach ( $new as $n ) { |
| 152 | + list( $rev, $title ) = $n; |
| 153 | + $link = $this->user->getSkin()->link( $title ); |
| 154 | + $acts = $this->actionLinks( $title, $rev, 'new' ); |
| 155 | + $wgOut->addHTML( "<li>$link ($acts) </li>" ); |
| 156 | + } |
| 157 | + $wgOut->addHTML( '</ol>' ); |
| 158 | + } |
| 159 | + |
| 160 | + } |
| 161 | + |
| 162 | + protected function actionLinks( $title, $rev, $old = 'old' ) { |
| 163 | + $actions = array(); |
| 164 | + $latest = $title->getLatestRevId(); |
| 165 | + |
| 166 | + if ( $latest !== $rev ) { |
| 167 | + $text = wfMsg( 'tpt-rev-old', $rev ); |
| 168 | + $actions[] = $this->user->getSkin()->link( |
| 169 | + $title, |
| 170 | + htmlspecialchars( $text ), |
| 171 | + array(), |
| 172 | + array( 'oldid' => $rev, 'diff' => $title->getLatestRevId() ) |
| 173 | + ); |
| 174 | + } else { |
| 175 | + $actions[] = wfMsgHtml( 'tpt-rev-latest' ); |
| 176 | + } |
| 177 | + |
| 178 | + if ( $this->user->isAllowed( 'pagetranslation') && |
| 179 | + (($old === 'new' && $latest === $rev) || |
| 180 | + ($old === 'old' && $latest !== $rev)) ) { |
| 181 | + $actions[] = $this->user->getSkin()->link( |
| 182 | + $this->getTitle(), |
| 183 | + wfMsgHtml( 'tpt-rev-mark-new' ), |
| 184 | + array(), |
| 185 | + array( |
| 186 | + 'target' => $title->getPrefixedText(), |
| 187 | + 'revision' => $title->getLatestRevId() |
| 188 | + ) |
| 189 | + ); |
| 190 | + } |
| 191 | + |
| 192 | + if ( $old === 'old' && $this->user->isAllowed( 'translate' ) ) { |
| 193 | + $actions[] = $this->user->getSkin()->link( |
| 194 | + SpecialPage::getTitleFor( 'Translate' ), |
| 195 | + wfMsgHtml( 'tpt-translate-this' ), |
| 196 | + array(), |
| 197 | + array( 'group' => 'page|' . $title->getPrefixedText() ) |
| 198 | + ); |
| 199 | + } |
| 200 | + |
| 201 | + global $wgLang; |
| 202 | + return $wgLang->semicolonList( $actions ); |
| 203 | + } |
| 204 | + |
| 205 | + public function checkInput( TranslatablePage $page, &$error = false ) { |
| 206 | + global $wgOut, $wgRequest; |
| 207 | + |
| 208 | + $parse = $page->getParse(); |
| 209 | + $sections = $parse->getSectionsForSave(); |
| 210 | + foreach ( $sections as $s ) { |
| 211 | + // We want to preserve $id, because it is the only thing we can use |
| 212 | + // to link the new names to current sections. Name will become |
| 213 | + // the new id only after it is saved into db and the page. |
| 214 | + // Do not allow changing names for old sections |
| 215 | + $s->name = $s->id; |
| 216 | + if ( $s->type !== 'new' ) continue; |
| 217 | + |
| 218 | + $name = $wgRequest->getText( 'tpt-sect-' . $s->id, $s->id ); |
| 219 | + |
| 220 | + $sectionTitle = Title::makeTitleSafe( |
| 221 | + NS_TRANSLATIONS, |
| 222 | + $page->getTitle()->getPrefixedText() . '/' . $name . '/qqq' |
| 223 | + ); |
| 224 | + if ( trim($name) === '' || !$sectionTitle ) { |
| 225 | + $wgOut->addWikiMsg( 'tpt-badsect', $name, $s->id ); |
| 226 | + $error = true; |
| 227 | + } else { |
| 228 | + // Update the name |
| 229 | + $s->name = $name; |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + return $sections; |
| 234 | + } |
| 235 | + |
| 236 | + public function showPage( TranslatablePage $page, $sections ) { |
| 237 | + global $wgOut, $wgScript; |
| 238 | + |
| 239 | + |
| 240 | + $wgOut->addWikiMsg( 'tpt-showpage-intro' ); |
| 241 | + |
| 242 | + $wgOut->addHTML( |
| 243 | + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getFullURL() ) ) . |
| 244 | + Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) . |
| 245 | + Xml::hidden( 'revision', $page->getRevision() ) . |
| 246 | + Xml::hidden( 'target', $page->getTitle()->getPrefixedtext() ) |
| 247 | + ); |
| 248 | + |
| 249 | + foreach ( $sections as $s ) { |
| 250 | + if ( $s->type === 'new' ) { |
| 251 | + $name = wfMsgHtml('tpt-section-new') . ' ' . Xml::input( 'tpt-sect-' . $s->id, 10, $s->name ); |
| 252 | + } else { |
| 253 | + $name = wfMsgHtml('tpt-section') . ' ' . htmlspecialchars( $s->name ); |
| 254 | + } |
| 255 | + |
| 256 | + if ( $s->type === 'changed' ) { |
| 257 | + $diff = new DifferenceEngine; |
| 258 | + $diff->setText( $s->getOldText(), $s->getText() ); |
| 259 | + $text = $diff->getDiff( wfMsgHtml('tpt-diff-old'), wfMsgHtml('tpt-diff-new') ); |
| 260 | + $diff->showDiffStyle(); |
| 261 | + } else { |
| 262 | + $text = TranslateUtils::convertWhiteSpaceToHTML( $s->getText() ); |
| 263 | + } |
| 264 | + |
| 265 | + $wgOut->addHTML( |
| 266 | + Xml::openElement( 'fieldset' ) . |
| 267 | + Xml::tags( 'legend', null, $name ) . |
| 268 | + $text . |
| 269 | + Xml::closeElement( 'fieldset' ) |
| 270 | + ); |
| 271 | + } |
| 272 | + |
| 273 | + $deletedSections = $page->getParse()->getDeletedSections(); |
| 274 | + if ( count($deletedSections) ) { |
| 275 | + $wgOut->addWikiMsg( 'tpt-deletedsections' ); |
| 276 | + foreach ( $deletedSections as $s ) { |
| 277 | + $name = htmlspecialchars( $s->id ); |
| 278 | + $wgOut->addHTML( |
| 279 | + Xml::openElement( 'fieldset' ) . |
| 280 | + Xml::tags( 'legend', null, wfMsgHtml('tpt-section') . ' ' . $name ) . |
| 281 | + TranslateUtils::convertWhiteSpaceToHTML( $s->getText() ) . |
| 282 | + Xml::closeElement( 'fieldset' ) |
| 283 | + ); |
| 284 | + } |
| 285 | + } |
| 286 | + $wgOut->addHTML( |
| 287 | + Xml::submitButton( wfMsg( 'tpt-submit' ) ) . |
| 288 | + Xml::closeElement( 'form' ) |
| 289 | + ); |
| 290 | + } |
| 291 | + |
| 292 | + public function markForTranslation( TranslatablePage $page, $sections ) { |
| 293 | + $text = $page->getParse()->getSourcePageText(); |
| 294 | + |
| 295 | + $article = new Article( $page->getTitle() ); |
| 296 | + $status = $article->doEdit( |
| 297 | + $text, |
| 298 | + wfMsgForContent( 'tpt-mark-summary' ), |
| 299 | + EDIT_FORCE_BOT | EDIT_UPDATE, |
| 300 | + $page->getRevision() |
| 301 | + ); |
| 302 | + |
| 303 | + if ( !$status->isOK() ) return array( 'tpt-edit-failed', $status->getWikiText() ); |
| 304 | + |
| 305 | + $newrevision = $status->value['revision']; |
| 306 | + if ( $newrevision === null ) { |
| 307 | + // Probably a no-change edit, so no new revision was assigned |
| 308 | + $newrevision = $page->getTitle()->getLatestRevId(); |
| 309 | + } |
| 310 | + |
| 311 | + $inserts = array(); |
| 312 | + foreach ( $sections as $s ) { |
| 313 | + $inserts[] = array( |
| 314 | + 'trs_page' => $page->getTitle()->getArticleId(), |
| 315 | + 'trs_key' => $s->name, |
| 316 | + 'trs_text' => $s->getText(), |
| 317 | + ); |
| 318 | + } |
| 319 | + |
| 320 | + $dbw = wfGetDB( DB_MASTER ); |
| 321 | + $dbw->delete( 'translate_sections', array( 'trs_page' => $page->getTitle()->getArticleId() ), __METHOD__ ); |
| 322 | + $ok = $dbw->insert( 'translate_sections', $inserts, __METHOD__ ); |
| 323 | + if ( $ok === false ) return array( 'tpt-insert-failed' ); |
| 324 | + |
| 325 | + $page->addMarkedTag( $newrevision ); |
| 326 | + |
| 327 | + return false; |
| 328 | + } |
| 329 | + |
| 330 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/tag/SpecialPageTranslation.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 331 | + native |
Index: trunk/extensions/Translate/tag/TPParse.php |
— | — | @@ -0,0 +1,127 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * This class represents the results of parsed source page, that is, the |
| 5 | + * extracted sections and a template. |
| 6 | + * |
| 7 | + * @author Niklas Laxström |
| 8 | + * @copyright Copyright © 2009 Niklas Laxström |
| 9 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
| 10 | + */ |
| 11 | +class TPParse { |
| 12 | + protected $title = null; |
| 13 | + |
| 14 | + public $sections = array(); |
| 15 | + public $template = null; |
| 16 | + public $dbSections = null; |
| 17 | + |
| 18 | + public function __construct( Title $title ) { |
| 19 | + $this->title = $title; |
| 20 | + } |
| 21 | + |
| 22 | + public function countSections() { |
| 23 | + return count($this->sections); |
| 24 | + } |
| 25 | + |
| 26 | + public function getTemplate() { |
| 27 | + return $this->template; |
| 28 | + } |
| 29 | + |
| 30 | + public function getSectionsForSave() { |
| 31 | + $this->loadFromDatabase(); |
| 32 | + |
| 33 | + $sections = $this->sections; |
| 34 | + $highest = 0; |
| 35 | + if ( count($this->dbSections) ) { |
| 36 | + $highest = call_user_func_array( 'max', array_keys( $this->dbSections ) ); |
| 37 | + } |
| 38 | + |
| 39 | + foreach ( $sections as $_ ) $highest = max( $_->id, $highest ); |
| 40 | + foreach ( $sections as $s ) { |
| 41 | + $s->type = 'old'; |
| 42 | + |
| 43 | + if ( $s->id === -1 ) { |
| 44 | + $s->type = 'new'; |
| 45 | + $s->id = ++$highest; |
| 46 | + } else { |
| 47 | + if ( isset($this->dbSections[$s->id]) ) { |
| 48 | + $storedText = $this->dbSections[$s->id]->text; |
| 49 | + if ( $s->text !== $storedText ) { |
| 50 | + $s->type = 'changed'; |
| 51 | + $s->oldtext = $storedText; |
| 52 | + } |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + } |
| 57 | + return $sections; |
| 58 | + } |
| 59 | + |
| 60 | + public function getDeletedSections() { |
| 61 | + $sections = $this->getSectionsForSave(); |
| 62 | + |
| 63 | + $deleted = $this->dbSections; |
| 64 | + foreach ( $sections as $s ) { |
| 65 | + if ( isset($deleted[$s->id]) ) |
| 66 | + unset($deleted[$s->id]); |
| 67 | + } |
| 68 | + return $deleted; |
| 69 | + } |
| 70 | + |
| 71 | + protected function loadFromDatabase() { |
| 72 | + if ( $this->dbSections !== null ) return; |
| 73 | + |
| 74 | + $this->dbSections = array(); |
| 75 | + |
| 76 | + $db = wfGetDB( DB_SLAVE ); |
| 77 | + $tables = 'translate_sections'; |
| 78 | + $vars = array( 'trs_key', 'trs_text' ); |
| 79 | + $conds = array( 'trs_page' => $this->title->getArticleID() ); |
| 80 | + |
| 81 | + $res = $db->select( $tables, $vars, $conds, __METHOD__ ); |
| 82 | + foreach ( $res as $r ) { |
| 83 | + $section = new TPsection; |
| 84 | + $section->id = $r->trs_key; |
| 85 | + $section->text = $r->trs_text; |
| 86 | + $section->type = 'db'; |
| 87 | + $this->dbSections[$r->trs_key] = $section; |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + public function getSourcePageText() { |
| 92 | + $text = $this->template; |
| 93 | + foreach ( $this->sections as $ph => $s ) { |
| 94 | + $text = str_replace( $ph, $s->getMarkedText(), $text ); |
| 95 | + } |
| 96 | + return $text; |
| 97 | + } |
| 98 | + |
| 99 | + public function getTranslationPageText( MessageCollection $collection ) { |
| 100 | + $text = $this->template; // The source |
| 101 | + |
| 102 | + // For finding the messages |
| 103 | + $prefix = $prefix = $this->title->getPrefixedText() . '/'; |
| 104 | + |
| 105 | + foreach ( $this->sections as $ph => $s ) { |
| 106 | + if ( isset($collection[$prefix.$s->id]) ) { |
| 107 | + $msg = $collection[$prefix.$s->id]; |
| 108 | + if ( $msg->translation() === null ) { |
| 109 | + // Just use |
| 110 | + $text = str_replace( $ph, $s->getTextForTrans(), $text ); |
| 111 | + } else { |
| 112 | + $sectiontext = $msg->translation(); |
| 113 | + $vars = $s->getVariables(); |
| 114 | + foreach ( $vars as $key => $value ) { |
| 115 | + $sectiontext = str_replace( $key, $value, $sectiontext ); |
| 116 | + } |
| 117 | + if ( $msg->fuzzy() ) { |
| 118 | + $sectiontext = "<div class=\"mw-translate-fuzzy\">$\n$sectiontext\n</div>"; |
| 119 | + } |
| 120 | + $text = str_replace( $ph, $sectiontext, $text ); |
| 121 | + } |
| 122 | + } else { |
| 123 | + $text = str_replace( $ph, $s->getTextForTrans(), $text ); |
| 124 | + } |
| 125 | + } |
| 126 | + return $text; |
| 127 | + } |
| 128 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/tag/TPParse.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 129 | + native |
Index: trunk/extensions/Translate/tag/PageTranslationHooks.php |
— | — | @@ -0,0 +1,343 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class PageTranslationHooks { |
| 5 | + |
| 6 | + // Uuugly hack |
| 7 | + static $allowTargetEdit = false; |
| 8 | + |
| 9 | + public static function renderTagPage( $parser, &$text, $state ) { |
| 10 | + $title = $parser->getTitle(); |
| 11 | + if ( strpos( $text, '</translate>' ) !== false ) { |
| 12 | + $cb = array( __CLASS__, 'replaceTagCb' ); |
| 13 | + # Remove the tags nicely, trying to not leave excess whitespace lying around |
| 14 | + $text = preg_replace_callback( '~(\n?<translate>\s*?)(.*?)(\s*?</translate>)~s', $cb, $text ); |
| 15 | + # Replace variable markers |
| 16 | + $text = preg_replace_callback( '~(<tvar[^<>]+>)(.*)(</>)~s', $cb, $text ); |
| 17 | + } |
| 18 | + return true; |
| 19 | + } |
| 20 | + |
| 21 | + public static function replaceTagCb( $matches ) { |
| 22 | + return $matches[2]; |
| 23 | + } |
| 24 | + |
| 25 | + public static function injectCss( $outputpage, $text ) { |
| 26 | + TranslateUtils::injectCSS(); |
| 27 | + return true; |
| 28 | + } |
| 29 | + |
| 30 | + public static function onSectionSave( $article, $user, $text, $summary, $minor, |
| 31 | + $_, $_, $flags, $revision ) { |
| 32 | + global $wgTranslateFuzzyBotName; |
| 33 | + $title = $article->getTitle(); |
| 34 | + |
| 35 | + // Some checks |
| 36 | + |
| 37 | + // We are only interested in the translations namespace |
| 38 | + if ( $title->getNamespace() != NS_TRANSLATIONS ) return true; |
| 39 | + // Do not trigger renders for fuzzybot or fuzzy |
| 40 | + if ( strpos( $text, TRANSLATE_FUZZY ) !== false ) return true; |
| 41 | + // For null revisions, don't do anything |
| 42 | + if ( $revision === null ) return true; |
| 43 | + |
| 44 | + // Figure out the group |
| 45 | + $groupKey = MessageIndex::titleToGroup( $title ); |
| 46 | + $group = MessageGroups::getGroup( $groupKey ); |
| 47 | + if ( !$group instanceof WikiPageMessageGroup ) return; |
| 48 | + |
| 49 | + // Finally we know the title and can construct a Translatable page |
| 50 | + $page = TranslatablePage::newFromTitle( $group->title ); |
| 51 | + |
| 52 | + // Add a tracking mark |
| 53 | + self::addSectionTag( $title, $revision->getId(), $page->getMarkedTag() ); |
| 54 | + |
| 55 | + // Update the target translation page |
| 56 | + list(, $code ) = TranslateUtils::figureMessage( $title->getDBkey() ); |
| 57 | + self::updateTranslationPage( $page, $group, $code, $user, $flags, $summary ); |
| 58 | + |
| 59 | + return true; |
| 60 | + } |
| 61 | + |
| 62 | + protected static function addSectionTag( Title $title, $revision, $pageRevision ) { |
| 63 | + if ( $pageRevision === null ) throw new MWException( 'Page revision is null' ); |
| 64 | + if ( $revision === null ) throw new MWException( 'Revision is null' ); |
| 65 | + |
| 66 | + $dbw = wfGetDB( DB_MASTER ); |
| 67 | + |
| 68 | + // Can this be done in one query? |
| 69 | + $id = $dbw->selectField( 'revtag_type', 'rtt_id', |
| 70 | + array( 'rtt_name' => 'tp:transver' ), __METHOD__ ); |
| 71 | + |
| 72 | + $conds = array( |
| 73 | + 'rt_page' => $title->getArticleId(), |
| 74 | + 'rt_type' => $id, |
| 75 | + 'rt_revision' => $revision |
| 76 | + ); |
| 77 | + $dbw->delete( 'revtag', $conds, __METHOD__ ); |
| 78 | + |
| 79 | + $conds['rt_value'] = $pageRevision; |
| 80 | + |
| 81 | + $dbw->insert( 'revtag', $conds, __METHOD__ ); |
| 82 | + } |
| 83 | + |
| 84 | + public static function updateTranslationPage( TranslatablePage $page, |
| 85 | + MessageGroup $group, $code, $user, $flags, $summary ) { |
| 86 | + |
| 87 | + $source = $page->getTitle(); |
| 88 | + $target = Title::makeTitle( $source->getNamespace(), $source->getDBkey() . "/$code" ); |
| 89 | + |
| 90 | + $collection = $group->initCollection( $code ); |
| 91 | + $group->fillCollection( $collection ); |
| 92 | + |
| 93 | + $text = $page->getParse()->getTranslationPageText( $collection ); |
| 94 | + |
| 95 | + // Same as in renderSourcePage |
| 96 | + $cb = array( __CLASS__, 'replaceTagCb' ); |
| 97 | + $text = preg_replace_callback( '~(\n?<translate>\s*?)(.*?)(\s*?</translate>)~s', $cb, $text ); |
| 98 | + |
| 99 | + #$flags |= EDIT_SUPPRESS_RC; // We can filter using CleanChanges |
| 100 | + $flags &= ~EDIT_NEW & ~EDIT_UPDATE; // We don't know |
| 101 | + |
| 102 | + $article = new Article( $target ); |
| 103 | + |
| 104 | + self::$allowTargetEdit = true; |
| 105 | + $article->doEdit( $text, $summary, $flags ); |
| 106 | + self::$allowTargetEdit = false; |
| 107 | + } |
| 108 | + |
| 109 | + public static function addSidebar( $out, $tpl ) { |
| 110 | + // TODO: fixme |
| 111 | + return true; |
| 112 | + global $wgLang; |
| 113 | + |
| 114 | + // Sort by translation percentage |
| 115 | + arsort( $status, SORT_NUMERIC ); |
| 116 | + |
| 117 | + foreach ( $status as $code => $percent ) { |
| 118 | + $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() ); |
| 119 | + $percent = $wgLang->formatNum( round( 100 * $percent ) ); |
| 120 | + $label = "$name ($percent%)"; |
| 121 | + |
| 122 | + $_title = TranslateTagUtils::codefyTitle( $title, $code ); |
| 123 | + |
| 124 | + $items[] = array( |
| 125 | + 'text' => $label, |
| 126 | + 'href' => $_title->getFullURL(), |
| 127 | + 'id' => 'foo', |
| 128 | + ); |
| 129 | + } |
| 130 | + |
| 131 | + $sidebar = $out->buildSidebar(); |
| 132 | + $sidebar['TRANSLATIONS'] = $items; |
| 133 | + |
| 134 | + $tpl->set( 'sidebar', $sidebar ); |
| 135 | + |
| 136 | + return true; |
| 137 | + } |
| 138 | + |
| 139 | + public static function languages( $data, $params, $parser ) { |
| 140 | + $title = $parser->getTitle(); |
| 141 | + $page = TranslatablePage::newFromTitle( $title ); |
| 142 | + $status = $page->getTranslationPercentages(); |
| 143 | + if ( !$status ) return ''; |
| 144 | + |
| 145 | + global $wgLang; |
| 146 | + |
| 147 | + // Sort by language code |
| 148 | + ksort( $status ); |
| 149 | + |
| 150 | + // $lobj = $parser->getFunctionLang(); |
| 151 | + $sk = $parser->mOptions->getSkin(); |
| 152 | + $legend = wfMsg( 'otherlanguages' ); |
| 153 | + |
| 154 | + global $wgTranslateCssLocation; |
| 155 | + |
| 156 | + $languages = array(); |
| 157 | + foreach ( $status as $code => $percent ) { |
| 158 | + $name = TranslateUtils::getLanguageName( $code, false, $wgLang->getCode() ); |
| 159 | + |
| 160 | + $percent *= 100; |
| 161 | + if ( $percent < 10 ) continue; |
| 162 | + if ( $percent < 20 ) $image = 1; |
| 163 | + elseif ( $percent < 40 ) $image = 2; |
| 164 | + elseif ( $percent < 60 ) $image = 3; |
| 165 | + elseif ( $percent < 80 ) $image = 4; |
| 166 | + else $image = 5; |
| 167 | + |
| 168 | + $percent = Xml::element( 'img', array( |
| 169 | + 'src' => "$wgTranslateCssLocation/images/prog-$image.png" |
| 170 | + ) ); |
| 171 | + $label = "$name $percent"; |
| 172 | + |
| 173 | + $suffix = ( $code === 'en' ) ? '' : "/$code"; |
| 174 | + $_title = Title::makeTitle( $title->getNamespace(), $title->getDBKey() . $suffix ); |
| 175 | + $languages[] = $sk->link( $_title, $label ); |
| 176 | + } |
| 177 | + |
| 178 | + $languages = implode( ' • ', $languages ); |
| 179 | + |
| 180 | + return <<<FOO |
| 181 | +<div class="mw-pt-languages"> |
| 182 | +<table><tbody> |
| 183 | + |
| 184 | +<tr valign="top"> |
| 185 | +<td class="mw-pt-languages-label"><b>$legend:</b></td> |
| 186 | +<td class="mw-pt-languages-list">$languages</td></tr> |
| 187 | + |
| 188 | +</tbody></table> |
| 189 | +</div> |
| 190 | +FOO; |
| 191 | + } |
| 192 | + |
| 193 | + public static function tpSyntaxCheck( $article, $user, $text, $summary, |
| 194 | + $minor, $_, $_, $flags, $status ) { |
| 195 | + // Quick escape on normal pages |
| 196 | + if ( strpos( $text, '</translate>' ) === false ) return true; |
| 197 | + |
| 198 | + $page = TranslatablePage::newFromText( $article->getTitle(), $text ); |
| 199 | + try { |
| 200 | + $page->getParse(); |
| 201 | + } catch ( TPException $e ) { |
| 202 | + call_user_func_array( array( $status, 'fatal' ), $ret ); |
| 203 | + return false; |
| 204 | + } |
| 205 | + |
| 206 | + return true; |
| 207 | + } |
| 208 | + |
| 209 | + public static function addTranstag( $article, $user, $text, $summary, |
| 210 | + $minor, $_, $_, $flags, $revision ) { |
| 211 | + // We are not interested in null revisions |
| 212 | + if ( $revision === null ) return true; |
| 213 | + |
| 214 | + // Quick escape on normal pages |
| 215 | + if ( strpos( $text, '</translate>' ) === false ) return true; |
| 216 | + |
| 217 | + // Add the ready tag |
| 218 | + $page = TranslatablePage::newFromTitle( $article->getTitle() ); |
| 219 | + $page->addReadyTag( $revision->getId() ); |
| 220 | + |
| 221 | + return true; |
| 222 | + } |
| 223 | + |
| 224 | + // Here we disable editing of some existing or unknown pages |
| 225 | + public static function translationsCheck( $title, $user, $action, &$result ) { |
| 226 | + |
| 227 | + // Case 1: Unknown section translations |
| 228 | + if ( $title->getNamespace() == NS_TRANSLATIONS && $action === 'edit' ) { |
| 229 | + $group = MessageIndex::titleToGroup( $title ); |
| 230 | + if ( $group === null ) { |
| 231 | + // No group means that the page is currently not |
| 232 | + // registered to any page translation message groups |
| 233 | + wfLoadExtensionMessages( 'PageTranslation' ); |
| 234 | + $result = array( 'tpt-unknown-page' ); |
| 235 | + return false; |
| 236 | + } |
| 237 | + |
| 238 | + // Case 2: Target pages |
| 239 | + } elseif( $title->getBaseText() != $title->getText() ) { |
| 240 | + $newtitle = Title::makeTitle( $title->getNamespace(), $title->getBaseText() ); |
| 241 | + |
| 242 | + if ( $newtitle && $newtitle->exists() ) { |
| 243 | + $page = TranslatablePage::newFromTitle( $newtitle ); |
| 244 | + |
| 245 | + if ( $page->getMarkedTag() && !self::$allowTargetEdit) { |
| 246 | + wfLoadExtensionMessages( 'PageTranslation' ); |
| 247 | + $result = array( |
| 248 | + 'tpt-target-page', |
| 249 | + $newtitle->getPrefixedText(), |
| 250 | + $page->getTranslationUrl( $title->getSubpageText() ) |
| 251 | + ); |
| 252 | + return false; |
| 253 | + } |
| 254 | + } |
| 255 | + } |
| 256 | + |
| 257 | + return true; |
| 258 | + } |
| 259 | + |
| 260 | + public static function onLoadExtensionSchemaUpdates() { |
| 261 | + global $wgExtNewTables; |
| 262 | + $dir = dirname( __FILE__ ) . '/..'; |
| 263 | + $wgExtNewTables[] = array( 'translate_sections', "$dir/translate.sql" ); |
| 264 | + $wgExtNewTables[] = array( 'revtag_type', "$dir/revtags.sql" ); |
| 265 | + |
| 266 | + // Add our tags if they are not registered yet |
| 267 | + // tp:tag is called also the ready tag |
| 268 | + $tags = array( 'tp:mark', 'tp:tag', 'tp:transver' ); |
| 269 | + |
| 270 | + $dbw = wfGetDB( DB_MASTER ); |
| 271 | + foreach ( $tags as $tag ) { |
| 272 | + $field = array( 'rtt_name' => $tag ); |
| 273 | + $ret = $dbw->selectField( 'revtag_type', 'rtt_name', $field, __METHOD__ ); |
| 274 | + if ( $ret !== $tag ) $dbw->insert( 'revtag_type', $field, __METHOD__ ); |
| 275 | + } |
| 276 | + return true; |
| 277 | + } |
| 278 | + |
| 279 | + public static function test(&$article, &$outputDone, &$pcache) { |
| 280 | + global $wgOut; |
| 281 | + if ( !$article->getOldID() ) { |
| 282 | + $wgOut->addHTML( self::getHeader( $article->getTitle() ) ); |
| 283 | + } else { |
| 284 | + echo "foo"; |
| 285 | + } |
| 286 | + return true; |
| 287 | + } |
| 288 | + |
| 289 | + public static function getHeader( Title $title, $code = false ) { |
| 290 | + global $wgLang, $wgUser; |
| 291 | + |
| 292 | + $sk = $wgUser->getSkin(); |
| 293 | + |
| 294 | + $page = TranslatablePage::newFromText( $title, '' ); |
| 295 | + |
| 296 | + $marked = $page->getMarkedTag(); |
| 297 | + $ready = $page->getReadyTag(); |
| 298 | + if ( $marked === false && $ready === false ) return ''; |
| 299 | + |
| 300 | + $latest = $title->getLatestRevId(); |
| 301 | + $canmark = $ready === $latest && $marked !== $latest; |
| 302 | + |
| 303 | + wfLoadExtensionMessages( 'PageTranslation' ); |
| 304 | + |
| 305 | + $actions = array(); |
| 306 | + |
| 307 | + if ( $marked && $wgUser->isAllowed('translate') ) { |
| 308 | + $par = array( |
| 309 | + 'group' => 'page|' . $title->getPrefixedText(), |
| 310 | + 'language' => $code ? $code : $wgLang->getCode(), |
| 311 | + 'task' => 'view' |
| 312 | + ); |
| 313 | + $translate = SpecialPage::getTitleFor( 'Translate' ); |
| 314 | + $linkDesc = wfMsgHtml( 'translate-tag-translate-link-desc' ); |
| 315 | + $actions[] = $sk->link( $translate, $linkDesc, array(), $par); |
| 316 | + } |
| 317 | + |
| 318 | + if ( $canmark && $wgUser->isAllowed('pagetranslation') ) { |
| 319 | + $par = array( |
| 320 | + 'target' => $title->getPrefixedText() |
| 321 | + ); |
| 322 | + $translate = SpecialPage::getTitleFor( 'PageTranslation' ); |
| 323 | + $linkDesc = wfMsgHtml( 'translate-tag-markthis' ); |
| 324 | + $actions[] = $sk->link( $translate, $linkDesc, array(), $par); |
| 325 | + } |
| 326 | + |
| 327 | + |
| 328 | + if ( $code ) { |
| 329 | + $legendText = wfMsgHtml( 'translate-tag-legend' ); |
| 330 | + $legendOther = wfMsgHtml( 'translate-tag-legend-fallback' ); |
| 331 | + $legendFuzzy = wfMsgHtml( 'translate-tag-legend-fuzzy' ); |
| 332 | + |
| 333 | + $actions[] = "$legendText <span class=\"mw-translate-other\">$legendOther</span>" . |
| 334 | + " <span class=\"mw-translate-fuzzy\">$legendFuzzy</span>"; |
| 335 | + } |
| 336 | + |
| 337 | + if ( !count($actions) ) return ''; |
| 338 | + $legend = "<div style=\"font-size: x-small; text-align: center\">"; |
| 339 | + $legend .= $wgLang->semicolonList( $actions ); |
| 340 | + $legend .= '</div><hr />'; |
| 341 | + return $legend; |
| 342 | + } |
| 343 | + |
| 344 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/tag/PageTranslationHooks.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 345 | + native |
Index: trunk/extensions/Translate/tag/TPSection.php |
— | — | @@ -0,0 +1,49 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * This class represents one section of a translatable page. |
| 5 | + * |
| 6 | + * @author Niklas Laxström |
| 7 | + * @copyright Copyright © 2009 Niklas Laxström |
| 8 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
| 9 | + */ |
| 10 | +class TPSection { |
| 11 | + public $id, $name, $text, $type; |
| 12 | + |
| 13 | + public function getText() { |
| 14 | + return $this->text; |
| 15 | + } |
| 16 | + |
| 17 | + public function getTextForTrans() { |
| 18 | + $re = '~<tvar\|([^>]+)>(.*?)</>~u'; |
| 19 | + return preg_replace( $re, '\2', $this->text ); |
| 20 | + } |
| 21 | + |
| 22 | + public function getMarkedText() { |
| 23 | + $id = isset($this->name) ? $this->name : $this->id; |
| 24 | + $header = "<!--T:{$id}-->"; |
| 25 | + $re = '~^(=+.*?=+\s*?)\n~'; |
| 26 | + $rep = "\\1 $header\n"; |
| 27 | + $count = 0; |
| 28 | + |
| 29 | + $text = preg_replace( $re, $rep, $this->text, 1, &$count ); |
| 30 | + if ( $count === 0 ) { |
| 31 | + $text = $header . "\n" . $this->text; |
| 32 | + } |
| 33 | + return $text; |
| 34 | + } |
| 35 | + |
| 36 | + public function getOldText() { |
| 37 | + return isset($this->oldtext) ? $this->oldtext : $this->text; |
| 38 | + } |
| 39 | + |
| 40 | + public function getVariables() { |
| 41 | + $re = '~<tvar\|([^>]+)>(.*?)</>~u'; |
| 42 | + $matches = array(); |
| 43 | + preg_match_all( $re, $this->text, $matches, PREG_SET_ORDER ); |
| 44 | + $vars = array(); |
| 45 | + foreach( $matches as $m ) { |
| 46 | + $vars[$m[1]] = $m[2]; |
| 47 | + } |
| 48 | + return $vars; |
| 49 | + } |
| 50 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/tag/TPSection.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 51 | + native |
Index: trunk/extensions/Translate/tag/TranslatablePage.php |
— | — | @@ -0,0 +1,362 @@ |
| 2 | +<?php |
| 3 | +class TranslatablePage { |
| 4 | + |
| 5 | + private $title = null; |
| 6 | + private $text = null; |
| 7 | + private $revision = null; |
| 8 | + private $source = null; |
| 9 | + private $init = false; |
| 10 | + |
| 11 | + private function __construct( Title $title ) { |
| 12 | + $this->title = $title; |
| 13 | + } |
| 14 | + |
| 15 | + // Public constructors // |
| 16 | + |
| 17 | + public static function newFromText( Title $title, $text ) { |
| 18 | + $obj = new self( $title ); |
| 19 | + $obj->text = $text; |
| 20 | + $obj->source = 'text'; |
| 21 | + return $obj; |
| 22 | + } |
| 23 | + |
| 24 | + public static function newFromRevision( Title $title, $revision ) { |
| 25 | + $rev = Revision::newFromTitle( $title, $revision ); |
| 26 | + if ( $rev === null ) throw new MWException( 'Revision is null' ); |
| 27 | + |
| 28 | + $obj = new self( $title ); |
| 29 | + $obj->source = 'revision'; |
| 30 | + $obj->revision = $revision; |
| 31 | + return $obj; |
| 32 | + } |
| 33 | + |
| 34 | + public static function newFromTitle( Title $title ) { |
| 35 | + $obj = new self( $title ); |
| 36 | + $obj->source = 'title'; |
| 37 | + return $obj; |
| 38 | + } |
| 39 | + |
| 40 | + // Getters // |
| 41 | + |
| 42 | + public function getTitle() { |
| 43 | + return $this->title; |
| 44 | + } |
| 45 | + |
| 46 | + public function getText() { |
| 47 | + if ( $this->init === false ) { |
| 48 | + switch ( $this->source ) { |
| 49 | + case 'text': |
| 50 | + break; |
| 51 | + case 'title': |
| 52 | + $revision = $this->getMarkedTag(); |
| 53 | + case 'revision': |
| 54 | + $rev = Revision::newFromTitle( $this->getTitle(), $this->revision ); |
| 55 | + $this->text = $rev->getText(); |
| 56 | + break; |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + if ( !is_string($this->text) ) throw new MWException( 'We have no text' ); |
| 61 | + return $this->text; |
| 62 | + } |
| 63 | + |
| 64 | + /** |
| 65 | + * Revision is null if object was constructed using newFromText. |
| 66 | + */ |
| 67 | + public function getRevision() { |
| 68 | + return $this->revision; |
| 69 | + } |
| 70 | + |
| 71 | + // Public functions // |
| 72 | + |
| 73 | + public function getParse() { |
| 74 | + if ( isset($this->cachedParse) ) return $this->cachedParse; |
| 75 | + |
| 76 | + $text = $this->getText(); |
| 77 | + |
| 78 | + $sections = array(); |
| 79 | + $tagPlaceHolders = array(); |
| 80 | + |
| 81 | + while (true) { |
| 82 | + $re = '~(<translate>)\s*(.*?)(</translate>)~s'; |
| 83 | + $matches = array(); |
| 84 | + $ok = preg_match_all( $re, $text, $matches, PREG_OFFSET_CAPTURE ); |
| 85 | + if ( $ok === false ) return array( 'pt-error', 'tag', $text ); |
| 86 | + if ( $ok === 0 ) break; // No matches |
| 87 | + |
| 88 | + // Do-placehold for the whole stuff |
| 89 | + $ph = $this->getUniq(); |
| 90 | + $start = $matches[0][0][1]; |
| 91 | + $len = strlen($matches[0][0][0]); |
| 92 | + $end = $start + $len; |
| 93 | + $text = self::index_replace( $text, $ph, $start, $end ); |
| 94 | + |
| 95 | + // Sectionise the contents |
| 96 | + // Strip the surrounding tags |
| 97 | + $contents = $matches[0][0][0]; // full match |
| 98 | + $start = $matches[2][0][1] - $matches[0][0][1]; // bytes before actual content |
| 99 | + $len = strlen($matches[2][0][0]); // len of the content |
| 100 | + $end = $start + $len; |
| 101 | + |
| 102 | + $ret = $this->sectionise( &$sections, substr( $contents, $start, $len ) ); |
| 103 | + |
| 104 | + $tagPlaceHolders[$ph] = |
| 105 | + self::index_replace( $contents, $ret, $start, $end ); |
| 106 | + } |
| 107 | + |
| 108 | + if ( strpos( $text, '<translate>' ) !== false ) |
| 109 | + throw new TPException( array( 'pt-parse-open', $text ) ); |
| 110 | + |
| 111 | + if ( strpos( $text, '</translate>' ) !== false ) |
| 112 | + throw new TPException( array( 'pt-parse-close', $text ) ); |
| 113 | + |
| 114 | + foreach ( $tagPlaceHolders as $ph => $value ) { |
| 115 | + $text = str_replace( $ph, $value, $text ); |
| 116 | + } |
| 117 | + |
| 118 | + $parse = new TPParse( $this->getTitle() ); |
| 119 | + $parse->template = $text; |
| 120 | + $parse->sections = $sections; |
| 121 | + |
| 122 | + // Cache it |
| 123 | + $this->cachedParse = $parse; |
| 124 | + |
| 125 | + return $parse; |
| 126 | + } |
| 127 | + |
| 128 | + // Inner functionality // |
| 129 | + |
| 130 | + protected function getUniq() { |
| 131 | + static $i = 0; |
| 132 | + return "\x7fUNIQ" . dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff)) . '|' . $i++; |
| 133 | + } |
| 134 | + |
| 135 | + protected static function index_replace( $string, $rep, $start, $end ) { |
| 136 | + return substr( $string, 0, $start ) . $rep . substr( $string, $end ); |
| 137 | + } |
| 138 | + |
| 139 | + protected function sectionise( $sections, $text ) { |
| 140 | + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE; |
| 141 | + $parts = preg_split( '~(\s*\n\n\s*|\s*$)~', $text, -1, $flags ); |
| 142 | + |
| 143 | + $template = ''; |
| 144 | + foreach ( $parts as $_ ) { |
| 145 | + if ( trim( $_ ) === '' ) { |
| 146 | + $template .= $_; |
| 147 | + } else { |
| 148 | + $ph = $this->getUniq(); |
| 149 | + $sections[$ph] = $this->shakeSection( $_ ); |
| 150 | + $template .= $ph; |
| 151 | + } |
| 152 | + } |
| 153 | + return $template; |
| 154 | + } |
| 155 | + |
| 156 | + protected function shakeSection( $content ) { |
| 157 | + $re = '~<!--T:(.*?)-->~'; |
| 158 | + $matches = array(); |
| 159 | + $count = preg_match_all( $re, $content, $matches, PREG_SET_ORDER ); |
| 160 | + if ( $count === false ) throw new TPException( 'pt-error', 'shake', $content ); |
| 161 | + if ( $count > 1 ) throw new TPException( 'pt-shake-dupid', $content ); |
| 162 | + |
| 163 | + $section = new TPSection; |
| 164 | + if ( $count === 1 ) { |
| 165 | + foreach ( $matches as $match ) { |
| 166 | + list( /*full*/, $id ) = $match; |
| 167 | + $section->id = $id; |
| 168 | + |
| 169 | + $rer1 = '~^\s*<!--T:(.*?)-->\s*~'; // Normal sections |
| 170 | + $rer2 = '~\s*<!--T:(.*?)-->~'; // Sections with title |
| 171 | + $content = preg_replace( $rer1, '', $content ); |
| 172 | + $content = preg_replace( $rer2, '', $content ); |
| 173 | + } |
| 174 | + } else { // New section |
| 175 | + $section->id = -1; |
| 176 | + } |
| 177 | + |
| 178 | + if ( $content === '' ) throw new TPException( 'pt-shake-empty' ); |
| 179 | + |
| 180 | + $section->text = $content; |
| 181 | + |
| 182 | + return $section; |
| 183 | + } |
| 184 | + |
| 185 | + public function addMarkedTag( $revision ) { |
| 186 | + $this->addTag( 'tp:mark', $revision ); |
| 187 | + } |
| 188 | + |
| 189 | + public function addReadyTag( $revision ) { |
| 190 | + $this->addTag( 'tp:tag', $revision ); |
| 191 | + } |
| 192 | + |
| 193 | + protected function addTag( $tag, $revision ) { |
| 194 | + $dbw = wfGetDB( DB_MASTER ); |
| 195 | + |
| 196 | + // Can this be done in one query? |
| 197 | + $id = $dbw->selectField( 'revtag_type', 'rtt_id', |
| 198 | + array( 'rtt_name' => $tag ), __METHOD__ ); |
| 199 | + |
| 200 | + $conds = array( |
| 201 | + 'rt_page' => $this->getTitle()->getArticleId(), |
| 202 | + 'rt_type' => $id, |
| 203 | + 'rt_revision' => $revision |
| 204 | + ); |
| 205 | + $dbw->delete( 'revtag', $conds, __METHOD__ ); |
| 206 | + $dbw->insert( 'revtag', $conds, __METHOD__ ); |
| 207 | + } |
| 208 | + |
| 209 | + public function getMarkedTag($db = DB_SLAVE) { |
| 210 | + return $this->getTag( 'tp:mark' ); |
| 211 | + } |
| 212 | + public function getReadyTag($db = DB_SLAVE) { |
| 213 | + return $this->getTag( 'tp:tag' ); |
| 214 | + } |
| 215 | + |
| 216 | + protected function getTag( $tag, $dbt = DB_SLAVE ) { |
| 217 | + $db = wfGetDB( $dbt ); |
| 218 | + |
| 219 | + // Can this be done in one query? |
| 220 | + $id = $db->selectField( 'revtag_type', 'rtt_id', |
| 221 | + array( 'rtt_name' => $tag ), __METHOD__ ); |
| 222 | + |
| 223 | + $fields = 'rt_revision'; |
| 224 | + $conds = array( |
| 225 | + 'rt_page' => $this->getTitle()->getArticleId(), |
| 226 | + 'rt_type' => $id, |
| 227 | + ); |
| 228 | + $options = array( 'ORDER BY', 'rt_revision DESC' ); |
| 229 | + return $db->selectField( 'revtag', $fields, $conds, __METHOD__, $options ); |
| 230 | + } |
| 231 | + |
| 232 | + |
| 233 | + public function getTranslationUrl( $code ) { |
| 234 | + $translate = SpecialPage::getTitleFor( 'Translate' ); |
| 235 | + $params = array( |
| 236 | + 'group' => 'page|' . $this->getTitle()->getPrefixedText(), |
| 237 | + 'task' => 'view' |
| 238 | + ); |
| 239 | + if ( $code ) $params['language'] = $code; |
| 240 | + return $translate->getFullURL( $params ); |
| 241 | + } |
| 242 | + |
| 243 | + protected function getMarkedRevs( $tag ) { |
| 244 | + $db = wfGetDB( DB_SLAVE ); |
| 245 | + |
| 246 | + // Can this be done in one query? |
| 247 | + $id = $db->selectField( 'revtag_type', 'rtt_id', |
| 248 | + array( 'rtt_name' => $tag ), __METHOD__ ); |
| 249 | + |
| 250 | + $fields = array( 'rt_revision', 'rt_value' ); |
| 251 | + $conds = array( |
| 252 | + 'rt_page' => $this->getTitle()->getArticleId(), |
| 253 | + 'rt_type' => $id, |
| 254 | + ); |
| 255 | + $options = array( 'ORDER BY', 'rt_revision DESC' ); |
| 256 | + return $db->select( 'revtag', $fields, $conds, __METHOD__, $options ); |
| 257 | + } |
| 258 | + |
| 259 | + public function getTranslationPercentages() { |
| 260 | + // Check the memory cache, as this is very slow to calculate |
| 261 | + global $wgMemc; |
| 262 | + $memcKey = wfMemcKey( 'pt', 'status', $this->getTitle()->getPrefixedText() ); |
| 263 | + $cache = $wgMemc->get( $memcKey ); |
| 264 | + #if ( is_array( $cache ) ) return $cache; |
| 265 | + |
| 266 | + // Fetch the available translation pages from database |
| 267 | + $dbr = wfGetDB( DB_SLAVE ); |
| 268 | + $likePattern = $dbr->escapeLike( $this->getTitle()->getDBkey() ) . '/%%'; |
| 269 | + $res = $dbr->select( |
| 270 | + 'page', |
| 271 | + array( 'page_namespace', 'page_title' ), |
| 272 | + array( |
| 273 | + 'page_namespace' => $this->getTitle()->getNamespace(), |
| 274 | + "page_title LIKE '$likePattern'" |
| 275 | + ), __METHOD__ ); |
| 276 | + |
| 277 | + $titles = TitleArray::newFromResult( $res ); |
| 278 | + |
| 279 | + // Calculate percentages for the available translations |
| 280 | + $group = MessageGroups::getGroup( 'page|' . $this->getTitle()->getPrefixedText() ); |
| 281 | + if ( !$group instanceof WikiPageMessageGroup ) return null; |
| 282 | + |
| 283 | + $markedRevs = $this->getMarkedRevs( 'tp:mark' ); |
| 284 | + |
| 285 | + |
| 286 | + $temp = array(); |
| 287 | + foreach ( $titles as $t ) { |
| 288 | + if ( $t->getSubpageText() === $t->getText() ) continue; |
| 289 | + $collection = $group->initCollection( $t->getSubpageText() ); |
| 290 | + $group->fillCollection( $collection ); |
| 291 | + $temp[$collection->code] = $this->getPercentageInternal( $collection, $markedRevs ); |
| 292 | + } |
| 293 | + // English is always up-to-date |
| 294 | + $temp['en'] = 1.00; |
| 295 | + |
| 296 | + // TODO: Ideally there would be some kind of purging here |
| 297 | + $wgMemc->set( $memcKey, $temp, 60*60*12 ); |
| 298 | + return $temp; |
| 299 | + } |
| 300 | + |
| 301 | + protected function getPercentageInternal( $collection, $markedRevs ) { |
| 302 | + $count = count($collection); |
| 303 | + if ( $count === 0 ) return 0; |
| 304 | + |
| 305 | + $total = 0; |
| 306 | + |
| 307 | + foreach ( $collection as $key => $message ) { |
| 308 | + if ( !$message->translated() ) continue; // No score |
| 309 | + |
| 310 | + $score = 1; |
| 311 | + |
| 312 | + // Fuzzy halves |
| 313 | + if ( $message->fuzzy() ) $score *= 0.5; |
| 314 | + |
| 315 | + // Reduce 20% for every newer revision than what is translated against |
| 316 | + $rev = $this->getTransrev( $key .'/' . $collection->code ); |
| 317 | + foreach ( $markedRevs as $r ) { |
| 318 | + if ( $rev === $r->rt_revision ) break; |
| 319 | + $score *= 0.8; |
| 320 | + } |
| 321 | + $total += $score; |
| 322 | + } |
| 323 | + return $total/$count; |
| 324 | + } |
| 325 | + |
| 326 | + protected function getTransRev( $suffix ) { |
| 327 | + $id = $this->getTagId( 'tp:transver' ); |
| 328 | + $title = Title::makeTitle( NS_TRANSLATIONS, |
| 329 | + $this->getTitle()->getPrefixedText() . '/' . $suffix |
| 330 | + ); |
| 331 | + |
| 332 | + $db = wfGetDB( DB_SLAVE ); |
| 333 | + $fields = 'rt_value'; |
| 334 | + $conds = array( |
| 335 | + 'rt_page' => $title->getArticleId(), |
| 336 | + 'rt_type' => $id, |
| 337 | + ); |
| 338 | + $options = array( 'ORDER BY', 'rt_revision DESC' ); |
| 339 | + return $db->selectField( 'revtag', $fields, $conds, __METHOD__, $options ); |
| 340 | + |
| 341 | + } |
| 342 | + |
| 343 | + protected function getTagId( $tag ) { |
| 344 | + static $tagcache = array(); |
| 345 | + if ( !isset( $tagcache[$tag] ) ) { |
| 346 | + $db = wfGetDB( DB_SLAVE ); |
| 347 | + $tagcache[$tag] = $db->selectField( |
| 348 | + 'revtag_type', // Table |
| 349 | + 'rtt_id', // Field |
| 350 | + array( 'rtt_name' => $tag ), // Conds |
| 351 | + __METHOD__ |
| 352 | + ); |
| 353 | + } |
| 354 | + return $tagcache[$tag]; |
| 355 | + } |
| 356 | +} |
| 357 | + |
| 358 | +class TPException extends MWException { |
| 359 | + public function __construct( $msg ) { |
| 360 | + parent::__construct( call_user_func_array( 'wfMsg', $msg ) ); |
| 361 | + } |
| 362 | +} |
| 363 | + |
Property changes on: trunk/extensions/Translate/tag/TranslatablePage.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 364 | + native |
Index: trunk/extensions/Translate/Translate.php |
— | — | @@ -11,7 +11,7 @@ |
12 | 12 | * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
13 | 13 | */ |
14 | 14 | |
15 | | -define( 'TRANSLATE_VERSION', '11:2009-04-27' ); |
| 15 | +define( 'TRANSLATE_VERSION', '12:2009-05-09' ); |
16 | 16 | |
17 | 17 | $wgExtensionCredits['specialpage'][] = array( |
18 | 18 | 'path' => __FILE__, |
— | — | @@ -28,6 +28,7 @@ |
29 | 29 | require_once( $dir . '_autoload.php' ); |
30 | 30 | |
31 | 31 | $wgExtensionMessagesFiles['Translate'] = $dir . 'Translate.i18n.php'; |
| 32 | +$wgExtensionMessagesFiles['PageTranslation'] = $dir . 'PageTranslation.i18n.php'; |
32 | 33 | $wgExtensionAliasesFiles['Translate'] = $dir . 'Translate.alias.php'; |
33 | 34 | $wgExtensionFunctions[] = 'efTranslateInit'; |
34 | 35 | |
— | — | @@ -43,6 +44,7 @@ |
44 | 45 | $wgSpecialPageGroups['TranslationChanges'] = 'changes'; |
45 | 46 | $wgSpecialPageGroups['TranslationStats'] = 'wiki'; |
46 | 47 | $wgSpecialPageGroups['LanguageStats'] = 'wiki'; |
| 48 | +$wgSpecialPageGroups['PageTranslation'] = 'pagetools'; |
47 | 49 | |
48 | 50 | $wgHooks['EditPage::showEditForm:initial'][] = 'TranslateEditAddons::addTools'; |
49 | 51 | $wgHooks['OutputPageBeforeHTML'][] = 'TranslateEditAddons::addNavigation'; |
— | — | @@ -56,19 +58,10 @@ |
57 | 59 | $wgHooks['SpecialRecentChangesPanel'][] = 'TranslateRcFilter::translationFilterForm'; |
58 | 60 | $wgHooks['SkinTemplateToolboxEnd'][] = 'TranslateToolbox::toolboxAllTranslations'; |
59 | 61 | |
60 | | -// Tag hooks |
61 | | -$wgHooks['getUserPermissionsErrorsExpensive'][] = 'TranslateTagHooks::disableEdit'; |
62 | | -$wgHooks['ParserAfterStrip'][] = 'TranslateTagHooks::renderTagPage'; |
63 | | -$wgHooks['OutputPageBeforeHTML'][] = 'TranslateTagHooks::injectCss'; |
64 | | -$wgHooks['ArticleSaveComplete'][] = 'TranslateTagHooks::onSave'; |
65 | | -$wgHooks['SkinTemplateOutputPageBeforeExec'][] = 'TranslateTagHooks::addSidebar'; |
66 | | -$wgHooks['ArticleSave'][] = 'TranslateTag::save'; |
67 | | -$wgHooks['ParserFirstCallInit'][] = 'efTranslateInitTags'; |
68 | | -$wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'TranslateTagHooks::onTemplate'; |
| 62 | +$wgEnablePageTranslation = false; |
| 63 | +$wgPageTranslationNamespace = 1198; |
69 | 64 | |
70 | | - |
71 | | -$wgJobClasses['FuzzyJob'] = 'FuzzyJob'; |
72 | | -$wgJobClasses['RenderJob'] = 'RenderJob'; |
| 65 | +//$wgJobClasses['RenderJob'] = 'RenderJob'; |
73 | 66 | $wgAvailableRights[] = 'translate'; |
74 | 67 | |
75 | 68 | define( 'TRANSLATE_FUZZY', '!!FUZZY!!' ); |
— | — | @@ -181,46 +174,86 @@ |
182 | 175 | $wgTranslatePHPlot = false; |
183 | 176 | $wgTranslatePHPlotFont = '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf'; |
184 | 177 | |
185 | | -// Options for <translate> tag |
186 | | -/** |
187 | | - * Can be used to define where translation of sections are stored for tag |
188 | | - * translations. |
189 | | - * Two item array, where first item is namespace and latter is page name. |
190 | | - * Namespace is either null which means the same as the current namespace, or |
191 | | - * integer denoting some fixed namespace. |
192 | | - * Page name can have following variables: |
193 | | - * - $NS: the namespace of the source page with |
194 | | - * - $PAGE: name of the page without namespace |
195 | | - * - $FULLNAME: name of the page with possible namespace |
196 | | - * - $KEY: automatically or assigned unique key for section, |
197 | | - * without this page names may not be unique! |
198 | | - * - $SNIPPET: automatically constructed snippet of the section contents to give |
199 | | - * more meaningful names for the pages. |
200 | | - */ |
201 | | -$wgTranslateTagTranslationLocation = array( |
202 | | - null, // Namespace is whatever the page is in |
203 | | - '$PAGE/translations/$KEY-$SNIPPET' |
204 | | -); |
205 | 178 | |
206 | | -#$wgContentTranslation = true; |
| 179 | +function wfMemIn() { } |
| 180 | +function wfMemOut() { } |
207 | 181 | |
208 | | -if ( $wgDebugComments ) { |
209 | | - require_once( "$dir/utils/MemProfile.php" ); |
210 | | -} else { |
211 | | - function wfMemIn() { } |
212 | | - function wfMemOut() { } |
213 | | -} |
214 | | - |
215 | 182 | function efTranslateInit() { |
216 | 183 | global $wgTranslatePHPlot, $wgAutoloadClasses; |
217 | 184 | if ( $wgTranslatePHPlot ) { |
218 | 185 | $wgAutoloadClasses['PHPlot'] = $wgTranslatePHPlot; |
219 | 186 | } |
| 187 | + |
| 188 | + global $wgEnablePageTranslation; |
| 189 | + if ( $wgEnablePageTranslation ) { |
| 190 | + |
| 191 | + // Special page + the right to use it |
| 192 | + global $wgSpecialPages, $wgAvailableRights; |
| 193 | + $wgSpecialPages['PageTranslation'] = 'SpecialPageTranslation'; |
| 194 | + $wgAvailableRights[] = 'pagetranslation'; |
| 195 | + |
| 196 | + // Namespaces |
| 197 | + global $wgPageTranslationNamespace, $wgExtraNamespaces; |
| 198 | + global $wgNamespacesWithSubpages, $wgNamespaceProtection; |
| 199 | + global $wgTranslateMessageNamespaces; |
| 200 | + // Defines for nice usage |
| 201 | + define ( 'NS_TRANSLATIONS', $wgPageTranslationNamespace ); |
| 202 | + define ( 'NS_TRANSLATIONS_TALK', $wgPageTranslationNamespace +1 ); |
| 203 | + // Register them as namespaces |
| 204 | + $wgExtraNamespaces[NS_TRANSLATIONS] = 'Translations'; |
| 205 | + $wgExtraNamespaces[NS_TRANSLATIONS_TALK] = 'Translations_talk'; |
| 206 | + $wgNamespacesWithSubpages[NS_TRANSLATIONS] = true; |
| 207 | + $wgNamespacesWithSubpages[NS_TRANSLATIONS_TALK] = true; |
| 208 | + // Standard protection and register it for filtering |
| 209 | + $wgNamespaceProtection[NS_TRANSLATIONS] = array( 'translate' ); |
| 210 | + $wgTranslateMessageNamespaces[] = NS_TRANSLATIONS; |
| 211 | + |
| 212 | + // Page translation hooks |
| 213 | + global $wgHooks; |
| 214 | + |
| 215 | + // Register our css, is there a better place for this? |
| 216 | + $wgHooks['OutputPageBeforeHTML'][] = 'PageTranslationHooks::injectCss'; |
| 217 | + |
| 218 | + // Add transver tags and update translation target pages |
| 219 | + $wgHooks['ArticleSaveComplete'][] = 'PageTranslationHooks::onSectionSave'; |
| 220 | + |
| 221 | + // Foo |
| 222 | + #$wgHooks['SkinTemplateOutputPageBeforeExec'][] = 'TranslateTagHooks::addSidebar'; |
| 223 | + |
| 224 | + // Register <languages/> |
| 225 | + $wgHooks['ParserFirstCallInit'][] = 'efTranslateInitTags'; |
| 226 | + |
| 227 | + // Strip <translate> tags etc. from source pages when rendering |
| 228 | + $wgHooks['ParserBeforeStrip'][] = 'PageTranslationHooks::renderTagPage'; |
| 229 | + |
| 230 | + // Check syntax for <translate> |
| 231 | + $wgHooks['ArticleSave'][] = 'PageTranslationHooks::tpSyntaxCheck'; |
| 232 | + |
| 233 | + // Add transtag to page props for discovery |
| 234 | + $wgHooks['ArticleSaveComplete'][] = 'PageTranslationHooks::addTranstag'; |
| 235 | + |
| 236 | + // Prevent editing of unknown pages in Translations namespace |
| 237 | + $wgHooks['getUserPermissionsErrorsExpensive'][] = 'PageTranslationHooks::translationsCheck'; |
| 238 | + |
| 239 | + $wgHooks['ArticleViewHeader'][] = 'PageTranslationHooks::test'; |
| 240 | + |
| 241 | + // Database schema |
| 242 | + $wgHooks['LoadExtensionSchemaUpdates'][] = 'PageTranslationHooks::onLoadExtensionSchemaUpdates'; |
| 243 | + |
| 244 | + } |
| 245 | + |
220 | 246 | } |
221 | 247 | |
222 | | - |
223 | 248 | function efTranslateInitTags( $parser ) { |
224 | 249 | // For nice language list in-page |
225 | | - $parser->setHook( 'languages', array( 'TranslateTagHooks', 'languages' ) ); |
| 250 | + $parser->setHook( 'languages', array( 'PageTranslationHooks', 'languages' ) ); |
226 | 251 | return true; |
227 | 252 | } |
| 253 | + |
| 254 | + |
| 255 | +if ( !defined('TRANSLATE_CLI') ) { |
| 256 | + function STDOUT() {} |
| 257 | + function STDERR() {} |
| 258 | +} |
| 259 | + |
| 260 | +$wgGroupPermissions['staff' ]['pagetranslation'] = true; |
Index: trunk/extensions/Translate/translate.sql |
— | — | @@ -0,0 +1,16 @@ |
| 2 | +-- SQL tables for Translate extension |
| 3 | + |
| 4 | +-- List of each section which has a name and text |
| 5 | +CREATE TABLE /*$wgDBprefix*/translate_sections ( |
| 6 | + -- Key to page_id |
| 7 | + trs_page int unsigned NOT NULL, |
| 8 | + |
| 9 | + -- Customizable section name |
| 10 | + trs_key varchar(255) binary NOT NULL, |
| 11 | + |
| 12 | + -- Section contents |
| 13 | + trs_text mediumblob NOT NULL, |
| 14 | + |
| 15 | + PRIMARY KEY (trs_page, trs_key), |
| 16 | + KEY trs_page (trs_page) |
| 17 | +) /*$wgDBTableOptions*/; |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/translate.sql |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 18 | + native |
Index: trunk/extensions/Translate/_autoload.php |
— | — | @@ -38,6 +38,7 @@ |
39 | 39 | $wgAutoloadClasses['SpecialTranslations'] = $dir . 'SpecialTranslations.php'; |
40 | 40 | $wgAutoloadClasses['SpecialLanguageStats'] = $dir . 'SpecialLanguageStats.php'; |
41 | 41 | |
| 42 | + |
42 | 43 | $wgAutoloadClasses['SimpleFormatReader'] = $dir . 'ffs/Simple.php'; |
43 | 44 | $wgAutoloadClasses['SimpleFormatWriter'] = $dir . 'ffs/Simple.php'; |
44 | 45 | $wgAutoloadClasses['WikiFormatReader'] = $dir . 'ffs/Wiki.php'; |
— | — | @@ -66,6 +67,8 @@ |
67 | 68 | $wgAutoloadClasses['TranslatePreferences'] = $dir . 'utils/UserToggles.php'; |
68 | 69 | $wgAutoloadClasses['TranslateToolbox'] = $dir . 'utils/ToolBox.php'; |
69 | 70 | |
| 71 | +$wgAutoloadClasses['MessageIndex'] = $dir . 'utils/MessageIndex.php'; |
| 72 | + |
70 | 73 | # predefined groups |
71 | 74 | $wgAutoloadClasses['PremadeMediawikiExtensionGroups'] = $dir . 'groups/MediaWikiExtensions.php'; |
72 | 75 | $wgAutoloadClasses['CommonistMessageGroup'] = $dir . 'groups/Commonist.php'; |
— | — | @@ -74,8 +77,11 @@ |
75 | 78 | $wgAutoloadClasses['NoccMessageGroup'] = $dir . 'groups/Nocc.php'; |
76 | 79 | |
77 | 80 | # tag |
78 | | -$wgAutoloadClasses['TranslateTag'] = $dir . 'tag/Tag.php'; |
79 | | -$wgAutoloadClasses['TranslateTagHooks'] = $dir . 'tag/Hooks.php'; |
80 | | -$wgAutoloadClasses['TranslateTagUtils'] = $dir . 'tag/Utils.php'; |
81 | | -$wgAutoloadClasses['FuzzyJob'] = $dir . 'tag/FuzzyJob.php'; |
82 | | -$wgAutoloadClasses['RenderJob'] = $dir . 'tag/RenderJob.php'; |
| 81 | +#$wgAutoloadClasses['RenderJob'] = $dir . 'tag/RenderJob.php'; |
| 82 | +# page translation |
| 83 | +$wgAutoloadClasses['PageTranslationHooks'] = $dir . 'tag/PageTranslationHooks.php'; |
| 84 | +$wgAutoloadClasses['TranslatablePage'] = $dir . 'tag/TranslatablePage.php'; |
| 85 | +$wgAutoloadClasses['TPException'] = $dir . 'tag/TranslatablePage.php'; |
| 86 | +$wgAutoloadClasses['TPParse'] = $dir . 'tag/TPParse.php'; |
| 87 | +$wgAutoloadClasses['TPSection'] = $dir . 'tag/TPSection.php'; |
| 88 | +$wgAutoloadClasses['SpecialPageTranslation'] = $dir . 'tag/SpecialPageTranslation.php'; |
Index: trunk/extensions/Translate/utils/MessageIndex.php |
— | — | @@ -0,0 +1,123 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * Only used for page translation currently. |
| 5 | + * |
| 6 | + * @author Niklas Laxstrom |
| 7 | + * |
| 8 | + * @copyright Copyright © 2008-2009, Niklas Laxström |
| 9 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
| 10 | + * @file |
| 11 | + */ |
| 12 | + |
| 13 | +class MessageIndex { |
| 14 | + static $cache = array(); |
| 15 | + |
| 16 | + // Nice shortcut |
| 17 | + public static function titleToGroup( Title $title ) { |
| 18 | + $namespace = $title->getNamespace(); |
| 19 | + $text = $title->getDBkey(); |
| 20 | + list( $key, ) = TranslateUtils::figureMessage( $text ); |
| 21 | + return self::messageToGroup( $namespace, $key ); |
| 22 | + } |
| 23 | + |
| 24 | + public static function messageToGroup( $namespace, $key ) { |
| 25 | + $namepace = MWNamespace::getCanonicalName( $namespace ); |
| 26 | + if ( $namespace === false ) return null; |
| 27 | + |
| 28 | + $key = self::normaliseKey( $key ); |
| 29 | + $index = self::index( $namespace ); |
| 30 | + return @$index[$key]; |
| 31 | + } |
| 32 | + |
| 33 | + public static function cache( $namespace = null ) { |
| 34 | + if ( $namespace !== null ) { |
| 35 | + $namepace = MWNamespace::getCanonicalName( $namespace ); |
| 36 | + if ( $namespace === false ) return null; |
| 37 | + } |
| 38 | + |
| 39 | + $groups = MessageGroups::singleton()->getGroups(); |
| 40 | + |
| 41 | + $hugearray = array(); |
| 42 | + $postponed = array(); |
| 43 | + |
| 44 | + STDOUT( "Working with ", 'main' ); |
| 45 | + |
| 46 | + foreach ( $groups as $g ) { |
| 47 | + # the cache is split per namespace for efficieny |
| 48 | + if ( $namespace !== null && $g->namespaces[0] !== $namespace ) |
| 49 | + continue; |
| 50 | + |
| 51 | + # Skip meta thingies |
| 52 | + if ( $g->isMeta() ) { |
| 53 | + $postponed[] = $g; |
| 54 | + continue; |
| 55 | + } |
| 56 | + |
| 57 | + self::checkAndAdd( $hugearray, $g ); |
| 58 | + } |
| 59 | + |
| 60 | + foreach ( $postponed as $g ) { |
| 61 | + self::checkAndAdd( $hugearray, $g, true ); |
| 62 | + } |
| 63 | + |
| 64 | + foreach ( $hugearray as $ns => $array ) { |
| 65 | + wfMkdirParents( dirname( self::file($ns) ) ); |
| 66 | + file_put_contents( self::file($ns), serialize( $array ) ); |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + protected static function checkAndAdd( &$hugearray, $g, $ignore = false ) { |
| 71 | + $messages = $g->getDefinitions(); |
| 72 | + $id = $g->getId(); |
| 73 | + |
| 74 | + if ( !is_array( $messages ) ) continue; |
| 75 | + |
| 76 | + STDOUT( "$id ", 'main' ); |
| 77 | + |
| 78 | + $namespace = $g->namespaces[0]; |
| 79 | + |
| 80 | + foreach ( $messages as $key => $data ) { |
| 81 | + # Force all keys to lower case, because the case doesn't matter and it is |
| 82 | + # easier to do comparing when the case of first letter is unknown, because |
| 83 | + # mediawiki forces it to upper case |
| 84 | + $key = self::normaliseKey( $key ); |
| 85 | + if ( isset( $hugearray[$namespace][$key] ) ) { |
| 86 | + if ( !$ignore ) |
| 87 | + STDERR( "Key $key already belongs to $hugearray[$namespace][$key], conflict with $id" ); |
| 88 | + } else { |
| 89 | + $hugearray[$namespace][$key] = &$id; |
| 90 | + } |
| 91 | + } |
| 92 | + unset( $id ); // Disconnect the previous references to this $id |
| 93 | + |
| 94 | + } |
| 95 | + |
| 96 | + protected static function file( $namespace ) { |
| 97 | + $dir = realpath( dirname( __FILE__ ) . '/../data' ); |
| 98 | + $namepace = MWNamespace::getCanonicalName( $namespace ); |
| 99 | + return "$dir/messageindex-$namespace.ser"; |
| 100 | + } |
| 101 | + |
| 102 | + protected static function normaliseKey( $key ) { |
| 103 | + return str_replace( " ", "_", strtolower( $key ) ); |
| 104 | + } |
| 105 | + |
| 106 | + protected static function index( $namespace ) { |
| 107 | + if ( !isset(self::$cache[$namespace]) ) { |
| 108 | + |
| 109 | + $file = self::file( $namespace ); |
| 110 | + if ( !file_exists( $file ) ) { |
| 111 | + self::cache( $namespace ); |
| 112 | + } |
| 113 | + |
| 114 | + if ( file_exists( $file ) ) { |
| 115 | + self::$cache[$namespace] = unserialize( file_get_contents( $file ) ); |
| 116 | + } else { |
| 117 | + self::$cache[$namespace] = array(); |
| 118 | + wfDebug( __METHOD__ . ": Message index missing." ); |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + return self::$cache[$namespace]; |
| 123 | + } |
| 124 | +} |
\ No newline at end of file |
Property changes on: trunk/extensions/Translate/utils/MessageIndex.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 125 | + native |