Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
function POTY(){
    var aj
    var token
    var wgAPIPath = wgServer + wgScriptPath + '/api.php?format=json&'
    function ch(o) { for (var i in o) { return o[i] } }
 
    var i18n = {
        'en': {
            'comment': 'Comment',
            'vote': 'Support',
            'saved': 'Vote saved',
            'votes': 'Votes list',
            'alreadyvoted': 'You have already voted for this image',
            'alreadyvoted2': 'You have edited the corresponding page. Perhaps your edit was a vote.'
        },
        'el': { // by Badseed
            'comment': 'Σχόλιο',
            'vote': 'Υπέρ',
            'saved': 'Η ψήφος αποθηκεύτηκε',
            'votes': 'Κατάλογος ψήφων',
            'alreadyvoted': 'Έχετε ήδη ψηφίσει αυτή την εικόνα',
            'alreadyvoted2': 'Επεξεργαστήκατε την αντίστοιχη σελίδα. Ίσως η επεξεργασία σας ήταν ψήφος.'
        },
        'fr': { // by Delhovlyn
            'comment': 'Commentaire',
            'vote': 'Soutenir',
            'saved': 'Vote enregistré',
            'votes': 'Liste des votes',
            'alreadyvoted': 'Vous avez déjà voté pour cette image',
            'alreadyvoted2': 'Vous avez édité la page correspondante. Peut-être que votre modification était un vote.'
        },
        'ru': {
            'comment': 'Комментарий',
            'vote': 'Проголосовать',
            'saved': 'Голос принят',
            'votes': 'Список голосов',
            'alreadyvoted': 'Вы уже проголосовали за это изображение',
            'alreadyvoted2': 'Вы редактировали соответствующую страницу. Возможно, эта правка была голосом.'
        },
        'uk': { // by Ahonc
            'comment': 'Коментар',
            'vote': 'Проголосувати',
            'saved': 'Голос прийнятий',
            'votes': 'Список голосів',
            'alreadyvoted': 'Ви вже голосували за це зображення',
            'alreadyvoted2': 'Ви редагували відповідну сторінку. Можливо, це редагування було голосом.'
        }
    }
    i18n = i18n[wgUserLanguage] || i18n.en
 
    appendCSS('                           \
        .purgeyourcache {                 \
            display: none;                \
        }                                 \
        form.potyvote input {             \
            width: 95%;                   \
        }                                 \
        form.potyvote input.go {          \
            width: auto;                  \
        }                                 \
        form.potyvote input.gr {          \
            color: #BBB;                  \
        }                                 \
        .poty-saved {                     \
            font-weight: bold;            \
            color: #4B0;                  \
            padding: 0.5em;               \
            text-align: center;           \
            font-size: smaller;           \
        }                                 \
        .poty-edited {                    \
            padding: 0.2em;               \
            color: #D27F37;               \
            font-size: smaller;           \
            line-height: 1.3em;           \
        }                                 \
        .poty-saved-img img {             \
            opacity: 0.16;                \
        }                                 \
        .poty-votes {                     \
            padding: 0.2em;               \
            font-size: smaller;           \
            color: #666 !important;       \
            text-align: right;            \
        }                                 \
    ')
 
    function init() {
        aj = sajax_init_object()
        aj.open('GET', wgAPIPath + 'action=query&list=usercontribs&ucnamespace=4&uclimit=500' +
                       '&ucdir=older&prop=info&intoken=edit&titles=1' + 
                       '&ucuser=<&ucend=20100512000000'.replace(/</, encodeURIComponent(wgUserName)), true)
        aj.onreadystatechange = function() { if (aj.readyState == 4 && aj.status == 200) galleries() }
        aj.send('')
    }
 
    window.POTYsave = function(button) {
        function e(a) { return encodeURIComponent(a) }
 
        var co = button.parentNode.getElementsByTagName('input')[0].value
        if (button.parentNode.getElementsByTagName('input')[0].className) co = ''
        
        text = '\n# {{2009POTY/Vote|' + wgUserName + '|comment=' + co + '}} ~~' + '~~'
        
        aj = sajax_init_object()
        aj.open('POST', wgAPIPath + 'action=edit&watchlist=nochange&section=2&token=' + e(token) +
                             '&summary=' + e('+1\u200B' + (co?': ':'') + co) +
                             '&title=' + e(button.title) +
                             '&appendtext=' + e(text), true)
 
        var p = button.parentNode.parentNode
        p.innerHTML = ''
        aj.onreadystatechange = function() {
            if (aj.readyState == 4 && aj.status == 200) {
                p.className = 'poty-saved'
                p.parentNode.parentNode.className += ' poty-saved-img'
                p.innerHTML = i18n.saved
            } else {
                p.innerHTML += '...'
            }
        }
        aj.send('')
    }
 
    function galleries() {
        data = eval('(' + aj.responseText + ')')
        if (!data) return // o_O
        token = ch(data.query.pages).edittoken
 
        contribs = data.query.usercontribs
        previous = {}
        var match
        for (var i=0; i<contribs.length; i++) {
            if (match = contribs[i].title.match(POTYregexp))
                previous[match[1]] = contribs[i].comment.match(/^\+1\u200b/) ? 2 : 1
        }
 
        var gallery = getElementsByClassName(document.body, 'div', 'dshuf')
        if (!gallery) return
 
        var gallerytext
        for (var i=1; i<gallery.length; i++) { // i=0 contains the whole table
            gallerytext = getElementsByClassName(gallery[i], 'div', 'gallerytext')[0]
            
            if (!gallerytext) continue
            title = gallerytext.getElementsByTagName('a')[0].title
            href  = gallerytext.getElementsByTagName('a')[0].href
 
            if (previous[title.match(POTYregexp)[1]] == 2) {
                gallerytext.innerHTML = '<p class="poty-saved">%</p>'.replace(/%/, i18n.alreadyvoted)
                gallerytext.parentNode.className += ' poty-saved-img'
            } else {
                gallerytext.innerHTML = '<p><form class="potyvote" action="javascript:void(0)">\
                <div><input type="text" class="gr" value="%co" \
                onfocus="if(this.className==\'gr\'){this.value=\'\';this.className=\'\'}" \
                onblur=" if(this.value==\'\'){this.className=\'gr\';this.value=\'%co\'}" /></div>\
                <input class="go" title="%ti" type="submit" onclick="POTYsave(this)" value="%su" /></form>\
                <div class="poty-edited">%da</div></p>'
                .replace(/%co/g, i18n.comment)
                .replace(/%su/g, i18n.vote)
                .replace(/%ti/g, title.replace(/&/g, '&amp;')
                                      .replace(/\u0022/g, '&quot;')
                                      .replace(/\u0027/g, '&#39;'))
                .replace(/%da/g, previous[title.match(POTYregexp)[1]] == 1 ? i18n.alreadyvoted2 : '')
            }
            gallerytext.parentNode.getElementsByTagName('div')[0].innerHTML +=
                '<a class="poty-votes" href="%">$</a>'
                .replace(/%/, href).replace(/\$/, i18n.votes)
                
        }
    }
 
    init()
}
 
POTYregexp = /^Commons:Picture of the Year\/2009\/(.*)/
POTYgalleries = /^Commons:Picture of the Year\/2009\/Galleries\/(.*)/
 
if (wgPageName.replace(/_/g, ' ').match(POTYgalleries))
    addOnloadHook(POTY)