aboutsummaryrefslogtreecommitdiffstats
path: root/mediagoblin
diff options
context:
space:
mode:
Diffstat (limited to 'mediagoblin')
-rw-r--r--mediagoblin/__init__.py17
-rw-r--r--mediagoblin/_version.py26
-rw-r--r--mediagoblin/admin/__init__.py16
-rw-r--r--mediagoblin/admin/routing.py20
-rw-r--r--mediagoblin/admin/views.py48
-rw-r--r--mediagoblin/app.py268
-rw-r--r--mediagoblin/auth/__init__.py15
-rw-r--r--mediagoblin/auth/forms.py66
-rw-r--r--mediagoblin/auth/lib.py120
-rw-r--r--mediagoblin/auth/routing.py33
-rw-r--r--mediagoblin/auth/tools.py159
-rw-r--r--mediagoblin/auth/views.py319
-rw-r--r--mediagoblin/config_spec.ini191
-rw-r--r--mediagoblin/db/__init__.py49
-rw-r--r--mediagoblin/db/base.py78
-rw-r--r--mediagoblin/db/extratypes.py63
-rw-r--r--mediagoblin/db/migration_tools.py276
-rw-r--r--mediagoblin/db/migrations.py289
-rw-r--r--mediagoblin/db/mixin.py334
-rw-r--r--mediagoblin/db/models.py524
-rw-r--r--mediagoblin/db/models_v0.py342
-rw-r--r--mediagoblin/db/open.py101
-rw-r--r--mediagoblin/db/util.py76
-rw-r--r--mediagoblin/decorators.py237
-rw-r--r--mediagoblin/edit/__init__.py15
-rw-r--r--mediagoblin/edit/forms.py107
-rw-r--r--mediagoblin/edit/lib.py24
-rw-r--r--mediagoblin/edit/routing.py28
-rw-r--r--mediagoblin/edit/views.py371
-rw-r--r--mediagoblin/errormiddleware.py60
-rw-r--r--mediagoblin/gmg_commands/__init__.py108
-rw-r--r--mediagoblin/gmg_commands/assetlink.py151
-rw-r--r--mediagoblin/gmg_commands/dbupdate.py132
-rw-r--r--mediagoblin/gmg_commands/import_export.py254
-rw-r--r--mediagoblin/gmg_commands/shell.py76
-rw-r--r--mediagoblin/gmg_commands/users.py103
-rw-r--r--mediagoblin/gmg_commands/util.py40
-rw-r--r--mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mobin0 -> 28177 bytes
-rw-r--r--mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po1256
-rw-r--r--mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mobin0 -> 25199 bytes
-rw-r--r--mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po1254
-rw-r--r--mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mobin0 -> 24330 bytes
-rw-r--r--mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po1254
-rw-r--r--mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mobin0 -> 25811 bytes
-rw-r--r--mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po1265
-rw-r--r--mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po1257
-rw-r--r--mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mobin0 -> 25211 bytes
-rw-r--r--mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po1255
-rw-r--r--mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mobin0 -> 25994 bytes
-rw-r--r--mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po1263
-rw-r--r--mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mobin0 -> 25146 bytes
-rw-r--r--mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po1252
-rw-r--r--mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mobin0 -> 26152 bytes
-rw-r--r--mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po1261
-rw-r--r--mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.mobin0 -> 27529 bytes
-rw-r--r--mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po1254
-rw-r--r--mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mobin0 -> 24326 bytes
-rw-r--r--mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.mobin0 -> 26342 bytes
-rw-r--r--mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mobin0 -> 25279 bytes
-rw-r--r--mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po1256
-rw-r--r--mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mobin0 -> 24944 bytes
-rw-r--r--mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.mobin0 -> 26202 bytes
-rw-r--r--mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.po1252
-rw-r--r--mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mobin0 -> 24753 bytes
-rw-r--r--mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mobin0 -> 23771 bytes
-rw-r--r--mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mobin0 -> 25584 bytes
-rw-r--r--mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mobin0 -> 25166 bytes
-rw-r--r--mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po1256
-rw-r--r--mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mobin0 -> 25911 bytes
-rw-r--r--mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mobin0 -> 32200 bytes
-rw-r--r--mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mobin0 -> 25686 bytes
-rw-r--r--mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po1257
-rw-r--r--mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mobin0 -> 24504 bytes
-rw-r--r--mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po1252
-rw-r--r--mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mobin0 -> 25789 bytes
-rw-r--r--mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mobin0 -> 24362 bytes
-rw-r--r--mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po1251
-rw-r--r--mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mobin0 -> 24592 bytes
-rw-r--r--mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po1253
-rw-r--r--mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mobin0 -> 24563 bytes
-rw-r--r--mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po1252
-rw-r--r--mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.mobin0 -> 15458 bytes
-rw-r--r--mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.po770
-rw-r--r--mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mobin0 -> 24771 bytes
-rw-r--r--mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po1252
-rw-r--r--mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mobin0 -> 23615 bytes
-rw-r--r--mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po1256
-rw-r--r--mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mobin0 -> 24306 bytes
-rw-r--r--mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po1251
-rw-r--r--mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mobin0 -> 23703 bytes
-rw-r--r--mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po1256
-rw-r--r--mediagoblin/init/__init__.py153
-rw-r--r--mediagoblin/init/celery/__init__.py99
-rw-r--r--mediagoblin/init/celery/dummy_settings_module.py0
-rw-r--r--mediagoblin/init/celery/from_celery.py96
-rw-r--r--mediagoblin/init/config.py164
-rw-r--r--mediagoblin/init/plugins/__init__.py62
-rw-r--r--mediagoblin/listings/__init__.py19
-rw-r--r--mediagoblin/listings/routing.py29
-rw-r--r--mediagoblin/listings/views.py110
-rw-r--r--mediagoblin/meddleware/__init__.py31
-rw-r--r--mediagoblin/meddleware/csrf.py152
-rw-r--r--mediagoblin/media_types/__init__.py155
-rw-r--r--mediagoblin/media_types/ascii/__init__.py31
-rw-r--r--mediagoblin/media_types/ascii/asciitoimage.py146
l---------mediagoblin/media_types/ascii/fonts/Inconsolata.otf1
-rw-r--r--mediagoblin/media_types/ascii/migrations.py17
-rw-r--r--mediagoblin/media_types/ascii/models.py40
-rw-r--r--mediagoblin/media_types/ascii/processing.py146
-rw-r--r--mediagoblin/media_types/audio/__init__.py30
l---------mediagoblin/media_types/audio/audioprocessing.py1
-rw-r--r--mediagoblin/media_types/audio/migrations.py17
-rw-r--r--mediagoblin/media_types/audio/models.py40
-rw-r--r--mediagoblin/media_types/audio/processing.py156
-rw-r--r--mediagoblin/media_types/audio/spectrogram.py360
-rw-r--r--mediagoblin/media_types/audio/transcoders.py237
-rw-r--r--mediagoblin/media_types/image/__init__.py55
-rw-r--r--mediagoblin/media_types/image/migrations.py17
-rw-r--r--mediagoblin/media_types/image/models.py49
-rw-r--r--mediagoblin/media_types/image/processing.py180
-rw-r--r--mediagoblin/media_types/pdf/__init__.py31
-rw-r--r--mediagoblin/media_types/pdf/migrations.py17
-rw-r--r--mediagoblin/media_types/pdf/models.py58
-rw-r--r--mediagoblin/media_types/pdf/processing.py277
-rw-r--r--mediagoblin/media_types/stl/__init__.py31
-rw-r--r--mediagoblin/media_types/stl/assets/blender_render.blendbin0 -> 401296 bytes
-rw-r--r--mediagoblin/media_types/stl/assets/blender_render.py84
-rw-r--r--mediagoblin/media_types/stl/migrations.py17
-rw-r--r--mediagoblin/media_types/stl/model_loader.py137
-rw-r--r--mediagoblin/media_types/stl/models.py50
-rw-r--r--mediagoblin/media_types/stl/processing.py193
-rw-r--r--mediagoblin/media_types/video/__init__.py36
-rw-r--r--mediagoblin/media_types/video/devices/web-advanced.json505
-rw-r--r--mediagoblin/media_types/video/devices/web-flv.pngbin0 -> 2234 bytes
-rw-r--r--mediagoblin/media_types/video/devices/web-webm.svg259
-rw-r--r--mediagoblin/media_types/video/devices/web.svg982
-rw-r--r--mediagoblin/media_types/video/migrations.py32
-rw-r--r--mediagoblin/media_types/video/models.py97
-rw-r--r--mediagoblin/media_types/video/processing.py212
-rw-r--r--mediagoblin/media_types/video/transcoders.py776
-rw-r--r--mediagoblin/media_types/video/util.py59
-rw-r--r--mediagoblin/messages.py50
-rw-r--r--mediagoblin/mg_globals.py70
-rw-r--r--mediagoblin/plugins/README6
-rw-r--r--mediagoblin/plugins/__init__.py16
-rw-r--r--mediagoblin/plugins/api/__init__.py47
-rw-r--r--mediagoblin/plugins/api/tools.py164
-rw-r--r--mediagoblin/plugins/api/views.py122
-rw-r--r--mediagoblin/plugins/flatpagesfile/README.rst163
-rw-r--r--mediagoblin/plugins/flatpagesfile/__init__.py78
-rw-r--r--mediagoblin/plugins/flatpagesfile/templates/flatpagesfile/base.html18
-rw-r--r--mediagoblin/plugins/geolocation/__init__.py35
-rw-r--r--mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html59
-rw-r--r--mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map_js_head.html25
-rw-r--r--mediagoblin/plugins/httpapiauth/__init__.py58
-rw-r--r--mediagoblin/plugins/oauth/README.rst148
-rw-r--r--mediagoblin/plugins/oauth/__init__.py109
-rw-r--r--mediagoblin/plugins/oauth/forms.py69
-rw-r--r--mediagoblin/plugins/oauth/migrations.py158
-rw-r--r--mediagoblin/plugins/oauth/models.py192
-rw-r--r--mediagoblin/plugins/oauth/templates/oauth/authorize.html31
-rw-r--r--mediagoblin/plugins/oauth/templates/oauth/client/connections.html34
-rw-r--r--mediagoblin/plugins/oauth/templates/oauth/client/list.html45
-rw-r--r--mediagoblin/plugins/oauth/templates/oauth/client/register.html34
-rw-r--r--mediagoblin/plugins/oauth/tools.py114
-rw-r--r--mediagoblin/plugins/oauth/views.py254
-rw-r--r--mediagoblin/plugins/piwigo/README.rst23
-rw-r--r--mediagoblin/plugins/piwigo/__init__.py42
-rw-r--r--mediagoblin/plugins/piwigo/forms.py44
-rw-r--r--mediagoblin/plugins/piwigo/tools.py165
-rw-r--r--mediagoblin/plugins/piwigo/views.py249
-rw-r--r--mediagoblin/plugins/raven/README.rst17
-rw-r--r--mediagoblin/plugins/raven/__init__.py92
-rw-r--r--mediagoblin/plugins/sampleplugin/README.rst8
-rw-r--r--mediagoblin/plugins/sampleplugin/__init__.py42
-rw-r--r--mediagoblin/plugins/trim_whitespace/README.rst25
-rw-r--r--mediagoblin/plugins/trim_whitespace/__init__.py73
-rw-r--r--mediagoblin/processing/__init__.py193
-rw-r--r--mediagoblin/processing/task.py145
-rw-r--r--mediagoblin/routing.py42
-rw-r--r--mediagoblin/static/css/audio.css84
-rw-r--r--mediagoblin/static/css/base.css748
l---------mediagoblin/static/css/extlib/reset.css1
-rw-r--r--mediagoblin/static/css/vjs-mg-skin.css415
l---------mediagoblin/static/extlib/leaflet1
l---------mediagoblin/static/extlib/pdf.js1
l---------mediagoblin/static/extlib/video-js1
l---------mediagoblin/static/fonts/Inconsolata.otf1
l---------mediagoblin/static/fonts/Lato-Bold.ttf1
l---------mediagoblin/static/fonts/Lato-BoldItalic.ttf1
l---------mediagoblin/static/fonts/Lato-Italic.ttf1
l---------mediagoblin/static/fonts/Lato-Regular.ttf1
-rw-r--r--mediagoblin/static/images/404.pngbin0 -> 156189 bytes
-rw-r--r--mediagoblin/static/images/background.pngbin0 -> 6336 bytes
-rw-r--r--mediagoblin/static/images/empty_back.pngbin0 -> 191 bytes
-rw-r--r--mediagoblin/static/images/frontpage_image.pngbin0 -> 65174 bytes
-rw-r--r--mediagoblin/static/images/goblin.icobin0 -> 318 bytes
-rw-r--r--mediagoblin/static/images/goblin.pngbin0 -> 413 bytes
-rw-r--r--mediagoblin/static/images/icon_comment.pngbin0 -> 283 bytes
-rw-r--r--mediagoblin/static/images/icon_feed.pngbin0 -> 378 bytes
-rw-r--r--mediagoblin/static/images/logo.pngbin0 -> 1689 bytes
-rw-r--r--mediagoblin/static/images/logo.svg116
-rw-r--r--mediagoblin/static/images/media_thumbs/image.pngbin0 -> 30341 bytes
-rw-r--r--mediagoblin/static/images/media_thumbs/video.jpgbin0 -> 7278 bytes
-rw-r--r--mediagoblin/static/images/video-js.pngbin0 -> 4459 bytes
-rw-r--r--mediagoblin/static/js/audio.js229
-rw-r--r--mediagoblin/static/js/autofilledin_password.js25
-rw-r--r--mediagoblin/static/js/collection_form_show.js26
-rw-r--r--mediagoblin/static/js/comment_show.js27
l---------mediagoblin/static/js/extlib/html5slider.js1
l---------mediagoblin/static/js/extlib/jquery.js1
l---------mediagoblin/static/js/extlib/leaflet1
l---------mediagoblin/static/js/extlib/thingiview.js1
-rw-r--r--mediagoblin/static/js/geolocation-map.js47
-rw-r--r--mediagoblin/static/js/header_dropdown.js27
-rw-r--r--mediagoblin/static/js/keyboard_navigation.js41
-rw-r--r--mediagoblin/static/js/show_password.js38
-rw-r--r--mediagoblin/storage/__init__.py264
-rw-r--r--mediagoblin/storage/cloudfiles.py246
-rw-r--r--mediagoblin/storage/filestorage.py113
-rw-r--r--mediagoblin/storage/mountstorage.py160
-rw-r--r--mediagoblin/submit/__init__.py15
-rw-r--r--mediagoblin/submit/forms.py53
-rw-r--r--mediagoblin/submit/lib.py102
-rw-r--r--mediagoblin/submit/routing.py21
-rw-r--r--mediagoblin/submit/views.py153
-rw-r--r--mediagoblin/templates/mediagoblin/admin/panel.html114
-rw-r--r--mediagoblin/templates/mediagoblin/auth/change_fp.html44
-rw-r--r--mediagoblin/templates/mediagoblin/auth/forgot_password.html38
-rw-r--r--mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt30
-rw-r--r--mediagoblin/templates/mediagoblin/auth/login.html62
-rw-r--r--mediagoblin/templates/mediagoblin/auth/register.html47
-rw-r--r--mediagoblin/templates/mediagoblin/auth/verification_email.txt26
-rw-r--r--mediagoblin/templates/mediagoblin/base.html128
-rw-r--r--mediagoblin/templates/mediagoblin/bits/above_content.html17
-rw-r--r--mediagoblin/templates/mediagoblin/bits/base_footer.html28
-rw-r--r--mediagoblin/templates/mediagoblin/bits/body_end.html17
-rw-r--r--mediagoblin/templates/mediagoblin/bits/body_start.html17
-rw-r--r--mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html35
-rw-r--r--mediagoblin/templates/mediagoblin/bits/logo.html25
-rw-r--r--mediagoblin/templates/mediagoblin/edit/attachments.html69
-rw-r--r--mediagoblin/templates/mediagoblin/edit/change_pass.html52
-rw-r--r--mediagoblin/templates/mediagoblin/edit/delete_account.html48
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit.html48
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit_account.html65
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit_collection.html39
-rw-r--r--mediagoblin/templates/mediagoblin/edit/edit_profile.html45
-rw-r--r--mediagoblin/templates/mediagoblin/error.html28
-rw-r--r--mediagoblin/templates/mediagoblin/extra_head.html19
-rw-r--r--mediagoblin/templates/mediagoblin/listings/collection.html43
-rw-r--r--mediagoblin/templates/mediagoblin/listings/tag.html43
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/ascii.html44
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/audio.html65
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/image.html46
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/pdf.html86
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/stl.html150
-rw-r--r--mediagoblin/templates/mediagoblin/media_displays/video.html74
-rw-r--r--mediagoblin/templates/mediagoblin/root.html38
-rw-r--r--mediagoblin/templates/mediagoblin/submit/collection.html34
-rw-r--r--mediagoblin/templates/mediagoblin/submit/start.html38
-rw-r--r--mediagoblin/templates/mediagoblin/test_submit.html34
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection.html72
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html53
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html59
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/collection_list.html56
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/comment_email.txt26
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/gallery.html63
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media.html205
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media_collect.html73
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html54
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/processing_panel.html109
-rw-r--r--mediagoblin/templates/mediagoblin/user_pages/user.html171
-rw-r--r--mediagoblin/templates/mediagoblin/utils/collection_gallery.html90
-rw-r--r--mediagoblin/templates/mediagoblin/utils/collections.html44
-rw-r--r--mediagoblin/templates/mediagoblin/utils/exif.html67
-rw-r--r--mediagoblin/templates/mediagoblin/utils/feed_link.html23
-rw-r--r--mediagoblin/templates/mediagoblin/utils/license.html28
-rw-r--r--mediagoblin/templates/mediagoblin/utils/messages.html28
-rw-r--r--mediagoblin/templates/mediagoblin/utils/object_gallery.html76
-rw-r--r--mediagoblin/templates/mediagoblin/utils/pagination.html65
-rw-r--r--mediagoblin/templates/mediagoblin/utils/prev_next.html48
-rw-r--r--mediagoblin/templates/mediagoblin/utils/profile.html30
-rw-r--r--mediagoblin/templates/mediagoblin/utils/tags.html46
-rw-r--r--mediagoblin/templates/mediagoblin/utils/wtforms.html76
-rw-r--r--mediagoblin/templates/mediagoblin/webfinger/host-meta.xml27
-rw-r--r--mediagoblin/templates/mediagoblin/webfinger/xrd.xml27
-rw-r--r--mediagoblin/tests/__init__.py22
-rw-r--r--mediagoblin/tests/appconfig_context_modified.ini26
-rw-r--r--mediagoblin/tests/appconfig_plugin_specs.ini21
-rw-r--r--mediagoblin/tests/appconfig_static_plugin.ini26
-rw-r--r--mediagoblin/tests/conftest.py41
-rw-r--r--mediagoblin/tests/fake_carrot_conf_bad.ini14
-rw-r--r--mediagoblin/tests/fake_carrot_conf_empty.ini0
-rw-r--r--mediagoblin/tests/fake_carrot_conf_good.ini13
-rw-r--r--mediagoblin/tests/fake_celery_conf.ini9
-rw-r--r--mediagoblin/tests/fake_celery_module.py15
-rw-r--r--mediagoblin/tests/fake_config_spec.ini10
-rw-r--r--mediagoblin/tests/pytest.ini2
-rw-r--r--mediagoblin/tests/resources.py41
-rw-r--r--mediagoblin/tests/test_api.py92
-rw-r--r--mediagoblin/tests/test_auth.py396
-rw-r--r--mediagoblin/tests/test_celery_setup.py60
-rw-r--r--mediagoblin/tests/test_collections.py32
-rw-r--r--mediagoblin/tests/test_config.py97
-rw-r--r--mediagoblin/tests/test_csrf_middleware.py86
-rw-r--r--mediagoblin/tests/test_edit.py144
-rw-r--r--mediagoblin/tests/test_exif.py431
-rw-r--r--mediagoblin/tests/test_exif/bad.jpg18
-rw-r--r--mediagoblin/tests/test_exif/empty.jpgbin0 -> 26636 bytes
-rw-r--r--mediagoblin/tests/test_exif/good.jpgbin0 -> 207590 bytes
-rw-r--r--mediagoblin/tests/test_exif/has-gps.jpgbin0 -> 141246 bytes
-rw-r--r--mediagoblin/tests/test_globals.py42
-rw-r--r--mediagoblin/tests/test_http_callback.py83
-rw-r--r--mediagoblin/tests/test_messages.py50
-rw-r--r--mediagoblin/tests/test_mgoblin_app.ini33
-rw-r--r--mediagoblin/tests/test_misc.py91
-rw-r--r--mediagoblin/tests/test_modelmethods.py167
-rw-r--r--mediagoblin/tests/test_oauth.py222
-rw-r--r--mediagoblin/tests/test_paste.ini40
-rw-r--r--mediagoblin/tests/test_pdf.py39
-rw-r--r--mediagoblin/tests/test_piwigo.py71
-rw-r--r--mediagoblin/tests/test_pluginapi.py466
-rw-r--r--mediagoblin/tests/test_processing.py18
-rw-r--r--mediagoblin/tests/test_session.py30
-rw-r--r--mediagoblin/tests/test_sql_migrations.py896
-rw-r--r--mediagoblin/tests/test_staticdirect.py9
-rw-r--r--mediagoblin/tests/test_storage.py321
-rw-r--r--mediagoblin/tests/test_submission.py294
-rw-r--r--mediagoblin/tests/test_submission/bigblue.pngbin0 -> 3142 bytes
-rwxr-xr-xmediagoblin/tests/test_submission/evil3
-rwxr-xr-xmediagoblin/tests/test_submission/evil.jpg3
-rwxr-xr-xmediagoblin/tests/test_submission/evil.png3
-rw-r--r--mediagoblin/tests/test_submission/good.jpgbin0 -> 10059 bytes
-rw-r--r--mediagoblin/tests/test_submission/good.pdfbin0 -> 194007 bytes
-rw-r--r--mediagoblin/tests/test_submission/good.pngbin0 -> 50598 bytes
-rw-r--r--mediagoblin/tests/test_tags.py39
-rw-r--r--mediagoblin/tests/test_timesince.py57
-rw-r--r--mediagoblin/tests/test_util.py145
-rw-r--r--mediagoblin/tests/test_workbench.py122
-rw-r--r--mediagoblin/tests/testplugins/__init__.py15
-rw-r--r--mediagoblin/tests/testplugins/callables1/__init__.py43
-rw-r--r--mediagoblin/tests/testplugins/callables2/__init__.py41
-rw-r--r--mediagoblin/tests/testplugins/callables3/__init__.py41
-rw-r--r--mediagoblin/tests/testplugins/modify_context/__init__.py55
-rw-r--r--mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html5
-rw-r--r--mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html6
-rw-r--r--mediagoblin/tests/testplugins/modify_context/views.py33
-rw-r--r--mediagoblin/tests/testplugins/pluginspec/__init__.py22
-rw-r--r--mediagoblin/tests/testplugins/pluginspec/config_spec.ini4
-rw-r--r--mediagoblin/tests/testplugins/staticstuff/__init__.py36
-rw-r--r--mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css4
-rw-r--r--mediagoblin/tests/testplugins/staticstuff/views.py28
-rw-r--r--mediagoblin/tests/tools.py233
-rw-r--r--mediagoblin/themes/airy/AGPLv3.txt661
-rw-r--r--mediagoblin/themes/airy/CC0_1.0.txt121
-rw-r--r--mediagoblin/themes/airy/assets/css/airy.css82
-rw-r--r--mediagoblin/themes/airy/assets/images/empty_dots.pngbin0 -> 205 bytes
-rw-r--r--mediagoblin/themes/airy/assets/images/icon_feed.pngbin0 -> 473 bytes
-rw-r--r--mediagoblin/themes/airy/assets/images/logo.pngbin0 -> 2596 bytes
-rw-r--r--mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html25
-rw-r--r--mediagoblin/themes/airy/templates/mediagoblin/extra_head.html20
-rw-r--r--mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html23
-rw-r--r--mediagoblin/themes/airy/theme.cfg4
-rw-r--r--mediagoblin/tools/__init__.py0
-rw-r--r--mediagoblin/tools/common.py73
-rw-r--r--mediagoblin/tools/crypto.py113
-rw-r--r--mediagoblin/tools/exif.py187
l---------mediagoblin/tools/extlib/EXIF.py1
-rw-r--r--mediagoblin/tools/extlib/__init__.py0
l---------mediagoblin/tools/extlib/wtf_html5.py1
-rw-r--r--mediagoblin/tools/files.py43
-rw-r--r--mediagoblin/tools/licenses.py69
-rw-r--r--mediagoblin/tools/mail.py150
-rw-r--r--mediagoblin/tools/pagination.py113
-rw-r--r--mediagoblin/tools/pluginapi.py367
-rw-r--r--mediagoblin/tools/processing.py87
-rw-r--r--mediagoblin/tools/request.py38
-rw-r--r--mediagoblin/tools/response.py108
-rw-r--r--mediagoblin/tools/routing.py67
-rw-r--r--mediagoblin/tools/session.py68
-rw-r--r--mediagoblin/tools/staticdirect.py101
-rw-r--r--mediagoblin/tools/template.py160
-rw-r--r--mediagoblin/tools/testing.py45
-rw-r--r--mediagoblin/tools/text.py124
-rw-r--r--mediagoblin/tools/theme.py89
-rw-r--r--mediagoblin/tools/timesince.py95
-rw-r--r--mediagoblin/tools/translate.py211
-rw-r--r--mediagoblin/tools/url.py44
-rw-r--r--mediagoblin/tools/workbench.py164
-rw-r--r--mediagoblin/user_pages/__init__.py15
-rw-r--r--mediagoblin/user_pages/forms.py51
-rw-r--r--mediagoblin/user_pages/lib.py77
-rw-r--r--mediagoblin/user_pages/routing.py91
-rw-r--r--mediagoblin/user_pages/views.py618
-rw-r--r--mediagoblin/views.py46
-rw-r--r--mediagoblin/webfinger/__init__.py25
-rw-r--r--mediagoblin/webfinger/routing.py23
-rw-r--r--mediagoblin/webfinger/views.py117
407 files changed, 70741 insertions, 0 deletions
diff --git a/mediagoblin/__init__.py b/mediagoblin/__init__.py
new file mode 100644
index 00000000..88dedd28
--- /dev/null
+++ b/mediagoblin/__init__.py
@@ -0,0 +1,17 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin._version import __version__
diff --git a/mediagoblin/_version.py b/mediagoblin/_version.py
new file mode 100644
index 00000000..2abc105f
--- /dev/null
+++ b/mediagoblin/_version.py
@@ -0,0 +1,26 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# valid version formats:
+# * x.y - final release
+# * x.ya1 - alpha 1
+# * x.yb1 - beta 1
+# * x.yrc1 - release candidate 1
+# * x.y.dev - dev
+
+# see http://www.python.org/dev/peps/pep-0386/
+
+__version__ = "0.4.1.dev"
diff --git a/mediagoblin/admin/__init__.py b/mediagoblin/admin/__init__.py
new file mode 100644
index 00000000..719b56e7
--- /dev/null
+++ b/mediagoblin/admin/__init__.py
@@ -0,0 +1,16 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
diff --git a/mediagoblin/admin/routing.py b/mediagoblin/admin/routing.py
new file mode 100644
index 00000000..29515f12
--- /dev/null
+++ b/mediagoblin/admin/routing.py
@@ -0,0 +1,20 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+admin_routes = [
+ ('mediagoblin.admin.panel',
+ '/panel',
+ 'mediagoblin.admin.views:admin_processing_panel')]
diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py
new file mode 100644
index 00000000..22ca74a3
--- /dev/null
+++ b/mediagoblin/admin/views.py
@@ -0,0 +1,48 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from werkzeug.exceptions import Forbidden
+
+from mediagoblin.db.models import MediaEntry
+from mediagoblin.decorators import require_active_login
+from mediagoblin.tools.response import render_to_response
+
+@require_active_login
+def admin_processing_panel(request):
+ '''
+ Show the global processing panel for this instance
+ '''
+ # TODO: Why not a "require_admin_login" decorator throwing a 403 exception?
+ if not request.user.is_admin:
+ raise Forbidden()
+
+ processing_entries = MediaEntry.query.filter_by(state = u'processing').\
+ order_by(MediaEntry.created.desc())
+
+ # Get media entries which have failed to process
+ failed_entries = MediaEntry.query.filter_by(state = u'failed').\
+ order_by(MediaEntry.created.desc())
+
+ processed_entries = MediaEntry.query.filter_by(state = u'processed').\
+ order_by(MediaEntry.created.desc()).limit(10)
+
+ # Render to response
+ return render_to_response(
+ request,
+ 'mediagoblin/admin/panel.html',
+ {'processing_entries': processing_entries,
+ 'failed_entries': failed_entries,
+ 'processed_entries': processed_entries})
diff --git a/mediagoblin/app.py b/mediagoblin/app.py
new file mode 100644
index 00000000..1984ce77
--- /dev/null
+++ b/mediagoblin/app.py
@@ -0,0 +1,268 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+from mediagoblin.routing import get_url_map
+from mediagoblin.tools.routing import endpoint_to_controller
+
+from werkzeug.wrappers import Request
+from werkzeug.exceptions import HTTPException
+from werkzeug.routing import RequestRedirect
+
+from mediagoblin import meddleware, __version__
+from mediagoblin.tools import common, session, translate, template
+from mediagoblin.tools.response import render_http_exception
+from mediagoblin.tools.theme import register_themes
+from mediagoblin.tools import request as mg_request
+from mediagoblin.mg_globals import setup_globals
+from mediagoblin.init.celery import setup_celery_from_config
+from mediagoblin.init.plugins import setup_plugins
+from mediagoblin.init import (get_jinja_loader, get_staticdirector,
+ setup_global_and_app_config, setup_locales, setup_workbench, setup_database,
+ setup_storage)
+from mediagoblin.tools.pluginapi import PluginManager, hook_transform
+from mediagoblin.tools.crypto import setup_crypto
+
+
+_log = logging.getLogger(__name__)
+
+
+class MediaGoblinApp(object):
+ """
+ WSGI application of MediaGoblin
+
+ ... this is the heart of the program!
+ """
+ def __init__(self, config_path, setup_celery=True):
+ """
+ Initialize the application based on a configuration file.
+
+ Arguments:
+ - config_path: path to the configuration file we're opening.
+ - setup_celery: whether or not to setup celery during init.
+ (Note: setting 'celery_setup_elsewhere' also disables
+ setting up celery.)
+ """
+ _log.info("GNU MediaGoblin %s main server starting", __version__)
+ _log.debug("Using config file %s", config_path)
+ ##############
+ # Setup config
+ ##############
+
+ # Open and setup the config
+ global_config, app_config = setup_global_and_app_config(config_path)
+
+ setup_crypto()
+
+ ##########################################
+ # Setup other connections / useful objects
+ ##########################################
+
+ # Setup Session Manager, not needed in celery
+ self.session_manager = session.SessionManager()
+
+ # load all available locales
+ setup_locales()
+
+ # Set up plugins -- need to do this early so that plugins can
+ # affect startup.
+ _log.info("Setting up plugins.")
+ setup_plugins()
+
+ # Set up the database
+ self.db = setup_database()
+
+ # Register themes
+ self.theme_registry, self.current_theme = register_themes(app_config)
+
+ # Get the template environment
+ self.template_loader = get_jinja_loader(
+ app_config.get('local_templates'),
+ self.current_theme,
+ PluginManager().get_template_paths()
+ )
+
+ # Set up storage systems
+ self.public_store, self.queue_store = setup_storage()
+
+ # set up routing
+ self.url_map = get_url_map()
+
+ # set up staticdirector tool
+ self.staticdirector = get_staticdirector(app_config)
+
+ # Setup celery, if appropriate
+ if setup_celery and not app_config.get('celery_setup_elsewhere'):
+ if os.environ.get('CELERY_ALWAYS_EAGER', 'false').lower() == 'true':
+ setup_celery_from_config(
+ app_config, global_config,
+ force_celery_always_eager=True)
+ else:
+ setup_celery_from_config(app_config, global_config)
+
+ #######################################################
+ # Insert appropriate things into mediagoblin.mg_globals
+ #
+ # certain properties need to be accessed globally eg from
+ # validators, etc, which might not access to the request
+ # object.
+ #######################################################
+
+ setup_globals(app=self)
+
+ # Workbench *currently* only used by celery, so this only
+ # matters in always eager mode :)
+ setup_workbench()
+
+ # instantiate application meddleware
+ self.meddleware = [common.import_component(m)(self)
+ for m in meddleware.ENABLED_MEDDLEWARE]
+
+ def call_backend(self, environ, start_response):
+ request = Request(environ)
+
+ # Compatibility with django, use request.args preferrably
+ request.GET = request.args
+
+ ## Routing / controller loading stuff
+ map_adapter = self.url_map.bind_to_environ(request.environ)
+
+ # By using fcgi, mediagoblin can run under a base path
+ # like /mediagoblin/. request.path_info contains the
+ # path inside mediagoblin. If the something needs the
+ # full path of the current page, that should include
+ # the basepath.
+ # Note: urlgen and routes are fine!
+ request.full_path = environ["SCRIPT_NAME"] + request.path
+ # python-routes uses SCRIPT_NAME. So let's use that too.
+ # The other option would be:
+ # request.full_path = environ["SCRIPT_URL"]
+
+ # Fix up environ for urlgen
+ # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off
+ if environ.get('HTTPS', '').lower() == 'off':
+ environ.pop('HTTPS')
+
+ ## Attach utilities to the request object
+ # Do we really want to load this via middleware? Maybe?
+ session_manager = self.session_manager
+ request.session = session_manager.load_session_from_cookie(request)
+ # Attach self as request.app
+ # Also attach a few utilities from request.app for convenience?
+ request.app = self
+
+ request.db = self.db
+ request.staticdirect = self.staticdirector
+
+ request.locale = translate.get_locale_from_request(request)
+ request.template_env = template.get_jinja_env(
+ self.template_loader, request.locale)
+
+ def build_proxy(endpoint, **kw):
+ try:
+ qualified = kw.pop('qualified')
+ except KeyError:
+ qualified = False
+
+ return map_adapter.build(
+ endpoint,
+ values=dict(**kw),
+ force_external=qualified)
+
+ request.urlgen = build_proxy
+
+ mg_request.setup_user_in_request(request)
+
+ request.controller_name = None
+ try:
+ found_rule, url_values = map_adapter.match(return_rule=True)
+ request.matchdict = url_values
+ except RequestRedirect as response:
+ # Deal with 301 responses eg due to missing final slash
+ return response(environ, start_response)
+ except HTTPException as exc:
+ # Stop and render exception
+ return render_http_exception(
+ request, exc,
+ exc.get_description(environ))(environ, start_response)
+
+ controller = endpoint_to_controller(found_rule)
+ # Make a reference to the controller's symbolic name on the request...
+ # used for lazy context modification
+ request.controller_name = found_rule.endpoint
+
+ # pass the request through our meddleware classes
+ try:
+ for m in self.meddleware:
+ response = m.process_request(request, controller)
+ if response is not None:
+ return response(environ, start_response)
+ except HTTPException as e:
+ return render_http_exception(
+ request, e,
+ e.get_description(environ))(environ, start_response)
+
+ request.start_response = start_response
+
+ # get the Http response from the controller
+ try:
+ response = controller(request)
+ except HTTPException as e:
+ response = render_http_exception(
+ request, e, e.get_description(environ))
+
+ # pass the response through the meddlewares
+ try:
+ for m in self.meddleware[::-1]:
+ m.process_response(request, response)
+ except HTTPException as e:
+ response = render_http_exception(
+ request, e, e.get_description(environ))
+
+ session_manager.save_session_to_cookie(request.session,
+ request, response)
+
+ return response(environ, start_response)
+
+ def __call__(self, environ, start_response):
+ ## If more errors happen that look like unclean sessions:
+ # self.db.check_session_clean()
+
+ try:
+ return self.call_backend(environ, start_response)
+ finally:
+ # Reset the sql session, so that the next request
+ # gets a fresh session
+ self.db.reset_after_request()
+
+
+def paste_app_factory(global_config, **app_config):
+ configs = app_config['config'].split()
+ mediagoblin_config = None
+ for config in configs:
+ if os.path.exists(config) and os.access(config, os.R_OK):
+ mediagoblin_config = config
+ break
+
+ if not mediagoblin_config:
+ raise IOError("Usable mediagoblin config not found.")
+
+ mgoblin_app = MediaGoblinApp(mediagoblin_config)
+ mgoblin_app = hook_transform('wrap_wsgi', mgoblin_app)
+
+ return mgoblin_app
diff --git a/mediagoblin/auth/__init__.py b/mediagoblin/auth/__init__.py
new file mode 100644
index 00000000..621845ba
--- /dev/null
+++ b/mediagoblin/auth/__init__.py
@@ -0,0 +1,15 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py
new file mode 100644
index 00000000..0a391d67
--- /dev/null
+++ b/mediagoblin/auth/forms.py
@@ -0,0 +1,66 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import wtforms
+
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+from mediagoblin.auth.tools import normalize_user_or_email_field
+
+
+class RegistrationForm(wtforms.Form):
+ username = wtforms.TextField(
+ _('Username'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_email=False)])
+ password = wtforms.PasswordField(
+ _('Password'),
+ [wtforms.validators.Required(),
+ wtforms.validators.Length(min=5, max=1024)])
+ email = wtforms.TextField(
+ _('Email address'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field(allow_user=False)])
+
+
+class LoginForm(wtforms.Form):
+ username = wtforms.TextField(
+ _('Username or Email'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field()])
+ password = wtforms.PasswordField(
+ _('Password'),
+ [wtforms.validators.Required(),
+ wtforms.validators.Length(min=5, max=1024)])
+
+
+class ForgotPassForm(wtforms.Form):
+ username = wtforms.TextField(
+ _('Username or email'),
+ [wtforms.validators.Required(),
+ normalize_user_or_email_field()])
+
+
+class ChangePassForm(wtforms.Form):
+ password = wtforms.PasswordField(
+ 'Password',
+ [wtforms.validators.Required(),
+ wtforms.validators.Length(min=5, max=1024)])
+ userid = wtforms.HiddenField(
+ '',
+ [wtforms.validators.Required()])
+ token = wtforms.HiddenField(
+ '',
+ [wtforms.validators.Required()])
diff --git a/mediagoblin/auth/lib.py b/mediagoblin/auth/lib.py
new file mode 100644
index 00000000..bfc36b28
--- /dev/null
+++ b/mediagoblin/auth/lib.py
@@ -0,0 +1,120 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import random
+
+import bcrypt
+
+from mediagoblin.tools.mail import send_email
+from mediagoblin.tools.template import render_template
+from mediagoblin import mg_globals
+
+
+def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None):
+ """
+ Check to see if this password matches.
+
+ Args:
+ - raw_pass: user submitted password to check for authenticity.
+ - stored_hash: The hash of the raw password (and possibly extra
+ salt) to check against
+ - extra_salt: (optional) If this password is with stored with a
+ non-database extra salt (probably in the config file) for extra
+ security, factor this into the check.
+
+ Returns:
+ True or False depending on success.
+ """
+ if extra_salt:
+ raw_pass = u"%s:%s" % (extra_salt, raw_pass)
+
+ hashed_pass = bcrypt.hashpw(raw_pass.encode('utf-8'), stored_hash)
+
+ # Reduce risk of timing attacks by hashing again with a random
+ # number (thx to zooko on this advice, which I hopefully
+ # incorporated right.)
+ #
+ # See also:
+ rand_salt = bcrypt.gensalt(5)
+ randplus_stored_hash = bcrypt.hashpw(stored_hash, rand_salt)
+ randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
+
+ return randplus_stored_hash == randplus_hashed_pass
+
+
+def bcrypt_gen_password_hash(raw_pass, extra_salt=None):
+ """
+ Generate a salt for this new password.
+
+ Args:
+ - raw_pass: user submitted password
+ - extra_salt: (optional) If this password is with stored with a
+ non-database extra salt
+ """
+ if extra_salt:
+ raw_pass = u"%s:%s" % (extra_salt, raw_pass)
+
+ return unicode(
+ bcrypt.hashpw(raw_pass.encode('utf-8'), bcrypt.gensalt()))
+
+
+def fake_login_attempt():
+ """
+ Pretend we're trying to login.
+
+ Nothing actually happens here, we're just trying to take up some
+ time, approximately the same amount of time as
+ bcrypt_check_password, so as to avoid figuring out what users are
+ on the system by intentionally faking logins a bunch of times.
+ """
+ rand_salt = bcrypt.gensalt(5)
+
+ hashed_pass = bcrypt.hashpw(str(random.random()), rand_salt)
+
+ randplus_stored_hash = bcrypt.hashpw(str(random.random()), rand_salt)
+ randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt)
+
+ randplus_stored_hash == randplus_hashed_pass
+
+
+EMAIL_FP_VERIFICATION_TEMPLATE = (
+ u"http://{host}{uri}?"
+ u"userid={userid}&token={fp_verification_key}")
+
+
+def send_fp_verification_email(user, request):
+ """
+ Send the verification email to users to change their password.
+
+ Args:
+ - user: a user object
+ - request: the request
+ """
+ rendered_email = render_template(
+ request, 'mediagoblin/auth/fp_verification_email.txt',
+ {'username': user.username,
+ 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format(
+ host=request.host,
+ uri=request.urlgen('mediagoblin.auth.verify_forgot_password'),
+ userid=unicode(user.id),
+ fp_verification_key=user.fp_verification_key)})
+
+ # TODO: There is no error handling in place
+ send_email(
+ mg_globals.app_config['email_sender_address'],
+ [user.email],
+ 'GNU MediaGoblin - Change forgotten password!',
+ rendered_email)
diff --git a/mediagoblin/auth/routing.py b/mediagoblin/auth/routing.py
new file mode 100644
index 00000000..2a6abb47
--- /dev/null
+++ b/mediagoblin/auth/routing.py
@@ -0,0 +1,33 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+auth_routes = [
+ ('mediagoblin.auth.register', '/register/',
+ 'mediagoblin.auth.views:register'),
+ ('mediagoblin.auth.login', '/login/',
+ 'mediagoblin.auth.views:login'),
+ ('mediagoblin.auth.logout', '/logout/',
+ 'mediagoblin.auth.views:logout'),
+ ('mediagoblin.auth.verify_email', '/verify_email/',
+ 'mediagoblin.auth.views:verify_email'),
+ ('mediagoblin.auth.resend_verification', '/resend_verification/',
+ 'mediagoblin.auth.views:resend_activation'),
+ ('mediagoblin.auth.forgot_password', '/forgot_password/',
+ 'mediagoblin.auth.views:forgot_password'),
+ ('mediagoblin.auth.verify_forgot_password',
+ '/forgot_password/verify/',
+ 'mediagoblin.auth.views:verify_forgot_password')]
diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py
new file mode 100644
index 00000000..db6b6e37
--- /dev/null
+++ b/mediagoblin/auth/tools.py
@@ -0,0 +1,159 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import uuid
+import logging
+
+import wtforms
+from sqlalchemy import or_
+
+from mediagoblin import mg_globals
+from mediagoblin.auth import lib as auth_lib
+from mediagoblin.db.models import User
+from mediagoblin.tools.mail import (normalize_email, send_email,
+ email_debug_message)
+from mediagoblin.tools.template import render_template
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+_log = logging.getLogger(__name__)
+
+
+def normalize_user_or_email_field(allow_email=True, allow_user=True):
+ """
+ Check if we were passed a field that matches a username and/or email
+ pattern.
+
+ This is useful for fields that can take either a username or email
+ address. Use the parameters if you want to only allow a username for
+ instance"""
+ message = _(u'Invalid User name or email address.')
+ nomail_msg = _(u"This field does not take email addresses.")
+ nouser_msg = _(u"This field requires an email address.")
+
+ def _normalize_field(form, field):
+ email = u'@' in field.data
+ if email: # normalize email address casing
+ if not allow_email:
+ raise wtforms.ValidationError(nomail_msg)
+ wtforms.validators.Email()(form, field)
+ field.data = normalize_email(field.data)
+ else: # lower case user names
+ if not allow_user:
+ raise wtforms.ValidationError(nouser_msg)
+ wtforms.validators.Length(min=3, max=30)(form, field)
+ wtforms.validators.Regexp(r'^\w+$')(form, field)
+ field.data = field.data.lower()
+ if field.data is None: # should not happen, but be cautious anyway
+ raise wtforms.ValidationError(message)
+ return _normalize_field
+
+
+EMAIL_VERIFICATION_TEMPLATE = (
+ u"http://{host}{uri}?"
+ u"userid={userid}&token={verification_key}")
+
+
+def send_verification_email(user, request):
+ """
+ Send the verification email to users to activate their accounts.
+
+ Args:
+ - user: a user object
+ - request: the request
+ """
+ rendered_email = render_template(
+ request, 'mediagoblin/auth/verification_email.txt',
+ {'username': user.username,
+ 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format(
+ host=request.host,
+ uri=request.urlgen('mediagoblin.auth.verify_email'),
+ userid=unicode(user.id),
+ verification_key=user.verification_key)})
+
+ # TODO: There is no error handling in place
+ send_email(
+ mg_globals.app_config['email_sender_address'],
+ [user.email],
+ # TODO
+ # Due to the distributed nature of GNU MediaGoblin, we should
+ # find a way to send some additional information about the
+ # specific GNU MediaGoblin instance in the subject line. For
+ # example "GNU MediaGoblin @ Wandborg - [...]".
+ 'GNU MediaGoblin - Verify your email!',
+ rendered_email)
+
+
+def basic_extra_validation(register_form, *args):
+ users_with_username = User.query.filter_by(
+ username=register_form.data['username']).count()
+ users_with_email = User.query.filter_by(
+ email=register_form.data['email']).count()
+
+ extra_validation_passes = True
+
+ if users_with_username:
+ register_form.username.errors.append(
+ _(u'Sorry, a user with that name already exists.'))
+ extra_validation_passes = False
+ if users_with_email:
+ register_form.email.errors.append(
+ _(u'Sorry, a user with that email address already exists.'))
+ extra_validation_passes = False
+
+ return extra_validation_passes
+
+
+def register_user(request, register_form):
+ """ Handle user registration """
+ extra_validation_passes = basic_extra_validation(register_form)
+
+ if extra_validation_passes:
+ # Create the user
+ user = User()
+ user.username = register_form.data['username']
+ user.email = register_form.data['email']
+ user.pw_hash = auth_lib.bcrypt_gen_password_hash(
+ register_form.password.data)
+ user.verification_key = unicode(uuid.uuid4())
+ user.save()
+
+ # log the user in
+ request.session['user_id'] = unicode(user.id)
+ request.session.save()
+
+ # send verification email
+ email_debug_message(request)
+ send_verification_email(user, request)
+
+ return user
+
+ return None
+
+
+def check_login_simple(username, password, username_might_be_email=False):
+ search = (User.username == username)
+ if username_might_be_email and ('@' in username):
+ search = or_(search, User.email == username)
+ user = User.query.filter(search).first()
+ if not user:
+ _log.info("User %r not found", username)
+ auth_lib.fake_login_attempt()
+ return None
+ if not auth_lib.bcrypt_check_password(password, user.pw_hash):
+ _log.warn("Wrong password for %r", username)
+ return None
+ _log.info("Logging %r in", username)
+ return user
diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py
new file mode 100644
index 00000000..bb7bda77
--- /dev/null
+++ b/mediagoblin/auth/views.py
@@ -0,0 +1,319 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import uuid
+import datetime
+
+from mediagoblin import messages, mg_globals
+from mediagoblin.db.models import User
+from mediagoblin.tools.response import render_to_response, redirect, render_404
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.tools.mail import email_debug_message
+from mediagoblin.auth import lib as auth_lib
+from mediagoblin.auth import forms as auth_forms
+from mediagoblin.auth.lib import send_fp_verification_email
+from mediagoblin.auth.tools import (send_verification_email, register_user,
+ check_login_simple)
+
+
+def register(request):
+ """The registration view.
+
+ Note that usernames will always be lowercased. Email domains are lowercased while
+ the first part remains case-sensitive.
+ """
+ # Redirects to indexpage if registrations are disabled
+ if not mg_globals.app_config["allow_registration"]:
+ messages.add_message(
+ request,
+ messages.WARNING,
+ _('Sorry, registration is disabled on this instance.'))
+ return redirect(request, "index")
+
+ register_form = auth_forms.RegistrationForm(request.form)
+
+ if request.method == 'POST' and register_form.validate():
+ # TODO: Make sure the user doesn't exist already
+ user = register_user(request, register_form)
+
+ if user:
+ # redirect the user to their homepage... there will be a
+ # message waiting for them to verify their email
+ return redirect(
+ request, 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/auth/register.html',
+ {'register_form': register_form})
+
+
+def login(request):
+ """
+ MediaGoblin login view.
+
+ If you provide the POST with 'next', it'll redirect to that view.
+ """
+ login_form = auth_forms.LoginForm(request.form)
+
+ login_failed = False
+
+ if request.method == 'POST':
+
+ username = login_form.data['username']
+
+ if login_form.validate():
+ user = check_login_simple(username, login_form.password.data, True)
+
+ if user:
+ # set up login in session
+ request.session['user_id'] = unicode(user.id)
+ request.session.save()
+
+ if request.form.get('next'):
+ return redirect(request, location=request.form['next'])
+ else:
+ return redirect(request, "index")
+
+ login_failed = True
+
+ return render_to_response(
+ request,
+ 'mediagoblin/auth/login.html',
+ {'login_form': login_form,
+ 'next': request.GET.get('next') or request.form.get('next'),
+ 'login_failed': login_failed,
+ 'allow_registration': mg_globals.app_config["allow_registration"]})
+
+
+def logout(request):
+ # Maybe deleting the user_id parameter would be enough?
+ request.session.delete()
+
+ return redirect(request, "index")
+
+
+def verify_email(request):
+ """
+ Email verification view
+
+ validates GET parameters against database and unlocks the user account, if
+ you are lucky :)
+ """
+ # If we don't have userid and token parameters, we can't do anything; 404
+ if not 'userid' in request.GET or not 'token' in request.GET:
+ return render_404(request)
+
+ user = User.query.filter_by(id=request.args['userid']).first()
+
+ if user and user.verification_key == unicode(request.GET['token']):
+ user.status = u'active'
+ user.email_verified = True
+ user.verification_key = None
+
+ user.save()
+
+ messages.add_message(
+ request,
+ messages.SUCCESS,
+ _("Your email address has been verified. "
+ "You may now login, edit your profile, and submit images!"))
+ else:
+ messages.add_message(
+ request,
+ messages.ERROR,
+ _('The verification key or user id is incorrect'))
+
+ return redirect(
+ request, 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+
+def resend_activation(request):
+ """
+ The reactivation view
+
+ Resend the activation email.
+ """
+
+ if request.user is None:
+ messages.add_message(
+ request,
+ messages.ERROR,
+ _('You must be logged in so we know who to send the email to!'))
+
+ return redirect(request, 'mediagoblin.auth.login')
+
+ if request.user.email_verified:
+ messages.add_message(
+ request,
+ messages.ERROR,
+ _("You've already verified your email address!"))
+
+ return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username'])
+
+ request.user.verification_key = unicode(uuid.uuid4())
+ request.user.save()
+
+ email_debug_message(request)
+ send_verification_email(request.user, request)
+
+ messages.add_message(
+ request,
+ messages.INFO,
+ _('Resent your verification email.'))
+ return redirect(
+ request, 'mediagoblin.user_pages.user_home',
+ user=request.user.username)
+
+
+def forgot_password(request):
+ """
+ Forgot password view
+
+ Sends an email with an url to renew forgotten password.
+ Use GET querystring parameter 'username' to pre-populate the input field
+ """
+ fp_form = auth_forms.ForgotPassForm(request.form,
+ username=request.args.get('username'))
+
+ if not (request.method == 'POST' and fp_form.validate()):
+ # Either GET request, or invalid form submitted. Display the template
+ return render_to_response(request,
+ 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form})
+
+ # If we are here: method == POST and form is valid. username casing
+ # has been sanitized. Store if a user was found by email. We should
+ # not reveal if the operation was successful then as we don't want to
+ # leak if an email address exists in the system.
+ found_by_email = '@' in fp_form.username.data
+
+ if found_by_email:
+ user = User.query.filter_by(
+ email = fp_form.username.data).first()
+ # Don't reveal success in case the lookup happened by email address.
+ success_message=_("If that email address (case sensitive!) is "
+ "registered an email has been sent with instructions "
+ "on how to change your password.")
+
+ else: # found by username
+ user = User.query.filter_by(
+ username = fp_form.username.data).first()
+
+ if user is None:
+ messages.add_message(request,
+ messages.WARNING,
+ _("Couldn't find someone with that username."))
+ return redirect(request, 'mediagoblin.auth.forgot_password')
+
+ success_message=_("An email has been sent with instructions "
+ "on how to change your password.")
+
+ if user and not(user.email_verified and user.status == 'active'):
+ # Don't send reminder because user is inactive or has no verified email
+ messages.add_message(request,
+ messages.WARNING,
+ _("Could not send password recovery email as your username is in"
+ "active or your account's email address has not been verified."))
+
+ return redirect(request, 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+ # SUCCESS. Send reminder and return to login page
+ if user:
+ user.fp_verification_key = unicode(uuid.uuid4())
+ user.fp_token_expire = datetime.datetime.now() + \
+ datetime.timedelta(days=10)
+ user.save()
+
+ email_debug_message(request)
+ send_fp_verification_email(user, request)
+
+ messages.add_message(request, messages.INFO, success_message)
+ return redirect(request, 'mediagoblin.auth.login')
+
+
+def verify_forgot_password(request):
+ """
+ Check the forgot-password verification and possibly let the user
+ change their password because of it.
+ """
+ # get form data variables, and specifically check for presence of token
+ formdata = _process_for_token(request)
+ if not formdata['has_userid_and_token']:
+ return render_404(request)
+
+ formdata_token = formdata['vars']['token']
+ formdata_userid = formdata['vars']['userid']
+ formdata_vars = formdata['vars']
+
+ # check if it's a valid user id
+ user = User.query.filter_by(id=formdata_userid).first()
+ if not user:
+ return render_404(request)
+
+ # check if we have a real user and correct token
+ if ((user and user.fp_verification_key and
+ user.fp_verification_key == unicode(formdata_token) and
+ datetime.datetime.now() < user.fp_token_expire
+ and user.email_verified and user.status == 'active')):
+
+ cp_form = auth_forms.ChangePassForm(formdata_vars)
+
+ if request.method == 'POST' and cp_form.validate():
+ user.pw_hash = auth_lib.bcrypt_gen_password_hash(
+ cp_form.password.data)
+ user.fp_verification_key = None
+ user.fp_token_expire = None
+ user.save()
+
+ messages.add_message(
+ request,
+ messages.INFO,
+ _("You can now log in using your new password."))
+ return redirect(request, 'mediagoblin.auth.login')
+ else:
+ return render_to_response(
+ request,
+ 'mediagoblin/auth/change_fp.html',
+ {'cp_form': cp_form})
+
+ # in case there is a valid id but no user with that id in the db
+ # or the token expired
+ else:
+ return render_404(request)
+
+
+def _process_for_token(request):
+ """
+ Checks for tokens in formdata without prior knowledge of request method
+
+ For now, returns whether the userid and token formdata variables exist, and
+ the formdata variables in a hash. Perhaps an object is warranted?
+ """
+ # retrieve the formdata variables
+ if request.method == 'GET':
+ formdata_vars = request.GET
+ else:
+ formdata_vars = request.form
+
+ formdata = {
+ 'vars': formdata_vars,
+ 'has_userid_and_token':
+ 'userid' in formdata_vars and 'token' in formdata_vars}
+
+ return formdata
diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini
new file mode 100644
index 00000000..b213970d
--- /dev/null
+++ b/mediagoblin/config_spec.ini
@@ -0,0 +1,191 @@
+[mediagoblin]
+# HTML title of the pages
+html_title = string(default="GNU MediaGoblin")
+
+# link to source for this MediaGoblin site
+source_link = string(default="https://gitorious.org/mediagoblin/mediagoblin")
+
+# Enabled media types
+media_types = string_list(default=list("mediagoblin.media_types.image"))
+
+# database stuff
+sql_engine = string(default="sqlite:///%(here)s/mediagoblin.db")
+
+# Where temporary files used in processing and etc are kept
+workbench_path = string(default="%(here)s/user_dev/media/workbench")
+
+# Where to store cryptographic sensible data
+crypto_path = string(default="%(here)s/user_dev/crypto")
+
+# Where mediagoblin-builtin static assets are kept
+direct_remote_path = string(default="/mgoblin_static/")
+
+# set to false to enable sending notices
+email_debug_mode = boolean(default=True)
+email_sender_address = string(default="notice@mediagoblin.example.org")
+email_smtp_host = string(default='')
+email_smtp_port = integer(default=25)
+email_smtp_user = string(default=None)
+email_smtp_pass = string(default=None)
+
+# Set to false to disable registrations
+allow_registration = boolean(default=True)
+
+# tag parsing
+tags_max_length = integer(default=255)
+
+# Enable/disable comments
+allow_comments = boolean(default=True)
+
+# Whether comments are ascending or descending
+comments_ascending = boolean(default=True)
+
+# By default not set, but you might want something like:
+# "%(here)s/user_dev/templates/"
+local_templates = string()
+
+# Whether or not celery is set up via an environment variable or
+# something else (and thus mediagoblin should not attempt to set it up
+# itself)
+celery_setup_elsewhere = boolean(default=False)
+
+# Whether or not users are able to upload files of any filetype with
+# their media entries -- This is useful if you want to provide the
+# source files for a media file but can also be a HUGE security risk.
+allow_attachments = boolean(default=False)
+
+# Cookie stuff
+csrf_cookie_name = string(default='mediagoblin_csrftoken')
+
+# Push stuff
+push_urls = string_list(default=list())
+
+exif_visible = boolean(default=False)
+original_date_visible = boolean(default=False)
+
+# Theming stuff
+theme_install_dir = string(default="%(here)s/user_dev/themes/")
+theme_web_path = string(default="/theme_static/")
+theme_linked_assets_dir = string(default="%(here)s/user_dev/theme_static/")
+theme = string()
+
+# plugin default assets directory
+plugin_web_path = string(default="/plugin_static/")
+plugin_linked_assets_dir = string(default="%(here)s/user_dev/plugin_static/")
+
+
+[storage:publicstore]
+storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage")
+base_dir = string(default="%(here)s/user_dev/media/public")
+base_url = string(default="/mgoblin_media/")
+
+[storage:queuestore]
+storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage")
+base_dir = string(default="%(here)s/user_dev/media/queue")
+
+[media:medium]
+# Dimensions used when creating media display images.
+max_width = integer(default=640)
+max_height = integer(default=640)
+
+[media:thumb]
+# Dimensions used when creating media thumbnails
+# This is unfortunately not implemented in the media
+# types yet. You can help!
+# TODO: Make plugins follow the media size settings
+max_width = integer(default=180)
+max_height = integer(default=180)
+
+[media_type:mediagoblin.media_types.image]
+# One of BICUBIC, BILINEAR, NEAREST, ANTIALIAS
+resize_filter = string(default="ANTIALIAS")
+#level of compression used when resizing images
+quality = integer(default=90)
+
+[media_type:mediagoblin.media_types.video]
+# Should we keep the original file?
+keep_original = boolean(default=False)
+
+# 0 means autodetect, autodetect means number_of_CPUs - 1
+vp8_threads = integer(default=0)
+# Range: 0..10
+vp8_quality = integer(default=8)
+# Range: -0.1..1
+vorbis_quality = float(default=0.3)
+
+# Autoplay the video when page is loaded?
+auto_play = boolean(default=True)
+
+[[skip_transcode]]
+mime_types = string_list(default=list("video/webm"))
+container_formats = string_list(default=list("Matroska"))
+video_codecs = string_list(default=list("VP8 video"))
+audio_codecs = string_list(default=list("Vorbis"))
+dimensions_match = boolean(default=True)
+
+[media_type:mediagoblin.media_types.audio]
+keep_original = boolean(default=True)
+# vorbisenc quality
+quality = float(default=0.3)
+create_spectrogram = boolean(default=True)
+spectrogram_fft_size = integer(default=4096)
+
+[media_type:mediagoblin.media_types.ascii]
+thumbnail_font = string(default=None)
+
+[media_type:mediagoblin.media_types.pdf]
+pdf_js = boolean(default=True)
+
+
+[celery]
+# default result stuff
+CELERY_RESULT_BACKEND = string(default="database")
+CELERY_RESULT_DBURI = string(default="sqlite:///%(here)s/celery.db")
+
+# default kombu stuff
+BROKER_TRANSPORT = string(default="sqlalchemy")
+BROKER_HOST = string(default="sqlite:///%(here)s/kombu.db")
+
+# known booleans
+CELERY_RESULT_PERSISTENT = boolean()
+CELERY_CREATE_MISSING_QUEUES = boolean()
+BROKER_USE_SSL = boolean()
+BROKER_CONNECTION_RETRY = boolean()
+CELERY_ALWAYS_EAGER = boolean()
+CELERY_EAGER_PROPAGATES_EXCEPTIONS = boolean()
+CELERY_IGNORE_RESULT = boolean()
+CELERY_TRACK_STARTED = boolean()
+CELERY_DISABLE_RATE_LIMITS = boolean()
+CELERY_ACKS_LATE = boolean()
+CELERY_STORE_ERRORS_EVEN_IF_IGNORED = boolean()
+CELERY_SEND_TASK_ERROR_EMAILS = boolean()
+CELERY_SEND_EVENTS = boolean()
+CELERY_SEND_TASK_SENT_EVENT = boolean()
+CELERYD_LOG_COLOR = boolean()
+CELERY_REDIRECT_STDOUTS = boolean()
+
+# known ints
+CELERYD_CONCURRENCY = integer()
+CELERYD_PREFETCH_MULTIPLIER = integer()
+CELERY_AMQP_TASK_RESULT_EXPIRES = integer()
+CELERY_AMQP_TASK_RESULT_CONNECTION_MAX = integer()
+REDIS_PORT = integer()
+REDIS_DB = integer()
+BROKER_PORT = integer()
+BROKER_CONNECTION_TIMEOUT = integer()
+CELERY_BROKER_CONNECTION_MAX_RETRIES = integer()
+CELERY_TASK_RESULT_EXPIRES = integer()
+CELERY_MAX_CACHED_RESULTS = integer()
+CELERY_DEFAULT_RATE_LIMIT = integer()
+CELERYD_MAX_TASKS_PER_CHILD = integer()
+CELERYD_TASK_TIME_LIMIT = integer()
+CELERYD_TASK_SOFT_TIME_LIMIT = integer()
+MAIL_PORT = integer()
+CELERYBEAT_MAX_LOOP_INTERVAL = integer()
+
+# known floats
+CELERYD_ETA_SCHEDULER_PRECISION = float()
+
+# known lists
+CELERY_ROUTES = string_list()
+CELERY_IMPORTS = string_list()
diff --git a/mediagoblin/db/__init__.py b/mediagoblin/db/__init__.py
new file mode 100644
index 00000000..27ca4b06
--- /dev/null
+++ b/mediagoblin/db/__init__.py
@@ -0,0 +1,49 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Database Abstraction/Wrapper Layer
+==================================
+
+This submodule is for most of the db specific stuff.
+
+There are two main ideas here:
+
+1. Open up a small possibility to replace mongo by another
+ db. This means, that all direct mongo accesses should
+ happen in the db submodule. While all the rest uses an
+ API defined by this submodule.
+
+ Currently this API happens to be basicly mongo.
+ Which means, that the abstraction/wrapper layer is
+ extremely thin.
+
+2. Give the rest of the app a simple and easy way to get most of
+ their db needs. Which often means some simple import
+ from db.util.
+
+What does that mean?
+
+* Never import mongo directly outside of this submodule.
+
+* Inside this submodule you can do whatever is needed. The
+ API border is exactly at the submodule layer. Nowhere
+ else.
+
+* helper functions can be moved in here. They become part
+ of the db.* API
+
+"""
diff --git a/mediagoblin/db/base.py b/mediagoblin/db/base.py
new file mode 100644
index 00000000..699a503a
--- /dev/null
+++ b/mediagoblin/db/base.py
@@ -0,0 +1,78 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import scoped_session, sessionmaker, object_session
+
+Session = scoped_session(sessionmaker())
+
+
+class GMGTableBase(object):
+ query = Session.query_property()
+
+ @classmethod
+ def find(cls, query_dict):
+ return cls.query.filter_by(**query_dict)
+
+ @classmethod
+ def find_one(cls, query_dict):
+ return cls.query.filter_by(**query_dict).first()
+
+ @classmethod
+ def one(cls, query_dict):
+ return cls.find(query_dict).one()
+
+ def get(self, key):
+ return getattr(self, key)
+
+ def setdefault(self, key, defaultvalue):
+ # The key *has* to exist on sql.
+ return getattr(self, key)
+
+ def save(self):
+ sess = object_session(self)
+ if sess is None:
+ sess = Session()
+ sess.add(self)
+ sess.commit()
+
+ def delete(self, commit=True):
+ """Delete the object and commit the change immediately by default"""
+ sess = object_session(self)
+ assert sess is not None, "Not going to delete detached %r" % self
+ sess.delete(self)
+ if commit:
+ sess.commit()
+
+
+Base = declarative_base(cls=GMGTableBase)
+
+
+class DictReadAttrProxy(object):
+ """
+ Maps read accesses to obj['key'] to obj.key
+ and hides all the rest of the obj
+ """
+ def __init__(self, proxied_obj):
+ self.proxied_obj = proxied_obj
+
+ def __getitem__(self, key):
+ try:
+ return getattr(self.proxied_obj, key)
+ except AttributeError:
+ raise KeyError("%r is not an attribute on %r"
+ % (key, self.proxied_obj))
diff --git a/mediagoblin/db/extratypes.py b/mediagoblin/db/extratypes.py
new file mode 100644
index 00000000..f2304af0
--- /dev/null
+++ b/mediagoblin/db/extratypes.py
@@ -0,0 +1,63 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from sqlalchemy.types import TypeDecorator, Unicode, TEXT
+import json
+
+
+class PathTupleWithSlashes(TypeDecorator):
+ "Represents a Tuple of strings as a slash separated string."
+
+ impl = Unicode
+
+ def process_bind_param(self, value, dialect):
+ if value is not None:
+ if len(value) == 0:
+ value = None
+ else:
+ value = '/'.join(value)
+ return value
+
+ def process_result_value(self, value, dialect):
+ if value is not None:
+ value = tuple(value.split('/'))
+ return value
+
+
+# The following class and only this one class is in very
+# large parts based on example code from sqlalchemy.
+#
+# The original copyright notice and license follows:
+# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+#
+class JSONEncoded(TypeDecorator):
+ "Represents an immutable structure as a json-encoded string."
+
+ impl = TEXT
+
+ def process_bind_param(self, value, dialect):
+ if value is not None:
+ value = json.dumps(value)
+ return value
+
+ def process_result_value(self, value, dialect):
+ if value is not None:
+ value = json.loads(value)
+ return value
diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py
new file mode 100644
index 00000000..c0c7e998
--- /dev/null
+++ b/mediagoblin/db/migration_tools.py
@@ -0,0 +1,276 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.common import simple_printer
+from sqlalchemy import Table
+
+class TableAlreadyExists(Exception):
+ pass
+
+
+class MigrationManager(object):
+ """
+ Migration handling tool.
+
+ Takes information about a database, lets you update the database
+ to the latest migrations, etc.
+ """
+
+ def __init__(self, name, models, migration_registry, session,
+ printer=simple_printer):
+ """
+ Args:
+ - name: identifier of this section of the database
+ - session: session we're going to migrate
+ - migration_registry: where we should find all migrations to
+ run
+ """
+ self.name = unicode(name)
+ self.models = models
+ self.session = session
+ self.migration_registry = migration_registry
+ self._sorted_migrations = None
+ self.printer = printer
+
+ # For convenience
+ from mediagoblin.db.models import MigrationData
+
+ self.migration_model = MigrationData
+ self.migration_table = MigrationData.__table__
+
+ @property
+ def sorted_migrations(self):
+ """
+ Sort migrations if necessary and store in self._sorted_migrations
+ """
+ if not self._sorted_migrations:
+ self._sorted_migrations = sorted(
+ self.migration_registry.items(),
+ # sort on the key... the migration number
+ key=lambda migration_tuple: migration_tuple[0])
+
+ return self._sorted_migrations
+
+ @property
+ def migration_data(self):
+ """
+ Get the migration row associated with this object, if any.
+ """
+ return self.session.query(
+ self.migration_model).filter_by(name=self.name).first()
+
+ @property
+ def latest_migration(self):
+ """
+ Return a migration number for the latest migration, or 0 if
+ there are no migrations.
+ """
+ if self.sorted_migrations:
+ return self.sorted_migrations[-1][0]
+ else:
+ # If no migrations have been set, we start at 0.
+ return 0
+
+ @property
+ def database_current_migration(self):
+ """
+ Return the current migration in the database.
+ """
+ # If the table doesn't even exist, return None.
+ if not self.migration_table.exists(self.session.bind):
+ return None
+
+ # Also return None if self.migration_data is None.
+ if self.migration_data is None:
+ return None
+
+ return self.migration_data.version
+
+ def set_current_migration(self, migration_number=None):
+ """
+ Set the migration in the database to migration_number
+ (or, the latest available)
+ """
+ self.migration_data.version = migration_number or self.latest_migration
+ self.session.commit()
+
+ def migrations_to_run(self):
+ """
+ Get a list of migrations to run still, if any.
+
+ Note that this will fail if there's no migration record for
+ this class!
+ """
+ assert self.database_current_migration is not None
+
+ db_current_migration = self.database_current_migration
+
+ return [
+ (migration_number, migration_func)
+ for migration_number, migration_func in self.sorted_migrations
+ if migration_number > db_current_migration]
+
+
+ def init_tables(self):
+ """
+ Create all tables relative to this package
+ """
+ # sanity check before we proceed, none of these should be created
+ for model in self.models:
+ # Maybe in the future just print out a "Yikes!" or something?
+ if model.__table__.exists(self.session.bind):
+ raise TableAlreadyExists(
+ u"Intended to create table '%s' but it already exists" %
+ model.__table__.name)
+
+ self.migration_model.metadata.create_all(
+ self.session.bind,
+ tables=[model.__table__ for model in self.models])
+
+ def create_new_migration_record(self):
+ """
+ Create a new migration record for this migration set
+ """
+ migration_record = self.migration_model(
+ name=self.name,
+ version=self.latest_migration)
+ self.session.add(migration_record)
+ self.session.commit()
+
+ def dry_run(self):
+ """
+ Print out a dry run of what we would have upgraded.
+ """
+ if self.database_current_migration is None:
+ self.printer(
+ u'~> Woulda initialized: %s\n' % self.name_for_printing())
+ return u'inited'
+
+ migrations_to_run = self.migrations_to_run()
+ if migrations_to_run:
+ self.printer(
+ u'~> Woulda updated %s:\n' % self.name_for_printing())
+
+ for migration_number, migration_func in migrations_to_run():
+ self.printer(
+ u' + Would update %s, "%s"\n' % (
+ migration_number, migration_func.func_name))
+
+ return u'migrated'
+
+ def name_for_printing(self):
+ if self.name == u'__main__':
+ return u"main mediagoblin tables"
+ else:
+ # TODO: Use the friendlier media manager "human readable" name
+ return u'media type "%s"' % self.name
+
+ def init_or_migrate(self):
+ """
+ Initialize the database or migrate if appropriate.
+
+ Returns information about whether or not we initialized
+ ('inited'), migrated ('migrated'), or did nothing (None)
+ """
+ assure_migrations_table_setup(self.session)
+
+ # Find out what migration number, if any, this database data is at,
+ # and what the latest is.
+ migration_number = self.database_current_migration
+
+ # Is this our first time? Is there even a table entry for
+ # this identifier?
+ # If so:
+ # - create all tables
+ # - create record in migrations registry
+ # - print / inform the user
+ # - return 'inited'
+ if migration_number is None:
+ self.printer(u"-> Initializing %s... " % self.name_for_printing())
+
+ self.init_tables()
+ # auto-set at latest migration number
+ self.create_new_migration_record()
+
+ self.printer(u"done.\n")
+ self.set_current_migration()
+ return u'inited'
+
+ # Run migrations, if appropriate.
+ migrations_to_run = self.migrations_to_run()
+ if migrations_to_run:
+ self.printer(
+ u'-> Updating %s:\n' % self.name_for_printing())
+ for migration_number, migration_func in migrations_to_run:
+ self.printer(
+ u' + Running migration %s, "%s"... ' % (
+ migration_number, migration_func.func_name))
+ migration_func(self.session)
+ self.set_current_migration(migration_number)
+ self.printer('done.\n')
+
+ return u'migrated'
+
+ # Otherwise return None. Well it would do this anyway, but
+ # for clarity... ;)
+ return None
+
+
+class RegisterMigration(object):
+ """
+ Tool for registering migrations
+
+ Call like:
+
+ @RegisterMigration(33)
+ def update_dwarves(database):
+ [...]
+
+ This will register your migration with the default migration
+ registry. Alternately, to specify a very specific
+ migration_registry, you can pass in that as the second argument.
+
+ Note, the number of your migration should NEVER be 0 or less than
+ 0. 0 is the default "no migrations" state!
+ """
+ def __init__(self, migration_number, migration_registry):
+ assert migration_number > 0, "Migration number must be > 0!"
+ assert migration_number not in migration_registry, \
+ "Duplicate migration numbers detected! That's not allowed!"
+
+ self.migration_number = migration_number
+ self.migration_registry = migration_registry
+
+ def __call__(self, migration):
+ self.migration_registry[self.migration_number] = migration
+ return migration
+
+
+def assure_migrations_table_setup(db):
+ """
+ Make sure the migrations table is set up in the database.
+ """
+ from mediagoblin.db.models import MigrationData
+
+ if not MigrationData.__table__.exists(db.bind):
+ MigrationData.metadata.create_all(
+ db.bind, tables=[MigrationData.__table__])
+
+
+def inspect_table(metadata, table_name):
+ """Simple helper to get a ref to an already existing table"""
+ return Table(table_name, metadata, autoload=True,
+ autoload_with=metadata.bind)
diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py
new file mode 100644
index 00000000..2c553396
--- /dev/null
+++ b/mediagoblin/db/migrations.py
@@ -0,0 +1,289 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import uuid
+
+from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger,
+ Integer, Unicode, UnicodeText, DateTime,
+ ForeignKey)
+from sqlalchemy.exc import ProgrammingError
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.sql import and_
+from migrate.changeset.constraint import UniqueConstraint
+
+from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
+from mediagoblin.db.models import MediaEntry, Collection, User
+
+MIGRATIONS = {}
+
+
+@RegisterMigration(1, MIGRATIONS)
+def ogg_to_webm_audio(db_conn):
+ metadata = MetaData(bind=db_conn.bind)
+
+ file_keynames = Table('core__file_keynames', metadata, autoload=True,
+ autoload_with=db_conn.bind)
+
+ db_conn.execute(
+ file_keynames.update().where(file_keynames.c.name == 'ogg').
+ values(name='webm_audio')
+ )
+ db_conn.commit()
+
+
+@RegisterMigration(2, MIGRATIONS)
+def add_wants_notification_column(db_conn):
+ metadata = MetaData(bind=db_conn.bind)
+
+ users = Table('core__users', metadata, autoload=True,
+ autoload_with=db_conn.bind)
+
+ col = Column('wants_comment_notification', Boolean,
+ default=True, nullable=True)
+ col.create(users, populate_defaults=True)
+ db_conn.commit()
+
+
+@RegisterMigration(3, MIGRATIONS)
+def add_transcoding_progress(db_conn):
+ metadata = MetaData(bind=db_conn.bind)
+
+ media_entry = inspect_table(metadata, 'core__media_entries')
+
+ col = Column('transcoding_progress', SmallInteger)
+ col.create(media_entry)
+ db_conn.commit()
+
+
+class Collection_v0(declarative_base()):
+ __tablename__ = "core__collections"
+
+ id = Column(Integer, primary_key=True)
+ title = Column(Unicode, nullable=False)
+ slug = Column(Unicode)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now,
+ index=True)
+ description = Column(UnicodeText)
+ creator = Column(Integer, ForeignKey(User.id), nullable=False)
+ items = Column(Integer, default=0)
+
+class CollectionItem_v0(declarative_base()):
+ __tablename__ = "core__collection_items"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
+ collection = Column(Integer, ForeignKey(Collection.id), nullable=False)
+ note = Column(UnicodeText, nullable=True)
+ added = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ position = Column(Integer)
+
+ ## This should be activated, normally.
+ ## But this would change the way the next migration used to work.
+ ## So it's commented for now.
+ __table_args__ = (
+ UniqueConstraint('collection', 'media_entry'),
+ {})
+
+collectionitem_unique_constraint_done = False
+
+@RegisterMigration(4, MIGRATIONS)
+def add_collection_tables(db_conn):
+ Collection_v0.__table__.create(db_conn.bind)
+ CollectionItem_v0.__table__.create(db_conn.bind)
+
+ global collectionitem_unique_constraint_done
+ collectionitem_unique_constraint_done = True
+
+ db_conn.commit()
+
+
+@RegisterMigration(5, MIGRATIONS)
+def add_mediaentry_collected(db_conn):
+ metadata = MetaData(bind=db_conn.bind)
+
+ media_entry = inspect_table(metadata, 'core__media_entries')
+
+ col = Column('collected', Integer, default=0)
+ col.create(media_entry)
+ db_conn.commit()
+
+
+class ProcessingMetaData_v0(declarative_base()):
+ __tablename__ = 'core__processing_metadata'
+
+ id = Column(Integer, primary_key=True)
+ media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False,
+ index=True)
+ callback_url = Column(Unicode)
+
+@RegisterMigration(6, MIGRATIONS)
+def create_processing_metadata_table(db):
+ ProcessingMetaData_v0.__table__.create(db.bind)
+ db.commit()
+
+
+# Okay, problem being:
+# Migration #4 forgot to add the uniqueconstraint for the
+# new tables. While creating the tables from scratch had
+# the constraint enabled.
+#
+# So we have four situations that should end up at the same
+# db layout:
+#
+# 1. Fresh install.
+# Well, easy. Just uses the tables in models.py
+# 2. Fresh install using a git version just before this migration
+# The tables are all there, the unique constraint is also there.
+# This migration should do nothing.
+# But as we can't detect the uniqueconstraint easily,
+# this migration just adds the constraint again.
+# And possibly fails very loud. But ignores the failure.
+# 3. old install, not using git, just releases.
+# This one will get the new tables in #4 (now with constraint!)
+# And this migration is just skipped silently.
+# 4. old install, always on latest git.
+# This one has the tables, but lacks the constraint.
+# So this migration adds the constraint.
+@RegisterMigration(7, MIGRATIONS)
+def fix_CollectionItem_v0_constraint(db_conn):
+ """Add the forgotten Constraint on CollectionItem"""
+
+ global collectionitem_unique_constraint_done
+ if collectionitem_unique_constraint_done:
+ # Reset it. Maybe the whole thing gets run again
+ # For a different db?
+ collectionitem_unique_constraint_done = False
+ return
+
+ metadata = MetaData(bind=db_conn.bind)
+
+ CollectionItem_table = inspect_table(metadata, 'core__collection_items')
+
+ constraint = UniqueConstraint('collection', 'media_entry',
+ name='core__collection_items_collection_media_entry_key',
+ table=CollectionItem_table)
+
+ try:
+ constraint.create()
+ except ProgrammingError:
+ # User probably has an install that was run since the
+ # collection tables were added, so we don't need to run this migration.
+ pass
+
+ db_conn.commit()
+
+
+@RegisterMigration(8, MIGRATIONS)
+def add_license_preference(db):
+ metadata = MetaData(bind=db.bind)
+
+ user_table = inspect_table(metadata, 'core__users')
+
+ col = Column('license_preference', Unicode)
+ col.create(user_table)
+ db.commit()
+
+
+@RegisterMigration(9, MIGRATIONS)
+def mediaentry_new_slug_era(db):
+ """
+ Update for the new era for media type slugs.
+
+ Entries without slugs now display differently in the url like:
+ /u/cwebber/m/id=251/
+
+ ... because of this, we should back-convert:
+ - entries without slugs should be converted to use the id, if possible, to
+ make old urls still work
+ - slugs with = (or also : which is now also not allowed) to have those
+ stripped out (small possibility of breakage here sadly)
+ """
+
+ def slug_and_user_combo_exists(slug, uploader):
+ return db.execute(
+ media_table.select(
+ and_(media_table.c.uploader==uploader,
+ media_table.c.slug==slug))).first() is not None
+
+ def append_garbage_till_unique(row, new_slug):
+ """
+ Attach junk to this row until it's unique, then save it
+ """
+ if slug_and_user_combo_exists(new_slug, row.uploader):
+ # okay, still no success;
+ # let's whack junk on there till it's unique.
+ new_slug += '-' + uuid.uuid4().hex[:4]
+ # keep going if necessary!
+ while slug_and_user_combo_exists(new_slug, row.uploader):
+ new_slug += uuid.uuid4().hex[:4]
+
+ db.execute(
+ media_table.update(). \
+ where(media_table.c.id==row.id). \
+ values(slug=new_slug))
+
+ metadata = MetaData(bind=db.bind)
+
+ media_table = inspect_table(metadata, 'core__media_entries')
+
+ for row in db.execute(media_table.select()):
+ # no slug, try setting to an id
+ if not row.slug:
+ append_garbage_till_unique(row, unicode(row.id))
+ # has "=" or ":" in it... we're getting rid of those
+ elif u"=" in row.slug or u":" in row.slug:
+ append_garbage_till_unique(
+ row, row.slug.replace(u"=", u"-").replace(u":", u"-"))
+
+ db.commit()
+
+
+@RegisterMigration(10, MIGRATIONS)
+def unique_collections_slug(db):
+ """Add unique constraint to collection slug"""
+ metadata = MetaData(bind=db.bind)
+ collection_table = inspect_table(metadata, "core__collections")
+ existing_slugs = {}
+ slugs_to_change = []
+
+ for row in db.execute(collection_table.select()):
+ # if duplicate slug, generate a unique slug
+ if row.creator in existing_slugs and row.slug in \
+ existing_slugs[row.creator]:
+ slugs_to_change.append(row.id)
+ else:
+ if not row.creator in existing_slugs:
+ existing_slugs[row.creator] = [row.slug]
+ else:
+ existing_slugs[row.creator].append(row.slug)
+
+ for row_id in slugs_to_change:
+ new_slug = unicode(uuid.uuid4())
+ db.execute(collection_table.update().
+ where(collection_table.c.id == row_id).
+ values(slug=new_slug))
+ # sqlite does not like to change the schema when a transaction(update) is
+ # not yet completed
+ db.commit()
+
+ constraint = UniqueConstraint('creator', 'slug',
+ name='core__collection_creator_slug_key',
+ table=collection_table)
+ constraint.create()
+
+ db.commit()
diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py
new file mode 100644
index 00000000..9f566e36
--- /dev/null
+++ b/mediagoblin/db/mixin.py
@@ -0,0 +1,334 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+This module contains some Mixin classes for the db objects.
+
+A bunch of functions on the db objects are really more like
+"utility functions": They could live outside the classes
+and be called "by hand" passing the appropiate reference.
+They usually only use the public API of the object and
+rarely use database related stuff.
+
+These functions now live here and get "mixed in" into the
+real objects.
+"""
+
+import uuid
+import re
+import datetime
+
+from werkzeug.utils import cached_property
+
+from mediagoblin import mg_globals
+from mediagoblin.media_types import get_media_managers, FileTypeNotSupported
+from mediagoblin.tools import common, licenses
+from mediagoblin.tools.text import cleaned_markdown_conversion
+from mediagoblin.tools.url import slugify
+
+
+class UserMixin(object):
+ @property
+ def bio_html(self):
+ return cleaned_markdown_conversion(self.bio)
+
+
+class GenerateSlugMixin(object):
+ """
+ Mixin to add a generate_slug method to objects.
+
+ Depends on:
+ - self.slug
+ - self.title
+ - self.check_slug_used(new_slug)
+ """
+ def generate_slug(self):
+ """
+ Generate a unique slug for this object.
+
+ This one does not *force* slugs, but usually it will probably result
+ in a niceish one.
+
+ The end *result* of the algorithm will result in these resolutions for
+ these situations:
+ - If we have a slug, make sure it's clean and sanitized, and if it's
+ unique, we'll use that.
+ - If we have a title, slugify it, and if it's unique, we'll use that.
+ - If we can't get any sort of thing that looks like it'll be a useful
+ slug out of a title or an existing slug, bail, and don't set the
+ slug at all. Don't try to create something just because. Make
+ sure we have a reasonable basis for a slug first.
+ - If we have a reasonable basis for a slug (either based on existing
+ slug or slugified title) but it's not unique, first try appending
+ the entry's id, if that exists
+ - If that doesn't result in something unique, tack on some randomly
+ generated bits until it's unique. That'll be a little bit of junk,
+ but at least it has the basis of a nice slug.
+ """
+ #Is already a slug assigned? Check if it is valid
+ if self.slug:
+ self.slug = slugify(self.slug)
+
+ # otherwise, try to use the title.
+ elif self.title:
+ # assign slug based on title
+ self.slug = slugify(self.title)
+
+ # We don't want any empty string slugs
+ if self.slug == u"":
+ self.slug = None
+
+ # Do we have anything at this point?
+ # If not, we're not going to get a slug
+ # so just return... we're not going to force one.
+ if not self.slug:
+ return # giving up!
+
+ # Otherwise, let's see if this is unique.
+ if self.check_slug_used(self.slug):
+ # It looks like it's being used... lame.
+
+ # Can we just append the object's id to the end?
+ if self.id:
+ slug_with_id = u"%s-%s" % (self.slug, self.id)
+ if not self.check_slug_used(slug_with_id):
+ self.slug = slug_with_id
+ return # success!
+
+ # okay, still no success;
+ # let's whack junk on there till it's unique.
+ self.slug += '-' + uuid.uuid4().hex[:4]
+ # keep going if necessary!
+ while self.check_slug_used(self.slug):
+ self.slug += uuid.uuid4().hex[:4]
+
+
+class MediaEntryMixin(GenerateSlugMixin):
+ def check_slug_used(self, slug):
+ # import this here due to a cyclic import issue
+ # (db.models -> db.mixin -> db.util -> db.models)
+ from mediagoblin.db.util import check_media_slug_used
+
+ return check_media_slug_used(self.uploader, slug, self.id)
+
+ @property
+ def description_html(self):
+ """
+ Rendered version of the description, run through
+ Markdown and cleaned with our cleaning tool.
+ """
+ return cleaned_markdown_conversion(self.description)
+
+ def get_display_media(self):
+ """Find the best media for display.
+
+ We try checking self.media_manager.fetching_order if it exists to
+ pull down the order.
+
+ Returns:
+ (media_size, media_path)
+ or, if not found, None.
+
+ """
+ fetch_order = self.media_manager.media_fetch_order
+
+ # No fetching order found? well, give up!
+ if not fetch_order:
+ return None
+
+ media_sizes = self.media_files.keys()
+
+ for media_size in fetch_order:
+ if media_size in media_sizes:
+ return media_size, self.media_files[media_size]
+
+ def main_mediafile(self):
+ pass
+
+ @property
+ def slug_or_id(self):
+ if self.slug:
+ return self.slug
+ else:
+ return u'id:%s' % self.id
+
+ def url_for_self(self, urlgen, **extra_args):
+ """
+ Generate an appropriate url for ourselves
+
+ Use a slug if we have one, else use our 'id'.
+ """
+ uploader = self.get_uploader
+
+ return urlgen(
+ 'mediagoblin.user_pages.media_home',
+ user=uploader.username,
+ media=self.slug_or_id,
+ **extra_args)
+
+ @property
+ def thumb_url(self):
+ """Return the thumbnail URL (for usage in templates)
+ Will return either the real thumbnail or a default fallback icon."""
+ # TODO: implement generic fallback in case MEDIA_MANAGER does
+ # not specify one?
+ if u'thumb' in self.media_files:
+ thumb_url = mg_globals.app.public_store.file_url(
+ self.media_files[u'thumb'])
+ else:
+ # No thumbnail in media available. Get the media's
+ # MEDIA_MANAGER for the fallback icon and return static URL
+ # Raises FileTypeNotSupported in case no such manager is enabled
+ manager = self.media_manager
+ thumb_url = mg_globals.app.staticdirector(manager[u'default_thumb'])
+ return thumb_url
+
+ @cached_property
+ def media_manager(self):
+ """Returns the MEDIA_MANAGER of the media's media_type
+
+ Raises FileTypeNotSupported in case no such manager is enabled
+ """
+ # TODO, we should be able to make this a simple lookup rather
+ # than iterating through all media managers.
+ for media_type, manager in get_media_managers():
+ if media_type == self.media_type:
+ return manager(self)
+ # Not found? Then raise an error
+ raise FileTypeNotSupported(
+ "MediaManager not in enabled types. Check media_types in config?")
+
+ def get_fail_exception(self):
+ """
+ Get the exception that's appropriate for this error
+ """
+ if self.fail_error:
+ return common.import_component(self.fail_error)
+
+ def get_license_data(self):
+ """Return license dict for requested license"""
+ return licenses.get_license_by_url(self.license or "")
+
+ def exif_display_iter(self):
+ if not self.media_data:
+ return
+ exif_all = self.media_data.get("exif_all")
+
+ for key in exif_all:
+ label = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', key)
+ yield label.replace('EXIF', '').replace('Image', ''), exif_all[key]
+
+ def exif_display_data_short(self):
+ """Display a very short practical version of exif info"""
+ if not self.media_data:
+ return
+
+ exif_all = self.media_data.get("exif_all")
+
+ exif_short = {}
+
+ if 'Image DateTimeOriginal' in exif_all:
+ # format date taken
+ takendate = datetime.datetime.strptime(
+ exif_all['Image DateTimeOriginal']['printable'],
+ '%Y:%m:%d %H:%M:%S').date()
+ taken = takendate.strftime('%B %d %Y')
+
+ exif_short.update({'Date Taken': taken})
+
+ aperture = None
+ if 'EXIF FNumber' in exif_all:
+ fnum = str(exif_all['EXIF FNumber']['printable']).split('/')
+
+ # calculate aperture
+ if len(fnum) == 2:
+ aperture = "f/%.1f" % (float(fnum[0])/float(fnum[1]))
+ elif fnum[0] != 'None':
+ aperture = "f/%s" % (fnum[0])
+
+ if aperture:
+ exif_short.update({'Aperture': aperture})
+
+ short_keys = [
+ ('Camera', 'Image Model', None),
+ ('Exposure', 'EXIF ExposureTime', lambda x: '%s sec' % x),
+ ('ISO Speed', 'EXIF ISOSpeedRatings', None),
+ ('Focal Length', 'EXIF FocalLength', lambda x: '%s mm' % x)]
+
+ for label, key, fmt_func in short_keys:
+ try:
+ val = fmt_func(exif_all[key]['printable']) if fmt_func \
+ else exif_all[key]['printable']
+ exif_short.update({label: val})
+ except KeyError:
+ pass
+
+ return exif_short
+
+
+class MediaCommentMixin(object):
+ @property
+ def content_html(self):
+ """
+ the actual html-rendered version of the comment displayed.
+ Run through Markdown and the HTML cleaner.
+ """
+ return cleaned_markdown_conversion(self.content)
+
+
+class CollectionMixin(GenerateSlugMixin):
+ def check_slug_used(self, slug):
+ # import this here due to a cyclic import issue
+ # (db.models -> db.mixin -> db.util -> db.models)
+ from mediagoblin.db.util import check_collection_slug_used
+
+ return check_collection_slug_used(self.creator, slug, self.id)
+
+ @property
+ def description_html(self):
+ """
+ Rendered version of the description, run through
+ Markdown and cleaned with our cleaning tool.
+ """
+ return cleaned_markdown_conversion(self.description)
+
+ @property
+ def slug_or_id(self):
+ return (self.slug or self.id)
+
+ def url_for_self(self, urlgen, **extra_args):
+ """
+ Generate an appropriate url for ourselves
+
+ Use a slug if we have one, else use our 'id'.
+ """
+ creator = self.get_creator
+
+ return urlgen(
+ 'mediagoblin.user_pages.user_collection',
+ user=creator.username,
+ collection=self.slug_or_id,
+ **extra_args)
+
+
+class CollectionItemMixin(object):
+ @property
+ def note_html(self):
+ """
+ the actual html-rendered version of the note displayed.
+ Run through Markdown and the HTML cleaner.
+ """
+ return cleaned_markdown_conversion(self.note)
diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py
new file mode 100644
index 00000000..2b925983
--- /dev/null
+++ b/mediagoblin/db/models.py
@@ -0,0 +1,524 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+TODO: indexes on foreignkeys, where useful.
+"""
+
+import logging
+import datetime
+
+from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \
+ Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \
+ SmallInteger
+from sqlalchemy.orm import relationship, backref
+from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.sql.expression import desc
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.util import memoized_property
+
+from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
+from mediagoblin.db.base import Base, DictReadAttrProxy
+from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin
+from mediagoblin.tools.files import delete_media_files
+from mediagoblin.tools.common import import_component
+
+# It's actually kind of annoying how sqlalchemy-migrate does this, if
+# I understand it right, but whatever. Anyway, don't remove this :P
+#
+# We could do migration calls more manually instead of relying on
+# this import-based meddling...
+from migrate import changeset
+
+_log = logging.getLogger(__name__)
+
+
+class User(Base, UserMixin):
+ """
+ TODO: We should consider moving some rarely used fields
+ into some sort of "shadow" table.
+ """
+ __tablename__ = "core__users"
+
+ id = Column(Integer, primary_key=True)
+ username = Column(Unicode, nullable=False, unique=True)
+ # Note: no db uniqueness constraint on email because it's not
+ # reliable (many email systems case insensitive despite against
+ # the RFC) and because it would be a mess to implement at this
+ # point.
+ email = Column(Unicode, nullable=False)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ pw_hash = Column(Unicode, nullable=False)
+ email_verified = Column(Boolean, default=False)
+ status = Column(Unicode, default=u"needs_email_verification", nullable=False)
+ # Intented to be nullable=False, but migrations would not work for it
+ # set to nullable=True implicitly.
+ wants_comment_notification = Column(Boolean, default=True)
+ license_preference = Column(Unicode)
+ verification_key = Column(Unicode)
+ is_admin = Column(Boolean, default=False, nullable=False)
+ url = Column(Unicode)
+ bio = Column(UnicodeText) # ??
+ fp_verification_key = Column(Unicode)
+ fp_token_expire = Column(DateTime)
+
+ ## TODO
+ # plugin data would be in a separate model
+
+ def __repr__(self):
+ return '<{0} #{1} {2} {3} "{4}">'.format(
+ self.__class__.__name__,
+ self.id,
+ 'verified' if self.email_verified else 'non-verified',
+ 'admin' if self.is_admin else 'user',
+ self.username)
+
+ def delete(self, **kwargs):
+ """Deletes a User and all related entries/comments/files/..."""
+ # Collections get deleted by relationships.
+
+ media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id)
+ for media in media_entries:
+ # TODO: Make sure that "MediaEntry.delete()" also deletes
+ # all related files/Comments
+ media.delete(del_orphan_tags=False, commit=False)
+
+ # Delete now unused tags
+ # TODO: import here due to cyclic imports!!! This cries for refactoring
+ from mediagoblin.db.util import clean_orphan_tags
+ clean_orphan_tags(commit=False)
+
+ # Delete user, pass through commit=False/True in kwargs
+ super(User, self).delete(**kwargs)
+ _log.info('Deleted user "{0}" account'.format(self.username))
+
+
+class MediaEntry(Base, MediaEntryMixin):
+ """
+ TODO: Consider fetching the media_files using join
+ """
+ __tablename__ = "core__media_entries"
+
+ id = Column(Integer, primary_key=True)
+ uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True)
+ title = Column(Unicode, nullable=False)
+ slug = Column(Unicode)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now,
+ index=True)
+ description = Column(UnicodeText) # ??
+ media_type = Column(Unicode, nullable=False)
+ state = Column(Unicode, default=u'unprocessed', nullable=False)
+ # or use sqlalchemy.types.Enum?
+ license = Column(Unicode)
+ collected = Column(Integer, default=0)
+
+ fail_error = Column(Unicode)
+ fail_metadata = Column(JSONEncoded)
+
+ transcoding_progress = Column(SmallInteger)
+
+ queued_media_file = Column(PathTupleWithSlashes)
+
+ queued_task_id = Column(Unicode)
+
+ __table_args__ = (
+ UniqueConstraint('uploader', 'slug'),
+ {})
+
+ get_uploader = relationship(User)
+
+ media_files_helper = relationship("MediaFile",
+ collection_class=attribute_mapped_collection("name"),
+ cascade="all, delete-orphan"
+ )
+ media_files = association_proxy('media_files_helper', 'file_path',
+ creator=lambda k, v: MediaFile(name=k, file_path=v)
+ )
+
+ attachment_files_helper = relationship("MediaAttachmentFile",
+ cascade="all, delete-orphan",
+ order_by="MediaAttachmentFile.created"
+ )
+ attachment_files = association_proxy("attachment_files_helper", "dict_view",
+ creator=lambda v: MediaAttachmentFile(
+ name=v["name"], filepath=v["filepath"])
+ )
+
+ tags_helper = relationship("MediaTag",
+ cascade="all, delete-orphan" # should be automatically deleted
+ )
+ tags = association_proxy("tags_helper", "dict_view",
+ creator=lambda v: MediaTag(name=v["name"], slug=v["slug"])
+ )
+
+ collections_helper = relationship("CollectionItem",
+ cascade="all, delete-orphan"
+ )
+ collections = association_proxy("collections_helper", "in_collection")
+
+ ## TODO
+ # fail_error
+
+ def get_comments(self, ascending=False):
+ order_col = MediaComment.created
+ if not ascending:
+ order_col = desc(order_col)
+ return self.all_comments.order_by(order_col)
+
+ def url_to_prev(self, urlgen):
+ """get the next 'newer' entry by this user"""
+ media = MediaEntry.query.filter(
+ (MediaEntry.uploader == self.uploader)
+ & (MediaEntry.state == u'processed')
+ & (MediaEntry.id > self.id)).order_by(MediaEntry.id).first()
+
+ if media is not None:
+ return media.url_for_self(urlgen)
+
+ def url_to_next(self, urlgen):
+ """get the next 'older' entry by this user"""
+ media = MediaEntry.query.filter(
+ (MediaEntry.uploader == self.uploader)
+ & (MediaEntry.state == u'processed')
+ & (MediaEntry.id < self.id)).order_by(desc(MediaEntry.id)).first()
+
+ if media is not None:
+ return media.url_for_self(urlgen)
+
+ @property
+ def media_data(self):
+ return getattr(self, self.media_data_ref)
+
+ def media_data_init(self, **kwargs):
+ """
+ Initialize or update the contents of a media entry's media_data row
+ """
+ media_data = self.media_data
+
+ if media_data is None:
+ # Get the correct table:
+ table = import_component(self.media_type + '.models:DATA_MODEL')
+ # No media data, so actually add a new one
+ media_data = table(**kwargs)
+ # Get the relationship set up.
+ media_data.get_media_entry = self
+ else:
+ # Update old media data
+ for field, value in kwargs.iteritems():
+ setattr(media_data, field, value)
+
+ @memoized_property
+ def media_data_ref(self):
+ return import_component(self.media_type + '.models:BACKREF_NAME')
+
+ def __repr__(self):
+ safe_title = self.title.encode('ascii', 'replace')
+
+ return '<{classname} {id}: {title}>'.format(
+ classname=self.__class__.__name__,
+ id=self.id,
+ title=safe_title)
+
+ def delete(self, del_orphan_tags=True, **kwargs):
+ """Delete MediaEntry and all related files/attachments/comments
+
+ This will *not* automatically delete unused collections, which
+ can remain empty...
+
+ :param del_orphan_tags: True/false if we delete unused Tags too
+ :param commit: True/False if this should end the db transaction"""
+ # User's CollectionItems are automatically deleted via "cascade".
+ # Comments on this Media are deleted by cascade, hopefully.
+
+ # Delete all related files/attachments
+ try:
+ delete_media_files(self)
+ except OSError, error:
+ # Returns list of files we failed to delete
+ _log.error('No such files from the user "{1}" to delete: '
+ '{0}'.format(str(error), self.get_uploader))
+ _log.info('Deleted Media entry id "{0}"'.format(self.id))
+ # Related MediaTag's are automatically cleaned, but we might
+ # want to clean out unused Tag's too.
+ if del_orphan_tags:
+ # TODO: Import here due to cyclic imports!!!
+ # This cries for refactoring
+ from mediagoblin.db.util import clean_orphan_tags
+ clean_orphan_tags(commit=False)
+ # pass through commit=False/True in kwargs
+ super(MediaEntry, self).delete(**kwargs)
+
+
+class FileKeynames(Base):
+ """
+ keywords for various places.
+ currently the MediaFile keys
+ """
+ __tablename__ = "core__file_keynames"
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True)
+
+ def __repr__(self):
+ return "<FileKeyname %r: %r>" % (self.id, self.name)
+
+ @classmethod
+ def find_or_new(cls, name):
+ t = cls.query.filter_by(name=name).first()
+ if t is not None:
+ return t
+ return cls(name=name)
+
+
+class MediaFile(Base):
+ """
+ TODO: Highly consider moving "name" into a new table.
+ TODO: Consider preloading said table in software
+ """
+ __tablename__ = "core__mediafiles"
+
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False)
+ name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False)
+ file_path = Column(PathTupleWithSlashes)
+
+ __table_args__ = (
+ PrimaryKeyConstraint('media_entry', 'name_id'),
+ {})
+
+ def __repr__(self):
+ return "<MediaFile %s: %r>" % (self.name, self.file_path)
+
+ name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True)
+ name = association_proxy('name_helper', 'name',
+ creator=FileKeynames.find_or_new
+ )
+
+
+class MediaAttachmentFile(Base):
+ __tablename__ = "core__attachment_files"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False)
+ name = Column(Unicode, nullable=False)
+ filepath = Column(PathTupleWithSlashes)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+ @property
+ def dict_view(self):
+ """A dict like view on this object"""
+ return DictReadAttrProxy(self)
+
+
+class Tag(Base):
+ __tablename__ = "core__tags"
+
+ id = Column(Integer, primary_key=True)
+ slug = Column(Unicode, nullable=False, unique=True)
+
+ def __repr__(self):
+ return "<Tag %r: %r>" % (self.id, self.slug)
+
+ @classmethod
+ def find_or_new(cls, slug):
+ t = cls.query.filter_by(slug=slug).first()
+ if t is not None:
+ return t
+ return cls(slug=slug)
+
+
+class MediaTag(Base):
+ __tablename__ = "core__media_tags"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False, index=True)
+ tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True)
+ name = Column(Unicode)
+ # created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+ __table_args__ = (
+ UniqueConstraint('tag', 'media_entry'),
+ {})
+
+ tag_helper = relationship(Tag)
+ slug = association_proxy('tag_helper', 'slug',
+ creator=Tag.find_or_new
+ )
+
+ def __init__(self, name=None, slug=None):
+ Base.__init__(self)
+ if name is not None:
+ self.name = name
+ if slug is not None:
+ self.tag_helper = Tag.find_or_new(slug)
+
+ @property
+ def dict_view(self):
+ """A dict like view on this object"""
+ return DictReadAttrProxy(self)
+
+
+class MediaComment(Base, MediaCommentMixin):
+ __tablename__ = "core__media_comments"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
+ author = Column(Integer, ForeignKey(User.id), nullable=False)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ content = Column(UnicodeText, nullable=False)
+
+ # Cascade: Comments are owned by their creator. So do the full thing.
+ # lazy=dynamic: People might post a *lot* of comments,
+ # so make the "posted_comments" a query-like thing.
+ get_author = relationship(User,
+ backref=backref("posted_comments",
+ lazy="dynamic",
+ cascade="all, delete-orphan"))
+
+ # Cascade: Comments are somewhat owned by their MediaEntry.
+ # So do the full thing.
+ # lazy=dynamic: MediaEntries might have many comments,
+ # so make the "all_comments" a query-like thing.
+ get_media_entry = relationship(MediaEntry,
+ backref=backref("all_comments",
+ lazy="dynamic",
+ cascade="all, delete-orphan"))
+
+
+class Collection(Base, CollectionMixin):
+ """An 'album' or 'set' of media by a user.
+
+ On deletion, contained CollectionItems get automatically reaped via
+ SQL cascade"""
+ __tablename__ = "core__collections"
+
+ id = Column(Integer, primary_key=True)
+ title = Column(Unicode, nullable=False)
+ slug = Column(Unicode)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now,
+ index=True)
+ description = Column(UnicodeText)
+ creator = Column(Integer, ForeignKey(User.id), nullable=False)
+ # TODO: No of items in Collection. Badly named, can we migrate to num_items?
+ items = Column(Integer, default=0)
+
+ # Cascade: Collections are owned by their creator. So do the full thing.
+ get_creator = relationship(User,
+ backref=backref("collections",
+ cascade="all, delete-orphan"))
+
+ __table_args__ = (
+ UniqueConstraint('creator', 'slug'),
+ {})
+
+ def get_collection_items(self, ascending=False):
+ #TODO, is this still needed with self.collection_items being available?
+ order_col = CollectionItem.position
+ if not ascending:
+ order_col = desc(order_col)
+ return CollectionItem.query.filter_by(
+ collection=self.id).order_by(order_col)
+
+
+class CollectionItem(Base, CollectionItemMixin):
+ __tablename__ = "core__collection_items"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
+ collection = Column(Integer, ForeignKey(Collection.id), nullable=False)
+ note = Column(UnicodeText, nullable=True)
+ added = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ position = Column(Integer)
+
+ # Cascade: CollectionItems are owned by their Collection. So do the full thing.
+ in_collection = relationship(Collection,
+ backref=backref(
+ "collection_items",
+ cascade="all, delete-orphan"))
+
+ get_media_entry = relationship(MediaEntry)
+
+ __table_args__ = (
+ UniqueConstraint('collection', 'media_entry'),
+ {})
+
+ @property
+ def dict_view(self):
+ """A dict like view on this object"""
+ return DictReadAttrProxy(self)
+
+
+class ProcessingMetaData(Base):
+ __tablename__ = 'core__processing_metadata'
+
+ id = Column(Integer, primary_key=True)
+ media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False,
+ index=True)
+ media_entry = relationship(MediaEntry,
+ backref=backref('processing_metadata',
+ cascade='all, delete-orphan'))
+ callback_url = Column(Unicode)
+
+ @property
+ def dict_view(self):
+ """A dict like view on this object"""
+ return DictReadAttrProxy(self)
+
+
+MODELS = [
+ User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames,
+ MediaAttachmentFile, ProcessingMetaData]
+
+
+######################################################
+# Special, migrations-tracking table
+#
+# Not listed in MODELS because this is special and not
+# really migrated, but used for migrations (for now)
+######################################################
+
+class MigrationData(Base):
+ __tablename__ = "core__migrations"
+
+ name = Column(Unicode, primary_key=True)
+ version = Column(Integer, nullable=False, default=0)
+
+######################################################
+
+
+def show_table_init(engine_uri):
+ if engine_uri is None:
+ engine_uri = 'sqlite:///:memory:'
+ from sqlalchemy import create_engine
+ engine = create_engine(engine_uri, echo=True)
+
+ Base.metadata.create_all(engine)
+
+
+if __name__ == '__main__':
+ from sys import argv
+ print repr(argv)
+ if len(argv) == 2:
+ uri = argv[1]
+ else:
+ uri = None
+ show_table_init(uri)
diff --git a/mediagoblin/db/models_v0.py b/mediagoblin/db/models_v0.py
new file mode 100644
index 00000000..bdedec2e
--- /dev/null
+++ b/mediagoblin/db/models_v0.py
@@ -0,0 +1,342 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+TODO: indexes on foreignkeys, where useful.
+"""
+
+###########################################################################
+# WHAT IS THIS FILE?
+# ------------------
+#
+# Upon occasion, someone runs into this file and wonders why we have
+# both a models.py and a models_v0.py.
+#
+# The short of it is: you can ignore this file.
+#
+# The long version is, in two parts:
+#
+# - We used to use MongoDB, then we switched to SQL and SQLAlchemy.
+# We needed to convert peoples' databases; the script we had would
+# switch them to the first version right after Mongo, convert over
+# all their tables, then run any migrations that were added after.
+#
+# - That script is now removed, but there is some discussion of
+# writing a test that would set us at the first SQL migration and
+# run everything after. If we wrote that, this file would still be
+# useful. But for now, it's legacy!
+#
+###########################################################################
+
+
+import datetime
+import sys
+
+from sqlalchemy import (
+ Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey,
+ UniqueConstraint, PrimaryKeyConstraint, SmallInteger, Float)
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship, backref
+from sqlalchemy.orm.collections import attribute_mapped_collection
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.util import memoized_property
+
+from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded
+from mediagoblin.db.base import GMGTableBase, Session
+
+
+Base_v0 = declarative_base(cls=GMGTableBase)
+
+
+class User(Base_v0):
+ """
+ TODO: We should consider moving some rarely used fields
+ into some sort of "shadow" table.
+ """
+ __tablename__ = "core__users"
+
+ id = Column(Integer, primary_key=True)
+ username = Column(Unicode, nullable=False, unique=True)
+ email = Column(Unicode, nullable=False)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ pw_hash = Column(Unicode, nullable=False)
+ email_verified = Column(Boolean, default=False)
+ status = Column(Unicode, default=u"needs_email_verification", nullable=False)
+ verification_key = Column(Unicode)
+ is_admin = Column(Boolean, default=False, nullable=False)
+ url = Column(Unicode)
+ bio = Column(UnicodeText) # ??
+ fp_verification_key = Column(Unicode)
+ fp_token_expire = Column(DateTime)
+
+ ## TODO
+ # plugin data would be in a separate model
+
+
+class MediaEntry(Base_v0):
+ """
+ TODO: Consider fetching the media_files using join
+ """
+ __tablename__ = "core__media_entries"
+
+ id = Column(Integer, primary_key=True)
+ uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True)
+ title = Column(Unicode, nullable=False)
+ slug = Column(Unicode)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now,
+ index=True)
+ description = Column(UnicodeText) # ??
+ media_type = Column(Unicode, nullable=False)
+ state = Column(Unicode, default=u'unprocessed', nullable=False)
+ # or use sqlalchemy.types.Enum?
+ license = Column(Unicode)
+
+ fail_error = Column(Unicode)
+ fail_metadata = Column(JSONEncoded)
+
+ queued_media_file = Column(PathTupleWithSlashes)
+
+ queued_task_id = Column(Unicode)
+
+ __table_args__ = (
+ UniqueConstraint('uploader', 'slug'),
+ {})
+
+ get_uploader = relationship(User)
+
+ media_files_helper = relationship("MediaFile",
+ collection_class=attribute_mapped_collection("name"),
+ cascade="all, delete-orphan"
+ )
+
+ attachment_files_helper = relationship("MediaAttachmentFile",
+ cascade="all, delete-orphan",
+ order_by="MediaAttachmentFile.created"
+ )
+
+ tags_helper = relationship("MediaTag",
+ cascade="all, delete-orphan"
+ )
+
+ def media_data_init(self, **kwargs):
+ """
+ Initialize or update the contents of a media entry's media_data row
+ """
+ session = Session()
+
+ media_data = session.query(self.media_data_table).filter_by(
+ media_entry=self.id).first()
+
+ # No media data, so actually add a new one
+ if media_data is None:
+ media_data = self.media_data_table(
+ media_entry=self.id,
+ **kwargs)
+ session.add(media_data)
+ # Update old media data
+ else:
+ for field, value in kwargs.iteritems():
+ setattr(media_data, field, value)
+
+ @memoized_property
+ def media_data_table(self):
+ # TODO: memoize this
+ models_module = self.media_type + '.models'
+ __import__(models_module)
+ return sys.modules[models_module].DATA_MODEL
+
+
+class FileKeynames(Base_v0):
+ """
+ keywords for various places.
+ currently the MediaFile keys
+ """
+ __tablename__ = "core__file_keynames"
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True)
+
+ def __repr__(self):
+ return "<FileKeyname %r: %r>" % (self.id, self.name)
+
+ @classmethod
+ def find_or_new(cls, name):
+ t = cls.query.filter_by(name=name).first()
+ if t is not None:
+ return t
+ return cls(name=name)
+
+
+class MediaFile(Base_v0):
+ """
+ TODO: Highly consider moving "name" into a new table.
+ TODO: Consider preloading said table in software
+ """
+ __tablename__ = "core__mediafiles"
+
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False)
+ name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False)
+ file_path = Column(PathTupleWithSlashes)
+
+ __table_args__ = (
+ PrimaryKeyConstraint('media_entry', 'name_id'),
+ {})
+
+ def __repr__(self):
+ return "<MediaFile %s: %r>" % (self.name, self.file_path)
+
+ name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True)
+ name = association_proxy('name_helper', 'name',
+ creator=FileKeynames.find_or_new
+ )
+
+
+class MediaAttachmentFile(Base_v0):
+ __tablename__ = "core__attachment_files"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False)
+ name = Column(Unicode, nullable=False)
+ filepath = Column(PathTupleWithSlashes)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+
+class Tag(Base_v0):
+ __tablename__ = "core__tags"
+
+ id = Column(Integer, primary_key=True)
+ slug = Column(Unicode, nullable=False, unique=True)
+
+ def __repr__(self):
+ return "<Tag %r: %r>" % (self.id, self.slug)
+
+ @classmethod
+ def find_or_new(cls, slug):
+ t = cls.query.filter_by(slug=slug).first()
+ if t is not None:
+ return t
+ return cls(slug=slug)
+
+
+class MediaTag(Base_v0):
+ __tablename__ = "core__media_tags"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id),
+ nullable=False, index=True)
+ tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True)
+ name = Column(Unicode)
+ # created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+
+ __table_args__ = (
+ UniqueConstraint('tag', 'media_entry'),
+ {})
+
+ tag_helper = relationship(Tag)
+ slug = association_proxy('tag_helper', 'slug',
+ creator=Tag.find_or_new
+ )
+
+ def __init__(self, name=None, slug=None):
+ Base_v0.__init__(self)
+ if name is not None:
+ self.name = name
+ if slug is not None:
+ self.tag_helper = Tag.find_or_new(slug)
+
+
+class MediaComment(Base_v0):
+ __tablename__ = "core__media_comments"
+
+ id = Column(Integer, primary_key=True)
+ media_entry = Column(
+ Integer, ForeignKey(MediaEntry.id), nullable=False, index=True)
+ author = Column(Integer, ForeignKey(User.id), nullable=False)
+ created = Column(DateTime, nullable=False, default=datetime.datetime.now)
+ content = Column(UnicodeText, nullable=False)
+
+ get_author = relationship(User)
+
+
+class ImageData(Base_v0):
+ __tablename__ = "image__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref("image__media_data", cascade="all, delete-orphan"))
+
+ width = Column(Integer)
+ height = Column(Integer)
+ exif_all = Column(JSONEncoded)
+ gps_longitude = Column(Float)
+ gps_latitude = Column(Float)
+ gps_altitude = Column(Float)
+ gps_direction = Column(Float)
+
+
+class VideoData(Base_v0):
+ __tablename__ = "video__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref("video__media_data", cascade="all, delete-orphan"))
+
+ width = Column(SmallInteger)
+ height = Column(SmallInteger)
+
+
+class AsciiData(Base_v0):
+ __tablename__ = "ascii__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref("ascii__media_data", cascade="all, delete-orphan"))
+
+
+class AudioData(Base_v0):
+ __tablename__ = "audio__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref("audio__media_data", cascade="all, delete-orphan"))
+
+
+######################################################
+# Special, migrations-tracking table
+#
+# Not listed in MODELS because this is special and not
+# really migrated, but used for migrations (for now)
+######################################################
+
+class MigrationData(Base_v0):
+ __tablename__ = "core__migrations"
+
+ name = Column(Unicode, primary_key=True)
+ version = Column(Integer, nullable=False, default=0)
+
+######################################################
diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py
new file mode 100644
index 00000000..0b1679fb
--- /dev/null
+++ b/mediagoblin/db/open.py
@@ -0,0 +1,101 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from sqlalchemy import create_engine, event
+import logging
+
+from mediagoblin.db.base import Base, Session
+from mediagoblin import mg_globals
+
+_log = logging.getLogger(__name__)
+
+
+class DatabaseMaster(object):
+ def __init__(self, engine):
+ self.engine = engine
+
+ for k, v in Base._decl_class_registry.iteritems():
+ setattr(self, k, v)
+
+ def commit(self):
+ Session.commit()
+
+ def save(self, obj):
+ Session.add(obj)
+ Session.flush()
+
+ def check_session_clean(self):
+ for dummy in Session():
+ _log.warn("STRANGE: There are elements in the sql session. "
+ "Please report this and help us track this down.")
+ break
+
+ def reset_after_request(self):
+ Session.rollback()
+ Session.remove()
+
+
+def load_models(app_config):
+ import mediagoblin.db.models
+
+ for media_type in app_config['media_types']:
+ _log.debug("Loading %s.models", media_type)
+ __import__(media_type + ".models")
+
+ for plugin in mg_globals.global_config.get('plugins', {}).keys():
+ _log.debug("Loading %s.models", plugin)
+ try:
+ __import__(plugin + ".models")
+ except ImportError as exc:
+ _log.debug("Could not load {0}.models: {1}".format(
+ plugin,
+ exc))
+
+
+def _sqlite_fk_pragma_on_connect(dbapi_con, con_record):
+ """Enable foreign key checking on each new sqlite connection"""
+ dbapi_con.execute('pragma foreign_keys=on')
+
+
+def _sqlite_disable_fk_pragma_on_connect(dbapi_con, con_record):
+ """
+ Disable foreign key checking on each new sqlite connection
+ (Good for migrations!)
+ """
+ dbapi_con.execute('pragma foreign_keys=off')
+
+
+def setup_connection_and_db_from_config(app_config, migrations=False):
+ engine = create_engine(app_config['sql_engine'])
+
+ # Enable foreign key checking for sqlite
+ if app_config['sql_engine'].startswith('sqlite://'):
+ if migrations:
+ event.listen(engine, 'connect',
+ _sqlite_disable_fk_pragma_on_connect)
+ else:
+ event.listen(engine, 'connect', _sqlite_fk_pragma_on_connect)
+
+ # logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
+
+ Session.configure(bind=engine)
+
+ return DatabaseMaster(engine)
+
+
+def check_db_migrations_current(db):
+ pass
diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py
new file mode 100644
index 00000000..6ffec44d
--- /dev/null
+++ b/mediagoblin/db/util.py
@@ -0,0 +1,76 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.db.base import Session
+from mediagoblin.db.models import MediaEntry, Tag, MediaTag, Collection
+
+
+##########################
+# Random utility functions
+##########################
+
+
+def atomic_update(table, query_dict, update_values):
+ table.find(query_dict).update(update_values,
+ synchronize_session=False)
+ Session.commit()
+
+
+def check_media_slug_used(uploader_id, slug, ignore_m_id):
+ query = MediaEntry.query.filter_by(uploader=uploader_id, slug=slug)
+ if ignore_m_id is not None:
+ query = query.filter(MediaEntry.id != ignore_m_id)
+ does_exist = query.first() is not None
+ return does_exist
+
+
+def media_entries_for_tag_slug(dummy_db, tag_slug):
+ return MediaEntry.query \
+ .join(MediaEntry.tags_helper) \
+ .join(MediaTag.tag_helper) \
+ .filter(
+ (MediaEntry.state == u'processed')
+ & (Tag.slug == tag_slug))
+
+
+def clean_orphan_tags(commit=True):
+ """Search for unused MediaTags and delete them"""
+ q1 = Session.query(Tag).outerjoin(MediaTag).filter(MediaTag.id==None)
+ for t in q1:
+ Session.delete(t)
+ # The "let the db do all the work" version:
+ # q1 = Session.query(Tag.id).outerjoin(MediaTag).filter(MediaTag.id==None)
+ # q2 = Session.query(Tag).filter(Tag.id.in_(q1))
+ # q2.delete(synchronize_session = False)
+ if commit:
+ Session.commit()
+
+
+def check_collection_slug_used(creator_id, slug, ignore_c_id):
+ filt = (Collection.creator == creator_id) \
+ & (Collection.slug == slug)
+ if ignore_c_id is not None:
+ filt = filt & (Collection.id != ignore_c_id)
+ does_exist = Session.query(Collection.id).filter(filt).first() is not None
+ return does_exist
+
+
+if __name__ == '__main__':
+ from mediagoblin.db.open import setup_connection_and_db_from_config
+
+ db = setup_connection_and_db_from_config({'sql_engine':'sqlite:///mediagoblin.db'})
+
+ clean_orphan_tags()
diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py
new file mode 100644
index 00000000..f3535fcf
--- /dev/null
+++ b/mediagoblin/decorators.py
@@ -0,0 +1,237 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from functools import wraps
+
+from urlparse import urljoin
+from werkzeug.exceptions import Forbidden, NotFound
+from werkzeug.urls import url_quote
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.db.models import MediaEntry, User
+from mediagoblin.tools.response import redirect, render_404
+
+
+def require_active_login(controller):
+ """
+ Require an active login from the user.
+ """
+ @wraps(controller)
+ def new_controller_func(request, *args, **kwargs):
+ if request.user and \
+ request.user.status == u'needs_email_verification':
+ return redirect(
+ request, 'mediagoblin.user_pages.user_home',
+ user=request.user.username)
+ elif not request.user or request.user.status != u'active':
+ next_url = urljoin(
+ request.urlgen('mediagoblin.auth.login',
+ qualified=True),
+ request.url)
+
+ return redirect(request, 'mediagoblin.auth.login',
+ next=next_url)
+
+ return controller(request, *args, **kwargs)
+
+ return new_controller_func
+
+def active_user_from_url(controller):
+ """Retrieve User() from <user> URL pattern and pass in as url_user=...
+
+ Returns a 404 if no such active user has been found"""
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ user = User.query.filter_by(username=request.matchdict['user']).first()
+ if user is None:
+ return render_404(request)
+
+ return controller(request, *args, url_user=user, **kwargs)
+
+ return wrapper
+
+
+def user_may_delete_media(controller):
+ """
+ Require user ownership of the MediaEntry to delete.
+ """
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ uploader_id = kwargs['media'].uploader
+ if not (request.user.is_admin or
+ request.user.id == uploader_id):
+ raise Forbidden()
+
+ return controller(request, *args, **kwargs)
+
+ return wrapper
+
+
+def user_may_alter_collection(controller):
+ """
+ Require user ownership of the Collection to modify.
+ """
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ creator_id = request.db.User.find_one(
+ {'username': request.matchdict['user']}).id
+ if not (request.user.is_admin or
+ request.user.id == creator_id):
+ raise Forbidden()
+
+ return controller(request, *args, **kwargs)
+
+ return wrapper
+
+
+def uses_pagination(controller):
+ """
+ Check request GET 'page' key for wrong values
+ """
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ try:
+ page = int(request.GET.get('page', 1))
+ if page < 0:
+ return render_404(request)
+ except ValueError:
+ return render_404(request)
+
+ return controller(request, page=page, *args, **kwargs)
+
+ return wrapper
+
+
+def get_user_media_entry(controller):
+ """
+ Pass in a MediaEntry based off of a url component
+ """
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ user = User.query.filter_by(username=request.matchdict['user']).first()
+ if not user:
+ raise NotFound()
+
+ media = None
+
+ # might not be a slug, might be an id, but whatever
+ media_slug = request.matchdict['media']
+
+ # if it starts with id: it actually isn't a slug, it's an id.
+ if media_slug.startswith(u'id:'):
+ try:
+ media = MediaEntry.query.filter_by(
+ id=int(media_slug[3:]),
+ state=u'processed',
+ uploader=user.id).first()
+ except ValueError:
+ raise NotFound()
+ else:
+ # no magical id: stuff? It's a slug!
+ media = MediaEntry.query.filter_by(
+ slug=media_slug,
+ state=u'processed',
+ uploader=user.id).first()
+
+ if not media:
+ # Didn't find anything? Okay, 404.
+ raise NotFound()
+
+ return controller(request, media=media, *args, **kwargs)
+
+ return wrapper
+
+
+def get_user_collection(controller):
+ """
+ Pass in a Collection based off of a url component
+ """
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ user = request.db.User.find_one(
+ {'username': request.matchdict['user']})
+
+ if not user:
+ return render_404(request)
+
+ collection = request.db.Collection.find_one(
+ {'slug': request.matchdict['collection'],
+ 'creator': user.id})
+
+ # Still no collection? Okay, 404.
+ if not collection:
+ return render_404(request)
+
+ return controller(request, collection=collection, *args, **kwargs)
+
+ return wrapper
+
+
+def get_user_collection_item(controller):
+ """
+ Pass in a CollectionItem based off of a url component
+ """
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ user = request.db.User.find_one(
+ {'username': request.matchdict['user']})
+
+ if not user:
+ return render_404(request)
+
+ collection_item = request.db.CollectionItem.find_one(
+ {'id': request.matchdict['collection_item'] })
+
+ # Still no collection item? Okay, 404.
+ if not collection_item:
+ return render_404(request)
+
+ return controller(request, collection_item=collection_item, *args, **kwargs)
+
+ return wrapper
+
+
+def get_media_entry_by_id(controller):
+ """
+ Pass in a MediaEntry based off of a url component
+ """
+ @wraps(controller)
+ def wrapper(request, *args, **kwargs):
+ media = MediaEntry.query.filter_by(
+ id=request.matchdict['media_id'],
+ state=u'processed').first()
+ # Still no media? Okay, 404.
+ if not media:
+ return render_404(request)
+
+ given_username = request.matchdict.get('user')
+ if given_username and (given_username != media.get_uploader.username):
+ return render_404(request)
+
+ return controller(request, media=media, *args, **kwargs)
+
+ return wrapper
+
+
+def get_workbench(func):
+ """Decorator, passing in a workbench as kwarg which is cleaned up afterwards"""
+
+ @wraps(func)
+ def new_func(*args, **kwargs):
+ with mgg.workbench_manager.create() as workbench:
+ return func(*args, workbench=workbench, **kwargs)
+
+ return new_func
diff --git a/mediagoblin/edit/__init__.py b/mediagoblin/edit/__init__.py
new file mode 100644
index 00000000..621845ba
--- /dev/null
+++ b/mediagoblin/edit/__init__.py
@@ -0,0 +1,15 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py
new file mode 100644
index 00000000..3b2486de
--- /dev/null
+++ b/mediagoblin/edit/forms.py
@@ -0,0 +1,107 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import wtforms
+
+from mediagoblin.tools.text import tag_length_validator, TOO_LONG_TAG_WARNING
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+from mediagoblin.tools.licenses import licenses_as_choices
+
+class EditForm(wtforms.Form):
+ title = wtforms.TextField(
+ _('Title'),
+ [wtforms.validators.Length(min=0, max=500)])
+ description = wtforms.TextAreaField(
+ _('Description of this work'),
+ description=_("""You can use
+ <a href="http://daringfireball.net/projects/markdown/basics">
+ Markdown</a> for formatting."""))
+ tags = wtforms.TextField(
+ _('Tags'),
+ [tag_length_validator],
+ description=_(
+ "Separate tags by commas."))
+ slug = wtforms.TextField(
+ _('Slug'),
+ [wtforms.validators.Required(message=_("The slug can't be empty"))],
+ description=_(
+ "The title part of this media's address. "
+ "You usually don't need to change this."))
+ license = wtforms.SelectField(
+ _('License'),
+ [wtforms.validators.Optional(),],
+ choices=licenses_as_choices())
+
+class EditProfileForm(wtforms.Form):
+ bio = wtforms.TextAreaField(
+ _('Bio'),
+ [wtforms.validators.Length(min=0, max=500)],
+ description=_("""You can use
+ <a href="http://daringfireball.net/projects/markdown/basics">
+ Markdown</a> for formatting."""))
+ url = wtforms.TextField(
+ _('Website'),
+ [wtforms.validators.Optional(),
+ wtforms.validators.URL(message=_("This address contains errors"))])
+
+
+class EditAccountForm(wtforms.Form):
+ license_preference = wtforms.SelectField(
+ _('License preference'),
+ [
+ wtforms.validators.Optional(),
+ wtforms.validators.AnyOf([lic[0] for lic in licenses_as_choices()]),
+ ],
+ choices=licenses_as_choices(),
+ description=_('This will be your default license on upload forms.'))
+ wants_comment_notification = wtforms.BooleanField(
+ label=_("Email me when others comment on my media"))
+
+
+class EditAttachmentsForm(wtforms.Form):
+ attachment_name = wtforms.TextField(
+ 'Title')
+ attachment_file = wtforms.FileField(
+ 'File')
+
+class EditCollectionForm(wtforms.Form):
+ title = wtforms.TextField(
+ _('Title'),
+ [wtforms.validators.Length(min=0, max=500), wtforms.validators.Required(message=_("The title can't be empty"))])
+ description = wtforms.TextAreaField(
+ _('Description of this collection'),
+ description=_("""You can use
+ <a href="http://daringfireball.net/projects/markdown/basics">
+ Markdown</a> for formatting."""))
+ slug = wtforms.TextField(
+ _('Slug'),
+ [wtforms.validators.Required(message=_("The slug can't be empty"))],
+ description=_(
+ "The title part of this collection's address. "
+ "You usually don't need to change this."))
+
+
+class ChangePassForm(wtforms.Form):
+ old_password = wtforms.PasswordField(
+ _('Old password'),
+ [wtforms.validators.Required()],
+ description=_(
+ "Enter your old password to prove you own this account."))
+ new_password = wtforms.PasswordField(
+ _('New password'),
+ [wtforms.validators.Required(),
+ wtforms.validators.Length(min=6, max=30)],
+ id="password")
diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py
new file mode 100644
index 00000000..aab537a0
--- /dev/null
+++ b/mediagoblin/edit/lib.py
@@ -0,0 +1,24 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+def may_edit_media(request, media):
+ """Check, if the request's user may edit the media details"""
+ if media.uploader == request.user.id:
+ return True
+ if request.user.is_admin:
+ return True
+ return False
diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py
new file mode 100644
index 00000000..622729ac
--- /dev/null
+++ b/mediagoblin/edit/routing.py
@@ -0,0 +1,28 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.routing import add_route
+
+add_route('mediagoblin.edit.profile', '/u/<string:user>/edit/',
+ 'mediagoblin.edit.views:edit_profile')
+add_route('mediagoblin.edit.legacy_edit_profile', '/edit/profile/',
+ 'mediagoblin.edit.views:legacy_edit_profile')
+add_route('mediagoblin.edit.account', '/edit/account/',
+ 'mediagoblin.edit.views:edit_account')
+add_route('mediagoblin.edit.delete_account', '/edit/account/delete/',
+ 'mediagoblin.edit.views:delete_account')
+add_route('mediagoblin.edit.pass', '/edit/password/',
+ 'mediagoblin.edit.views:change_pass')
diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py
new file mode 100644
index 00000000..508c380d
--- /dev/null
+++ b/mediagoblin/edit/views.py
@@ -0,0 +1,371 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from datetime import datetime
+
+from werkzeug.exceptions import Forbidden
+from werkzeug.utils import secure_filename
+
+from mediagoblin import messages
+from mediagoblin import mg_globals
+
+from mediagoblin.auth import lib as auth_lib
+from mediagoblin.edit import forms
+from mediagoblin.edit.lib import may_edit_media
+from mediagoblin.decorators import (require_active_login, active_user_from_url,
+ get_media_entry_by_id,
+ user_may_alter_collection, get_user_collection)
+from mediagoblin.tools.response import render_to_response, \
+ redirect, redirect_obj
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.tools.text import (
+ convert_to_tag_list_of_dicts, media_tags_as_string)
+from mediagoblin.tools.url import slugify
+from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used
+
+import mimetypes
+
+
+@get_media_entry_by_id
+@require_active_login
+def edit_media(request, media):
+ if not may_edit_media(request, media):
+ raise Forbidden("User may not edit this media")
+
+ defaults = dict(
+ title=media.title,
+ slug=media.slug,
+ description=media.description,
+ tags=media_tags_as_string(media.tags),
+ license=media.license)
+
+ form = forms.EditForm(
+ request.form,
+ **defaults)
+
+ if request.method == 'POST' and form.validate():
+ # Make sure there isn't already a MediaEntry with such a slug
+ # and userid.
+ slug = slugify(form.slug.data)
+ slug_used = check_media_slug_used(media.uploader, slug, media.id)
+
+ if slug_used:
+ form.slug.errors.append(
+ _(u'An entry with that slug already exists for this user.'))
+ else:
+ media.title = form.title.data
+ media.description = form.description.data
+ media.tags = convert_to_tag_list_of_dicts(
+ form.tags.data)
+
+ media.license = unicode(form.license.data) or None
+ media.slug = slug
+ media.save()
+
+ return redirect_obj(request, media)
+
+ if request.user.is_admin \
+ and media.uploader != request.user.id \
+ and request.method != 'POST':
+ messages.add_message(
+ request, messages.WARNING,
+ _("You are editing another user's media. Proceed with caution."))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/edit.html',
+ {'media': media,
+ 'form': form})
+
+
+# Mimetypes that browsers parse scripts in.
+# Content-sniffing isn't taken into consideration.
+UNSAFE_MIMETYPES = [
+ 'text/html',
+ 'text/svg+xml']
+
+
+@get_media_entry_by_id
+@require_active_login
+def edit_attachments(request, media):
+ if mg_globals.app_config['allow_attachments']:
+ form = forms.EditAttachmentsForm()
+
+ # Add any attachements
+ if 'attachment_file' in request.files \
+ and request.files['attachment_file']:
+
+ # Security measure to prevent attachments from being served as
+ # text/html, which will be parsed by web clients and pose an XSS
+ # threat.
+ #
+ # TODO
+ # This method isn't flawless as some browsers may perform
+ # content-sniffing.
+ # This method isn't flawless as we do the mimetype lookup on the
+ # machine parsing the upload form, and not necessarily the machine
+ # serving the attachments.
+ if mimetypes.guess_type(
+ request.files['attachment_file'].filename)[0] in \
+ UNSAFE_MIMETYPES:
+ public_filename = secure_filename('{0}.notsafe'.format(
+ request.files['attachment_file'].filename))
+ else:
+ public_filename = secure_filename(
+ request.files['attachment_file'].filename)
+
+ attachment_public_filepath \
+ = mg_globals.public_store.get_unique_filepath(
+ ['media_entries', unicode(media.id), 'attachment',
+ public_filename])
+
+ attachment_public_file = mg_globals.public_store.get_file(
+ attachment_public_filepath, 'wb')
+
+ try:
+ attachment_public_file.write(
+ request.files['attachment_file'].stream.read())
+ finally:
+ request.files['attachment_file'].stream.close()
+
+ media.attachment_files.append(dict(
+ name=form.attachment_name.data \
+ or request.files['attachment_file'].filename,
+ filepath=attachment_public_filepath,
+ created=datetime.utcnow(),
+ ))
+
+ media.save()
+
+ messages.add_message(
+ request, messages.SUCCESS,
+ _("You added the attachment %s!") \
+ % (form.attachment_name.data
+ or request.files['attachment_file'].filename))
+
+ return redirect(request,
+ location=media.url_for_self(request.urlgen))
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/attachments.html',
+ {'media': media,
+ 'form': form})
+ else:
+ raise Forbidden("Attachments are disabled")
+
+@require_active_login
+def legacy_edit_profile(request):
+ """redirect the old /edit/profile/?username=USER to /u/USER/edit/"""
+ username = request.GET.get('username') or request.user.username
+ return redirect(request, 'mediagoblin.edit.profile', user=username)
+
+
+@require_active_login
+@active_user_from_url
+def edit_profile(request, url_user=None):
+ # admins may edit any user profile
+ if request.user.username != url_user.username:
+ if not request.user.is_admin:
+ raise Forbidden(_("You can only edit your own profile."))
+
+ # No need to warn again if admin just submitted an edited profile
+ if request.method != 'POST':
+ messages.add_message(
+ request, messages.WARNING,
+ _("You are editing a user's profile. Proceed with caution."))
+
+ user = url_user
+
+ form = forms.EditProfileForm(request.form,
+ url=user.url,
+ bio=user.bio)
+
+ if request.method == 'POST' and form.validate():
+ user.url = unicode(form.url.data)
+ user.bio = unicode(form.bio.data)
+
+ user.save()
+
+ messages.add_message(request,
+ messages.SUCCESS,
+ _("Profile changes saved"))
+ return redirect(request,
+ 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/edit_profile.html',
+ {'user': user,
+ 'form': form})
+
+
+@require_active_login
+def edit_account(request):
+ user = request.user
+ form = forms.EditAccountForm(request.form,
+ wants_comment_notification=user.wants_comment_notification,
+ license_preference=user.license_preference)
+
+ if request.method == 'POST':
+ form_validated = form.validate()
+
+ if form_validated and \
+ form.wants_comment_notification.validate(form):
+ user.wants_comment_notification = \
+ form.wants_comment_notification.data
+
+ if form_validated and \
+ form.license_preference.validate(form):
+ user.license_preference = \
+ form.license_preference.data
+
+ if form_validated and not form.errors:
+ user.save()
+ messages.add_message(request,
+ messages.SUCCESS,
+ _("Account settings saved"))
+ return redirect(request,
+ 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/edit_account.html',
+ {'user': user,
+ 'form': form})
+
+
+@require_active_login
+def delete_account(request):
+ """Delete a user completely"""
+ user = request.user
+ if request.method == 'POST':
+ if request.form.get(u'confirmed'):
+ # Form submitted and confirmed. Actually delete the user account
+ # Log out user and delete cookies etc.
+ # TODO: Should we be using MG.auth.views.py:logout for this?
+ request.session.delete()
+
+ # Delete user account and all related media files etc....
+ request.user.delete()
+
+ # We should send a message that the user has been deleted
+ # successfully. But we just deleted the session, so we
+ # can't...
+ return redirect(request, 'index')
+
+ else: # Did not check the confirmation box...
+ messages.add_message(
+ request, messages.WARNING,
+ _('You need to confirm the deletion of your account.'))
+
+ # No POST submission or not confirmed, just show page
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/delete_account.html',
+ {'user': user})
+
+
+@require_active_login
+@user_may_alter_collection
+@get_user_collection
+def edit_collection(request, collection):
+ defaults = dict(
+ title=collection.title,
+ slug=collection.slug,
+ description=collection.description)
+
+ form = forms.EditCollectionForm(
+ request.form,
+ **defaults)
+
+ if request.method == 'POST' and form.validate():
+ # Make sure there isn't already a Collection with such a slug
+ # and userid.
+ slug_used = check_collection_slug_used(collection.creator,
+ form.slug.data, collection.id)
+
+ # Make sure there isn't already a Collection with this title
+ existing_collection = request.db.Collection.find_one({
+ 'creator': request.user.id,
+ 'title':form.title.data})
+
+ if existing_collection and existing_collection.id != collection.id:
+ messages.add_message(
+ request, messages.ERROR,
+ _('You already have a collection called "%s"!') % \
+ form.title.data)
+ elif slug_used:
+ form.slug.errors.append(
+ _(u'A collection with that slug already exists for this user.'))
+ else:
+ collection.title = unicode(form.title.data)
+ collection.description = unicode(form.description.data)
+ collection.slug = unicode(form.slug.data)
+
+ collection.save()
+
+ return redirect_obj(request, collection)
+
+ if request.user.is_admin \
+ and collection.creator != request.user.id \
+ and request.method != 'POST':
+ messages.add_message(
+ request, messages.WARNING,
+ _("You are editing another user's collection. Proceed with caution."))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/edit_collection.html',
+ {'collection': collection,
+ 'form': form})
+
+
+@require_active_login
+def change_pass(request):
+ form = forms.ChangePassForm(request.form)
+ user = request.user
+
+ if request.method == 'POST' and form.validate():
+
+ if not auth_lib.bcrypt_check_password(
+ form.old_password.data, user.pw_hash):
+ form.old_password.errors.append(
+ _('Wrong password'))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/change_pass.html',
+ {'form': form,
+ 'user': user})
+
+ # Password matches
+ user.pw_hash = auth_lib.bcrypt_gen_password_hash(
+ form.new_password.data)
+ user.save()
+
+ messages.add_message(
+ request, messages.SUCCESS,
+ _('Your password was changed successfully'))
+
+ return redirect(request, 'mediagoblin.edit.account')
+
+ return render_to_response(
+ request,
+ 'mediagoblin/edit/change_pass.html',
+ {'form': form,
+ 'user': user})
diff --git a/mediagoblin/errormiddleware.py b/mediagoblin/errormiddleware.py
new file mode 100644
index 00000000..c6789f32
--- /dev/null
+++ b/mediagoblin/errormiddleware.py
@@ -0,0 +1,60 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from paste.exceptions.errormiddleware import make_error_middleware
+
+MGOBLIN_ERROR_MESSAGE = """\
+<div style="text-align:center;font-family: monospace">
+ <h1>YEOWCH... that's an error!</h1>
+ <pre>
+.-------------------------.
+| __ _ |
+| -, \_,------,_// |
+| <\ ,-- --.\ |
+| / (x ) ( X ) |
+| ' '--, ,--'\ |
+| / \ -v-v-u-v / |
+| . '.__.--__'.\ |
+| / ',___/ / \__/' |
+| | | ,'\_'/, || |
+| \_| | | | | || |
+| W',_ ||| |||_'' |
+| | '------'| |
+| |__| |_|_ |
+| ,,,-' '-,,, |
+'-------------------------'
+ </pre>
+ <p>Something bad happened, and things broke.</p>
+ <p>If this is not your website, you may want to alert the owner.</p>
+ <br><br>
+ <p>
+ Powered... er broken... by
+ <a href="http://www.mediagoblin.org">MediaGoblin</a>,
+ a <a href="http://www.gnu.org">GNU Project</a>.
+ </p>
+</div>"""
+
+
+def mgoblin_error_middleware(app, global_conf, **kw):
+ """
+ MediaGoblin wrapped error middleware.
+
+ This is really just wrapping the error middleware from Paste.
+ It should take all of Paste's default options, so see:
+ http://pythonpaste.org/modules/exceptions.html
+ """
+ kw['error_message'] = MGOBLIN_ERROR_MESSAGE
+ return make_error_middleware(app, global_conf, **kw)
diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py
new file mode 100644
index 00000000..d8156126
--- /dev/null
+++ b/mediagoblin/gmg_commands/__init__.py
@@ -0,0 +1,108 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import argparse
+import os
+
+from mediagoblin.tools.common import import_component
+
+
+SUBCOMMAND_MAP = {
+ 'shell': {
+ 'setup': 'mediagoblin.gmg_commands.shell:shell_parser_setup',
+ 'func': 'mediagoblin.gmg_commands.shell:shell',
+ 'help': 'Run a shell with some tools pre-setup'},
+ 'adduser': {
+ 'setup': 'mediagoblin.gmg_commands.users:adduser_parser_setup',
+ 'func': 'mediagoblin.gmg_commands.users:adduser',
+ 'help': 'Creates an user'},
+ 'makeadmin': {
+ 'setup': 'mediagoblin.gmg_commands.users:makeadmin_parser_setup',
+ 'func': 'mediagoblin.gmg_commands.users:makeadmin',
+ 'help': 'Makes user an admin'},
+ 'changepw': {
+ 'setup': 'mediagoblin.gmg_commands.users:changepw_parser_setup',
+ 'func': 'mediagoblin.gmg_commands.users:changepw',
+ 'help': 'Changes a user\'s password'},
+ 'dbupdate': {
+ 'setup': 'mediagoblin.gmg_commands.dbupdate:dbupdate_parse_setup',
+ 'func': 'mediagoblin.gmg_commands.dbupdate:dbupdate',
+ 'help': 'Set up or update the SQL database'},
+ 'assetlink': {
+ 'setup': 'mediagoblin.gmg_commands.assetlink:assetlink_parser_setup',
+ 'func': 'mediagoblin.gmg_commands.assetlink:assetlink',
+ 'help': 'Link assets for themes and plugins for static serving'},
+ # 'theme': {
+ # 'setup': 'mediagoblin.gmg_commands.theme:theme_parser_setup',
+ # 'func': 'mediagoblin.gmg_commands.theme:theme',
+ # 'help': 'Theming commands',
+ # }
+
+ ## These might be useful, mayyyybe, but don't really work anymore
+ ## due to mongo change and the "versatility" of sql options.
+ ##
+ ## For now, commenting out. Might re-enable soonish?
+ #
+ # 'env_export': {
+ # 'setup': 'mediagoblin.gmg_commands.import_export:import_export_parse_setup',
+ # 'func': 'mediagoblin.gmg_commands.import_export:env_export',
+ # 'help': 'Exports the data for this MediaGoblin instance'},
+ # 'env_import': {
+ # 'setup': 'mediagoblin.gmg_commands.import_export:import_export_parse_setup',
+ # 'func': 'mediagoblin.gmg_commands.import_export:env_import',
+ # 'help': 'Imports the data for this MediaGoblin instance'},
+ }
+
+
+def main_cli():
+ parser = argparse.ArgumentParser(
+ description='GNU MediaGoblin utilities.')
+ parser.add_argument(
+ '-cf', '--conf_file', default=None,
+ help=(
+ "Config file used to set up environment. "
+ "Default to mediagoblin_local.ini if readable, "
+ "otherwise mediagoblin.ini"))
+
+ subparsers = parser.add_subparsers(help='sub-command help')
+ for command_name, command_struct in SUBCOMMAND_MAP.iteritems():
+ if 'help' in command_struct:
+ subparser = subparsers.add_parser(
+ command_name, help=command_struct['help'])
+ else:
+ subparser = subparsers.add_parser(command_name)
+
+ setup_func = import_component(command_struct['setup'])
+ exec_func = import_component(command_struct['func'])
+
+ setup_func(subparser)
+
+ subparser.set_defaults(func=exec_func)
+
+ args = parser.parse_args()
+ args.orig_conf_file = args.conf_file
+ if args.conf_file is None:
+ if os.path.exists('mediagoblin_local.ini') \
+ and os.access('mediagoblin_local.ini', os.R_OK):
+ args.conf_file = 'mediagoblin_local.ini'
+ else:
+ args.conf_file = 'mediagoblin.ini'
+
+ args.func(args)
+
+
+if __name__ == '__main__':
+ main_cli()
diff --git a/mediagoblin/gmg_commands/assetlink.py b/mediagoblin/gmg_commands/assetlink.py
new file mode 100644
index 00000000..148ebe9e
--- /dev/null
+++ b/mediagoblin/gmg_commands/assetlink.py
@@ -0,0 +1,151 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from mediagoblin import mg_globals
+from mediagoblin.init import setup_global_and_app_config
+from mediagoblin.gmg_commands import util as commands_util
+from mediagoblin.tools.theme import register_themes
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.tools.common import simple_printer
+from mediagoblin.tools import pluginapi
+
+
+def assetlink_parser_setup(subparser):
+ # theme_subparsers = subparser.add_subparsers(
+ # dest=u"subcommand",
+ # help=u'Assetlink options')
+
+ # # Install command
+ # install_parser = theme_subparsers.add_parser(
+ # u'install', help=u'Install a theme to this mediagoblin instance')
+ # install_parser.add_argument(
+ # u'themefile', help=u'The theme archive to be installed')
+
+ # theme_subparsers.add_parser(
+ # u'assetlink',
+ # help=(
+ # u"Link the currently installed theme's assets "
+ # u"to the served theme asset directory"))
+ pass
+
+
+###########
+# Utilities
+###########
+
+def link_theme_assets(theme, link_dir, printer=simple_printer):
+ """
+ Returns a list of string of text telling the user what we did
+ which should be printable.
+ """
+ link_dir = link_dir.rstrip(os.path.sep)
+ link_parent_dir = os.path.dirname(link_dir)
+
+ if theme is None:
+ printer(_("Cannot link theme... no theme set\n"))
+ return
+
+ def _maybe_unlink_link_dir():
+ """unlink link directory if it exists"""
+ if os.path.lexists(link_dir) \
+ and os.path.islink(link_dir):
+ os.unlink(link_dir)
+ return True
+
+ return
+
+ if theme.get('assets_dir') is None:
+ printer(_("No asset directory for this theme\n"))
+ if _maybe_unlink_link_dir():
+ printer(
+ _("However, old link directory symlink found; removed.\n"))
+ return
+
+ _maybe_unlink_link_dir()
+
+ # make the link directory parent dirs if necessary
+ if not os.path.lexists(link_parent_dir):
+ os.makedirs(link_parent_dir)
+
+ os.symlink(
+ theme['assets_dir'].rstrip(os.path.sep),
+ link_dir)
+ printer("Linked the theme's asset directory:\n %s\nto:\n %s\n" % (
+ theme['assets_dir'], link_dir))
+
+
+def link_plugin_assets(plugin_static, plugins_link_dir, printer=simple_printer):
+ """
+ Arguments:
+ - plugin_static: a mediagoblin.tools.staticdirect.PluginStatic instance
+ representing the static assets of this plugins' configuration
+ - plugins_link_dir: Base directory plugins are linked from
+ """
+ # link_dir is the final directory we'll link to, a combination of
+ # the plugin assetlink directory and plugin_static.name
+ link_dir = os.path.join(
+ plugins_link_dir.rstrip(os.path.sep), plugin_static.name)
+
+ # make the link directory parent dirs if necessary
+ if not os.path.lexists(plugins_link_dir):
+ os.makedirs(plugins_link_dir)
+
+ # See if the link_dir already exists.
+ if os.path.lexists(link_dir):
+ # if this isn't a symlink, there's something wrong... error out.
+ if not os.path.islink(link_dir):
+ printer(_('Could not link "%s": %s exists and is not a symlink\n') % (
+ plugin_static.name, link_dir))
+ return
+
+ # if this is a symlink and the path already exists, skip it.
+ if os.path.realpath(link_dir) == plugin_static.file_path:
+ # Is this comment helpful or not?
+ printer(_('Skipping "%s"; already set up.\n') % (
+ plugin_static.name))
+ return
+
+ # Otherwise, it's a link that went to something else... unlink it
+ printer(_('Old link found for "%s"; removing.\n') % (
+ plugin_static.name))
+ os.unlink(link_dir)
+
+ os.symlink(
+ plugin_static.file_path.rstrip(os.path.sep),
+ link_dir)
+ printer('Linked asset directory for plugin "%s":\n %s\nto:\n %s\n' % (
+ plugin_static.name,
+ plugin_static.file_path.rstrip(os.path.sep),
+ link_dir))
+
+
+def assetlink(args):
+ """
+ Link the asset directory of the currently installed theme and plugins
+ """
+ mgoblin_app = commands_util.setup_app(args)
+ app_config = mg_globals.app_config
+
+ # link theme
+ link_theme_assets(mgoblin_app.current_theme, app_config['theme_linked_assets_dir'])
+
+ # link plugin assets
+ ## ... probably for this we need the whole application initialized
+ for plugin_static in pluginapi.hook_runall("static_setup"):
+ link_plugin_assets(
+ plugin_static, app_config['plugin_linked_assets_dir'])
diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py
new file mode 100644
index 00000000..fa25ecb2
--- /dev/null
+++ b/mediagoblin/gmg_commands/dbupdate.py
@@ -0,0 +1,132 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+from sqlalchemy.orm import sessionmaker
+
+from mediagoblin.db.open import setup_connection_and_db_from_config
+from mediagoblin.db.migration_tools import MigrationManager
+from mediagoblin.init import setup_global_and_app_config
+from mediagoblin.tools.common import import_component
+
+_log = logging.getLogger(__name__)
+logging.basicConfig()
+_log.setLevel(logging.DEBUG)
+
+def dbupdate_parse_setup(subparser):
+ pass
+
+
+class DatabaseData(object):
+ def __init__(self, name, models, migrations):
+ self.name = name
+ self.models = models
+ self.migrations = migrations
+
+ def make_migration_manager(self, session):
+ return MigrationManager(
+ self.name, self.models, self.migrations, session)
+
+
+def gather_database_data(media_types, plugins):
+ """
+ Gather all database data relevant to the extensions we have
+ installed so we can do migrations and table initialization.
+
+ Returns a list of DatabaseData objects.
+ """
+ managed_dbdata = []
+
+ # Add main first
+ from mediagoblin.db.models import MODELS as MAIN_MODELS
+ from mediagoblin.db.migrations import MIGRATIONS as MAIN_MIGRATIONS
+
+ managed_dbdata.append(
+ DatabaseData(
+ u'__main__', MAIN_MODELS, MAIN_MIGRATIONS))
+
+ # Then get all registered media managers (eventually, plugins)
+ for media_type in media_types:
+ models = import_component('%s.models:MODELS' % media_type)
+ migrations = import_component('%s.migrations:MIGRATIONS' % media_type)
+ managed_dbdata.append(
+ DatabaseData(media_type, models, migrations))
+
+ for plugin in plugins:
+ try:
+ models = import_component('{0}.models:MODELS'.format(plugin))
+ except ImportError as exc:
+ _log.debug('No models found for {0}: {1}'.format(
+ plugin,
+ exc))
+
+ models = []
+ except AttributeError as exc:
+ _log.warning('Could not find MODELS in {0}.models, have you \
+forgotten to add it? ({1})'.format(plugin, exc))
+ models = []
+
+ try:
+ migrations = import_component('{0}.migrations:MIGRATIONS'.format(
+ plugin))
+ except ImportError as exc:
+ _log.debug('No migrations found for {0}: {1}'.format(
+ plugin,
+ exc))
+
+ migrations = {}
+ except AttributeError as exc:
+ _log.debug('Cloud not find MIGRATIONS in {0}.migrations, have you \
+forgotten to add it? ({1})'.format(plugin, exc))
+ migrations = {}
+
+ if models:
+ managed_dbdata.append(
+ DatabaseData(plugin, models, migrations))
+
+
+ return managed_dbdata
+
+
+def run_dbupdate(app_config, global_config):
+ """
+ Initialize or migrate the database as specified by the config file.
+
+ Will also initialize or migrate all extensions (media types, and
+ in the future, plugins)
+ """
+
+ # Gather information from all media managers / projects
+ dbdatas = gather_database_data(
+ app_config['media_types'],
+ global_config.get('plugins', {}).keys())
+
+ # Set up the database
+ db = setup_connection_and_db_from_config(app_config, migrations=True)
+
+ Session = sessionmaker(bind=db.engine)
+
+ # Setup media managers for all dbdata, run init/migrate and print info
+ # For each component, create/migrate tables
+ for dbdata in dbdatas:
+ migration_manager = dbdata.make_migration_manager(Session())
+ migration_manager.init_or_migrate()
+
+
+def dbupdate(args):
+ global_config, app_config = setup_global_and_app_config(args.conf_file)
+ run_dbupdate(app_config, global_config)
diff --git a/mediagoblin/gmg_commands/import_export.py b/mediagoblin/gmg_commands/import_export.py
new file mode 100644
index 00000000..d51a1e3e
--- /dev/null
+++ b/mediagoblin/gmg_commands/import_export.py
@@ -0,0 +1,254 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin import mg_globals
+from mediagoblin.db.open import setup_connection_and_db_from_config
+from mediagoblin.storage.filestorage import BasicFileStorage
+from mediagoblin.init import setup_storage, setup_global_and_app_config
+
+import shutil
+import tarfile
+import tempfile
+import subprocess
+import os.path
+import os
+import sys
+import logging
+from contextlib import closing
+
+_log = logging.getLogger('gmg.import_export')
+logging.basicConfig()
+_log.setLevel(logging.INFO)
+
+
+def import_export_parse_setup(subparser):
+ # TODO: Add default
+ subparser.add_argument(
+ 'tar_file')
+ subparser.add_argument(
+ '--mongodump_path', default='mongodump',
+ help='mongodump binary')
+ subparser.add_argument(
+ '--mongorestore_path', default='mongorestore',
+ help='mongorestore binary')
+ subparser.add_argument(
+ '--cache_path',
+ help='Temporary directory where files will be temporarily dumped')
+
+
+def _import_media(db, args):
+ '''
+ Import media files
+
+ Must be called after _import_database()
+ '''
+ _log.info('-> Importing media...')
+
+ media_cache = BasicFileStorage(
+ args._cache_path['media'])
+
+ # TODO: Add import of queue files
+ queue_cache = BasicFileStorage(args._cache_path['queue'])
+
+ for entry in db.MediaEntry.find():
+ for name, path in entry.media_files.items():
+ _log.info('Importing: {0} - {1}'.format(
+ entry.title.encode('ascii', 'replace'),
+ name))
+
+ media_file = mg_globals.public_store.get_file(path, mode='wb')
+ media_file.write(
+ media_cache.get_file(path, mode='rb').read())
+
+ _log.info('...Media imported')
+
+
+def _import_database(db, args):
+ '''
+ Restore mongo database from ___.bson files
+ '''
+ _log.info('-> Importing database...')
+
+ p = subprocess.Popen([
+ args.mongorestore_path,
+ '-d', db.name,
+ os.path.join(args._cache_path['database'], db.name)])
+
+ p.wait()
+
+ _log.info('...Database imported')
+
+
+def env_import(args):
+ '''
+ Restore mongo database and media files from a tar archive
+ '''
+ if not args.cache_path:
+ args.cache_path = tempfile.mkdtemp()
+
+ setup_global_and_app_config(args.conf_file)
+
+ # Creates mg_globals.public_store and mg_globals.queue_store
+ setup_storage()
+
+ global_config, app_config = setup_global_and_app_config(args.conf_file)
+ db = setup_connection_and_db_from_config(
+ app_config)
+
+ tf = tarfile.open(
+ args.tar_file,
+ mode='r|gz')
+
+ tf.extractall(args.cache_path)
+
+ args.cache_path = os.path.join(
+ args.cache_path, 'mediagoblin-data')
+ args = _setup_paths(args)
+
+ # Import database from extracted data
+ _import_database(db, args)
+
+ _import_media(db, args)
+
+ _clean(args)
+
+
+def _setup_paths(args):
+ '''
+ Populate ``args`` variable with cache subpaths
+ '''
+ args._cache_path = dict()
+ PATH_MAP = {
+ 'media': 'media',
+ 'queue': 'queue',
+ 'database': 'database'}
+
+ for key, val in PATH_MAP.items():
+ args._cache_path[key] = os.path.join(args.cache_path, val)
+
+ return args
+
+
+def _create_archive(args):
+ '''
+ Create the tar archive
+ '''
+ _log.info('-> Compressing to archive')
+
+ tf = tarfile.open(
+ args.tar_file,
+ mode='w|gz')
+
+ with closing(tf):
+ tf.add(args.cache_path, 'mediagoblin-data/')
+
+ _log.info('...Archiving done')
+
+
+def _clean(args):
+ '''
+ Remove cache directory
+ '''
+ shutil.rmtree(args.cache_path)
+
+
+def _export_check(args):
+ '''
+ Run security checks for export command
+ '''
+ if os.path.exists(args.tar_file):
+ overwrite = raw_input(
+ 'The output file already exists. '
+ 'Are you **SURE** you want to overwrite it? '
+ '(yes/no)> ')
+ if not overwrite == 'yes':
+ print 'Aborting.'
+
+ return False
+
+ return True
+
+
+def _export_database(db, args):
+ _log.info('-> Exporting database...')
+
+ p = subprocess.Popen([
+ args.mongodump_path,
+ '-d', db.name,
+ '-o', args._cache_path['database']])
+
+ p.wait()
+
+ _log.info('...Database exported')
+
+
+def _export_media(db, args):
+ _log.info('-> Exporting media...')
+
+ media_cache = BasicFileStorage(
+ args._cache_path['media'])
+
+ # TODO: Add export of queue files
+ queue_cache = BasicFileStorage(args._cache_path['queue'])
+
+ for entry in db.MediaEntry.find():
+ for name, path in entry.media_files.items():
+ _log.info(u'Exporting {0} - {1}'.format(
+ entry.title,
+ name))
+ try:
+ mc_file = media_cache.get_file(path, mode='wb')
+ mc_file.write(
+ mg_globals.public_store.get_file(path, mode='rb').read())
+ except Exception as e:
+ _log.error('Failed: {0}'.format(e))
+
+ _log.info('...Media exported')
+
+
+def env_export(args):
+ '''
+ Export database and media files to a tar archive
+ '''
+ if args.cache_path:
+ if os.path.exists(args.cache_path):
+ _log.error('The cache directory must not exist '
+ 'before you run this script')
+ _log.error('Cache directory: {0}'.format(args.cache_path))
+
+ return False
+ else:
+ args.cache_path = tempfile.mkdtemp()
+
+ args = _setup_paths(args)
+
+ if not _export_check(args):
+ _log.error('Checks did not pass, exiting')
+ sys.exit(0)
+
+ globa_config, app_config = setup_global_and_app_config(args.conf_file)
+
+ setup_storage()
+
+ db = setup_connection_and_db_from_config(app_config)
+
+ _export_database(db, args)
+
+ _export_media(db, args)
+
+ _create_archive(args)
+
+ _clean(args)
diff --git a/mediagoblin/gmg_commands/shell.py b/mediagoblin/gmg_commands/shell.py
new file mode 100644
index 00000000..4998acd7
--- /dev/null
+++ b/mediagoblin/gmg_commands/shell.py
@@ -0,0 +1,76 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import code
+
+from mediagoblin import mg_globals
+from mediagoblin.gmg_commands import util as commands_util
+
+
+def shell_parser_setup(subparser):
+ subparser.add_argument(
+ '--ipython', help='Use ipython',
+ action="store_true")
+
+
+SHELL_BANNER = """\
+GNU MediaGoblin shell!
+----------------------
+Available vars:
+ - mgoblin_app: instantiated mediagoblin application
+ - mg_globals: mediagoblin.globals
+ - db: database instance
+"""
+
+def py_shell(**user_namespace):
+ """
+ Run a shell using normal python shell.
+ """
+ code.interact(
+ banner=SHELL_BANNER,
+ local=user_namespace)
+
+
+def ipython_shell(**user_namespace):
+ """
+ Run a shell for the user using ipython. Return False if there is no IPython
+ """
+ try:
+ from IPython import embed
+ except:
+ return False
+
+ embed(
+ banner1=SHELL_BANNER,
+ user_ns=user_namespace)
+ return True
+
+def shell(args):
+ """
+ Setup a shell for the user either a normal Python shell or an IPython one
+ """
+ user_namespace = {
+ 'mg_globals': mg_globals,
+ 'mgoblin_app': commands_util.setup_app(args),
+ 'db': mg_globals.database}
+
+ if args.ipython:
+ ipython_shell(**user_namespace)
+ else:
+ # Try ipython_shell first and fall back if not available
+ if not ipython_shell(**user_namespace):
+ py_shell(**user_namespace)
diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py
new file mode 100644
index 00000000..024c8498
--- /dev/null
+++ b/mediagoblin/gmg_commands/users.py
@@ -0,0 +1,103 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.gmg_commands import util as commands_util
+from mediagoblin.auth import lib as auth_lib
+from mediagoblin import mg_globals
+
+def adduser_parser_setup(subparser):
+ subparser.add_argument(
+ '--username','-u',
+ help="Username used to login")
+ subparser.add_argument(
+ '--password','-p',
+ help="Your supersecret word to login, beware of storing it in bash history")
+ subparser.add_argument(
+ '--email','-e',
+ help="Email to receive notifications")
+
+
+def adduser(args):
+ #TODO: Lets trust admins this do not validate Emails :)
+ commands_util.setup_app(args)
+
+ args.username = commands_util.prompt_if_not_set(args.username, "Username:")
+ args.password = commands_util.prompt_if_not_set(args.password, "Password:",True)
+ args.email = commands_util.prompt_if_not_set(args.email, "Email:")
+
+ db = mg_globals.database
+ users_with_username = \
+ db.User.find({
+ 'username': args.username.lower(),
+ }).count()
+
+ if users_with_username:
+ print u'Sorry, a user with that name already exists.'
+
+ else:
+ # Create the user
+ entry = db.User()
+ entry.username = unicode(args.username.lower())
+ entry.email = unicode(args.email)
+ entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password)
+ entry.status = u'active'
+ entry.email_verified = True
+ entry.save()
+
+ print "User created (and email marked as verified)"
+
+
+def makeadmin_parser_setup(subparser):
+ subparser.add_argument(
+ 'username',
+ help="Username to give admin level")
+
+
+def makeadmin(args):
+ commands_util.setup_app(args)
+
+ db = mg_globals.database
+
+ user = db.User.one({'username': unicode(args.username.lower())})
+ if user:
+ user.is_admin = True
+ user.save()
+ print 'The user is now Admin'
+ else:
+ print 'The user doesn\'t exist'
+
+
+def changepw_parser_setup(subparser):
+ subparser.add_argument(
+ 'username',
+ help="Username used to login")
+ subparser.add_argument(
+ 'password',
+ help="Your NEW supersecret word to login")
+
+
+def changepw(args):
+ commands_util.setup_app(args)
+
+ db = mg_globals.database
+
+ user = db.User.one({'username': unicode(args.username.lower())})
+ if user:
+ user.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password)
+ user.save()
+ print 'Password successfully changed'
+ else:
+ print 'The user doesn\'t exist'
diff --git a/mediagoblin/gmg_commands/util.py b/mediagoblin/gmg_commands/util.py
new file mode 100644
index 00000000..6a6853d5
--- /dev/null
+++ b/mediagoblin/gmg_commands/util.py
@@ -0,0 +1,40 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin import app
+import getpass
+
+
+def setup_app(args):
+ """
+ Setup the application after reading the mediagoblin config files
+ """
+ mgoblin_app = app.MediaGoblinApp(args.conf_file)
+
+ return mgoblin_app
+
+def prompt_if_not_set(variable, text, password=False):
+ """
+ Checks if the variable is None and prompt for a value if it is
+ """
+ if variable is None:
+ if not password:
+ variable=raw_input(text + u' ')
+ else:
+ variable=getpass.getpass(text + u' ')
+
+ return variable
diff --git a/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..543830c8
--- /dev/null
+++ b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..1f086613
--- /dev/null
+++ b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1256 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Jiyda <jiydam@gmail.com>, 2013
+# Majid Al-Dharrab <majid@aldharrab.com>, 2011
+# minaeid90 <minaeid90@gmail.com>, 2013
+# OmarKH <Omar.w.kh@gmail.com>, 2011
+# OsamaK <osamak@gnu.org>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Arabic (http://www.transifex.com/projects/p/mediagoblin/language/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "اسم المستخدم"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "كلمة السر"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "عنوان البريد الإلكتروني"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "اسم المستخدم او الايميل"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "اسم مستخدم او ايميل غير صحيح."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "هذا الحقل لا يأخذ ايميل."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "هذا الحقل يحتاج ايميل."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "عفوًا، التسجيل غير متاح هنا."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "عذرًا، لقد اختار مستخدم آخر هذا الاسم."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "عذرًا، لقد اختار مستخدم آخر هذا الايميل."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "تم التحقق من بريدك الإلكتروني. يمكنك الآن الولوج، وتحرير ملفك الشخصي، ونشر الصور!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "مفتاح التحقق أو معرف المستخدم خاطئ"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "يجب عليك تسجيل الدخول لإرسال بريد الكترونى لك!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "لقد قمت بالفعل بالتحقق من عنوان البريد الإلكتروني الخاص بك!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "أعدنا إرسال رسالة التحقق."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "إذا كان هذا الايميل(حساس للحروف الكبيرة والصغيرة!) مُسجل, فقد تم إرسال ايميل به تعليمات عن كيفية تغيير رقمك السري."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "لم نتمكن من العثور على أحد له أسم المستخدم هذا."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "لقد تم إرسال ايميل به تعليمات عن كيفية تغيير رقمك السري."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "تعذر إرسال رسالة استعادة كلمة السر لأن اسم المستخدم معطل أو لأننا لم نتحقق من بريدك الإلكتروني."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "تستطيع الآن الدخول باستخدام رقمك السري الجديد."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "العنوان"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "وصف هذا العمل."
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "بامكانك استخدام ⏎\n<a href=\"http://daringfireball.net/projects/markdown/basics\">⏎\nMarkdown</a> للإدراج."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "الوسوم"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "قم بفصل المحددات بفصلة."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "المسار"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "لا يمكن ترك المسار فارغًا"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "مقدمة عنوان هذه الميديا, غالبا لن تحتاج لتغيره."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "ترخيص"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "السيرة"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "الموقع الإلكتروني"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "العنوان يحتوي على اخطاء"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "تفضيل رخصة"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "سوف تكون هذه رخصتك المبدئية في نماذج التحميل."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "ارسل لي رسالة عندما يقوم الاخرون بالتعليق على الميديا خاصتي"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "لا يمكن ترك العنوان فارغًا"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "وصف هذه المجموعة"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "مقدمة عنوان هذه المجموعة, غالبا لن تحتاج لتغيره."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr " كلمة السر القديمة"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "قم بإدخال رقمك السري القديم حتى تثبت انك صاحب هذا الحساب."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "رقم سري جديد"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "يوجد ملف آخر بهذا المسار لدى هذى المستخدم."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "أنت تحرّر وسائط مستخدم آخر. كن حذرًا أثناء العملية."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "لقد قمت بإضافة مرفقة %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "يمكنك فقط تعديل حسابك الخاص"
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "أنت تحرّر ملف مستخدم آخر. كن حذرًا أثناء العملية."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "تم حفظ تغيرات حسابك"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "تم حفظ خصائص حسابك"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "يجب عليك تأكيد إلغاء حسابك."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "أنت لديك مجموعة تدعى \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "توجد مجموعة اخرى بهذا المسار لهذا المستخدم."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "أنت تعدل مجموعة مستخدم آخر. كن حذرًا أثناء العملية."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "كلمة سر خاطئة"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "لم يتم ربط الثيم... لاتوجد مجموعة ثيمات\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "لا يوجد مسار جيد لهذا الثيم\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "ولكن, الرابط القديم للمسار الذي تم ايجاده; حُذف.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "CSRF كوكيز غير موجودة, وهذا من الممكن ان يكون نتيجة لمانع الكوكيز او شئ من هذا القبيل.<br/>تأكد من أنك قمت بالسماح لخصائص الكوكيز لهذا الميدان."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "عذرا, انا لا ادعم هذا النوع من الملفات :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "فشل في تحويل الفيديو"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "المكان"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "عرض في <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "سماح"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "رفض"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "الاسم"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "اسم العميل المنشِئ"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "الوصف"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "سوف يكون هذا مرئي بالنسبة للمستخدمين حتى يتاح\nللبرنامج خاصتك بالتصديق عليهم."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "النوع"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>سري</strong> - يستطيع العميل\nان يقوم بطلب نسخة من GNU MediaGoblin والتي من الممكن ان \nيعترضه وكيل المستخدم (مثلا الخادم من جانب العميل).<br />\n<strong>عام</strong> - لا يستطيع العميل ارسال طلبات سرية\nلنسخة من GNU MediaGoblin (مثلا \nخادم الجافا سكريبت من جانب العميل)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "تحويل لينك"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "الرابط الموجه للبرنامج, هذا الحقل\n<strong>مطلوب</strong> لجمهور العملاء."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "هذا الحقل مطلوب لجمهور العملاء"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "العميل {0} تم تسجيله!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "ارتباطات العميل المنشئ"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "عميلك المنشئ"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "اضف"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "الملف المعطى لهذا النوع من الميديا غير صحيح."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "الملف"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "يجب أن تضع ملفًا."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "يا سلام! نُشرَت!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "تم إضافة المجموعة \"%s\"!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "تأكد من بريدك الإلكترونى!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "تسجيل خروج"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "تسجيل دخول"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "<a href=\"%(user_url)s\">%(user_name)s</a>'s حساب"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "تغيير خصائص الحساب"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "لوحة معالجة الوسائط"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "تسجيل خروج"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "أضف وسائط"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "إنشاء مجموعة جديدة"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "صورة قزم مرتبك"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "أحدث الوسائط"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "يمكنك متابعة عملية معالجة وسائط معرضك من هنا."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "توجد وسائط تحت المعالجة"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "لا توجد وسائط تحت المعالجة"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "فشلت معالجة هذه الملفات:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "لا توجد مداخل فاشلة!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "آخر 10 تحويلات ناجحة"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "لا يوجد مداخل مُعالجة بعد! "
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "قم بضبط رقمك السري الجديد"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "قم بضبط رقم سري"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "استعادة كلمة السر"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "ارسل تعليمات"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "مرحبًا يا %(username)s،\n\nإن أردت تغيير كلمة سرك في غنو ميدياغوبلن فافتح الوصلة التالية في متصفحك:\n\n%(verification_url)s\n\nإن كنت ترى أن هذه الرسالة وصلتك خطأً فتجاهلها واستمتع بحياتك!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "فشل الولوج!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "ألا تملك حسابًا بعد؟"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "أنشئ حسابًا هنا!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "أنسيت كلمة سرك؟"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "أنشئ حسابًا!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "أنشئ"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "أهلًا يا %(username)s،\n\nافتح الرابط التالي\nفي متصفحك لتفعيل حسابك في غنو ميدياغوبلن:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "برعاية <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> مشروع."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "تم النشر وفقا ل <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Source code</a> متاح."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "استكشف"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "اهلا, مرحبا بك في موقع MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "هذا الموقع يقوم بتشغيل <a href=\"http://mediagoblin.org\">MediaGoblin</a>, وهو برنامج استضافة ميديا فائق الروعة."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "لكي تضيف الميديا خاصتك, تضع التعليقات, والمزيد, يجب عليك الدخول بحساب MediaGoblin الخاص بك."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "ليس لديك واحد حتى الآن؟ انه سهل!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "شعار ميدياغوبلن"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "تعديل المرفقات ل %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "مرفقات"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "أضف مرفقة"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "ألغِ"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "احفظ التغييرات"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "هل تريد فعلا إلغاء المستخدم '%(user_name)s' وكل الميديا/التعليقات المتعلقة به؟"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "نعم, قم بإلغاء حسابي"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "احذف نهائيًا"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "تحرير %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "نغيير %(username)s خصائص الحساب"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "إلغِ حسابي"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "تحرير %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "تحرير ملف %(username)s الشخصي"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "يتم تحديد الميديا ب: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "تحميل"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "أصلي"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "عذرا, لن يتم تشغيل الصوت لأن ⏎\n»متصفحك لا يدعم HTML5 ⏎\n»صوتيا."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "تستطيع الحصول على متصفح حديث ⏎\n»يمكنه تشغيل الصوت في <a href=\"http://getfirefox.com\">⏎\n» http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "ملف أصلي"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "ملف WebM (Vorbic كوديك)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "صورة ل%(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "تبديل التدوير"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "منظور"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "مقدمة"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "أعلى"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "جانب"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "تحميل نموذج"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "بنية الملف"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "طول الكائن"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "عذرا, لن يتم تشغيل هذا الفيديو لأن ⏎\n»متصفحك لا يدعم HTML5 ⏎\n»فيديو."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "تستطيع الحصول على متصفح حديث ⏎\n»يمكنه تشغيل هذا الفيديو في <a href=\"http://getfirefox.com\">⏎\n» http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM ملف (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "إضافة مجموعة"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "اضف الميديا الخاصة بك"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (%(username)s's مجموعة)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s بواسطة <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "تعديل"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "إلغاء"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "أتود حقًا حذف %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "هل تريد فعلا إلغاء %(media_title)s من %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "إلغاء"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "%(username)s's مجموعات"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>'s مجموعات"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "اهلا, %(username)s,\n%(comment_author)s قام بالتعليق على مشاركتك (%(comment_url)s) في %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "%(username)s ميديا"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "<a href=\"%(user_url)s\">\n%(username)s\n</a>\n's ميديا بالمحدد\n<a href=\"%(tag_url)s\">\n%(tag)s\n</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "وسائط <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ اختيار الميديا بواسطة <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "أضف تعليق"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "اضف هذا التعليق"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "إضافة “%(media_title)s” لمجموعة"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "إضافة مجموعة جديدة"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "يمكنك متابعة عملية معالجة وسائط معرضك من هنا."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "آخر 10 تحميلات ناجحة خاصة بك"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "ملف %(username)s الشخصي"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "عذرًا، تعذر العثور على مستخدم بهذا الاسم."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "يجب التحقق من البريد الإلكتروني"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "أوشكنا على الانتهاء! ما زال حسابك بحاجة إلى التفعيل."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "ستصلك رسالة إلكترونية خلال لحظات بها التعليمات."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "إن لم تصل."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "أعد إرسال رسالة التحقق"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "سجّل أحدهم حسابًا بهذا الاسم، ولكننا بانتظار التفعيل حتى الآن."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "إن كنت أنت ذلك الشخص لكنك فقدت رسالة التحقق، يمكنك <a href=\"%(login_url)s\">الولوج</a> وإعادة إرسالها."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "هذه زاوية لتخبر الآخرين فيها عن نفسك."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "حرِّر الملف الشخصي"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "لم يعبئ هذا العضو بيانات ملفه بعد."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "تحديد مجموعة"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "أظهِر كل وسائط %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "هنا ستظهر وسائطك، ولكن يبدو أنك لم تضف شيئًا بعد."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "لا يبدو أنه توجد أي وسائط هنا حتى الآن..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(إلغاء)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "تم تجميعه في"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "إضافة مجموعة"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "ايقونة تغذية"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "تغذية ذرية"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "جميع الحقوق محفوظة"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "اجدد←"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "→اقدم"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "اذهب إلى صفحة:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "اجدد"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "اقدم"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "تحدد ب"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "لم نستطيع قراءة هذه الصورة."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "ويحي!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "حدث خطأ"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "غير مسموح بهذه العملية"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "عذرا ديف, لا استطيع ترك تفعل هذا!</p><p>لقد حاولت تشغيل خاصية ليست مسموحة لك. هل كنت تحاول إلغاء جميع حسابات المستخدمين مجددا؟"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "يبدو أنه لا توجد صفحة بهذا العنوان, عذرا</p><p>إذا كنت متأكد من صحة العنوان, من الممكن أن تكون الصفحة التي تبحث عنها قد تم نقلها أو إلغاءها."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "تعليق"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "بامكانك استخدام <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> للإدراج."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "أنا متأكد من رغبتي بحذف هذا العمل"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "أنا متأكد من أنني أريد إلغاء هذه المادة من المجموعة"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "مجموعة"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- إختار --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "إدراج ملاحظة"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "قام بالتعليق على مشاركتك"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "عذرا, لقد قمت بادخال تعليق فارغ."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "لقد تم إرسال تعليقك!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "من فضلك قم بفحص المداخل وقم بالمحاولة مرة أخرى."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "يجب عليك إختيار أو إضافة مجموعة"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" توجد بالفعل في المجموعة \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" أُضيفت للمجموعة \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "لقد قمت بإلغاء الميديا."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "لم يتم إلغاء الميديا لأنك لم تقم بإختيار انك متأكد من ذلك."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "أنت على وشك حذف وسائط مستخدم آخر. كن حذرًا أثناء العملية."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "لقد قمت بإلغاء المادة من المجموعة."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "لم يتم إلغاء المادة لأنك لم تقم بإختيار انك متأكد من ذلك."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "أنت على وشك حذف مادة من مجموعة مستخدم آخر. كن حذرا."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "لقد قمت بإلغاء المجموعة \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "لم يتم إلغاء المجموعة لأنك لم تقم بإختيار انك متأكد من ذلك."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "أنت على وشك حذف مجموعة مستخدم آخر. كن حذرا."
diff --git a/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..ec01d7f7
--- /dev/null
+++ b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..9ebbdf18
--- /dev/null
+++ b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1254 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Al fred <devaleitzer@aim.com>, 2011
+# Al fred <devaleitzer@aim.com>, 2011
+# skarbat <skarbat@gmail.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Catalan (http://www.transifex.com/projects/p/mediagoblin/language/ca/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ca\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Nom d'usuari"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Contrasenya"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Adreça electrònica"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Nom d'usuari o correu"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Ho sentim, el registre està desactivat en aquest cas."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Lamentablement aquest usuari ja existeix."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Perdó, ja existeix un usuari amb aquesta adreça de correu."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Ja s'ha verificat la vostra adreça electrònica. Ara podeu entrar, editar el vostre perfil i penjar imatge!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "La clau de verificació o la identificació de l'usuari no són correctes."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Has d'estar conectat per saber a qui hem d'enviar el correu!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Ja has verificat la teva adreça de correu!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Torna'm a enviar el correu de verificació"
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "S'ha enviat un correu amb instruccions de com cambiar la teva contrasenya"
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "No hem pogut enviar el correu de recuperació de contrasenya perquè el teu nom d'usuari és inactiu o bé l'adreça electrònica del teu compte no ha sigut verificada."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Ara et pots conectar amb la teva nova contrasenya."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Títol"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Descripció d'aquest treball."
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Pots utilitzar⏎ <a href=\"http://daringfireball.net/projects/markdown/basics\">⏎ Markdown</a> per donar-li format"
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etiquetes"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Separa els tags amb comes."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Llimac"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "El llimac no pot ésser buit"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "El títol de l'adreça d'aquest mitjà. Normalment no necessites modificar això."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Llicència"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Biografia"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Lloc web"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Aquesta adreça conté errors"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Envia'm correu quan d'altres comentin al meu mitjà"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "El títol no pot ser buit"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Descripció d'aquesta col.lecció"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "La part del títol de l'adreça d'aquesta col.lecció. Normalment no cal que canviis això."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Contrasenya antiga"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Introdueix la teva contrasenya antiga per comprovar que aquest compte és teu."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nova contrasenya"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Ja existeix una entrada amb aquest llimac per aquest usuari"
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Esteu editant fitxers d'un altre usuari. Aneu amb compte."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Esteu editant el perfil d'un usuari. Aneu amb compte"
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Els canvis al perfil s'han guardat"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Els detalls del compte s'han guardat"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Ja tens una col.lecció anomenada \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Estas editant la col.lecció d'un altre usuari. Prossegueix amb cautela."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Contrasenya errònia"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "No es pot enllaçar el tema... no hi ha tema establert\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Tot i així, l'enllaç antic al directori s'ha trobat; eliminat.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Ho sento, no puc manegar aquest tipus d'arxiu :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "La transformació del vídeo ha fallat"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Ubicació"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Veure a <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Permetre"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Denegar"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nom"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "El nom del client OAuth"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Descripció"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Això serà visiable a usuaris que permetin que la teva aplicació\n s'autentifiqui com a ells."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Tipus"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Confidencial</strong> - El client pot\n fer peticions a la instància GNU MediaGoblin que no pot ésser\n interceptada per l'agent d'usuari (el client a la part servidor).<br />\n <strong>Public</strong> - El client no pot fer peticions \n confidencials a la instància GNU MediaGoblin (la part \n client JavaScript)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Redireccionar URI "
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "La URI de redirecció per les aplicacions, aquest camp\n és <strong>requeriment</strong> per els clients públics."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Aquest camp és requeriment per a clients públics"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "El client {0} ha sigut enregistrat!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Afegir"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Aquest tipus de fitxer no és vàlid."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Fitxer"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Heu d'escollir un fitxer."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Visca! S'ha enviat!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "S'ha afegit la col.leccio \"%s\"!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifica el teu correu electrònic"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Entra"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Modificar els ajustaments del compte"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Quadre de processament de fitxers"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Tots els fitxers"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Mitjans més recents"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Aqui pots seguir l'estat del mitjà que s'està processant a aquesta instància."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "S'està processant el fitxer"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "No s'està processant cap mitjà"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "No s'han pogut penjar els següents fitxers:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Sense entrades fallades!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Les últimes 10 pujades correctes"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Encara no hi ha entrades processades!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Estableix la teva nova contrasenya"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Establir contrasenya"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Recuperar contrasenya"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Enviar instruccions"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Hola %(username)s,⏎ ⏎ per cambiar la teva contrasenya de GNU MediaGoblin, obre la següent URL al ⏎ teu navegador:⏎ ⏎ %(verification_url)s⏎ ⏎ Si creus que hi ha un error, ignora el correu i continua essent⏎ un goblin feliç!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Inici de sessió ha fallat!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Encara no teniu un compte?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Creeu-ne un aquí!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Has oblidat la teva contrasenya?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Creeu un compte!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Crea"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Hi %(username)s,\n\nto activate your GNU MediaGoblin account, open the following URL in\nyour web browser:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Alliberat segons la <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Codi font</a> disponible."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Explorar"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Hola, una benvinguda al MediaGoblin!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "El lloc esta usant <a href=\"http://mediagoblin.org\">MediaGoblin</a>, una gran i extraordinària peça de software per allotjar mitjans."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Per afegir el teu propi mitjà, col.locar comentaris, i més, pots conectar-te amb el teu compte MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "No en tens una encara? Es fàcil!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Logo de mediagoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Editant afegits per a %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Cancel·la"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Desa els canvis"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Esborrar permanentment"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Edició %(media_title)s "
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Modificant els detalls del compte de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Editant %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Editant perfil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Mitjà marcat amb: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Descarregar"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Ho sento, aquest audiothis àudio no funcionarà perque \n »el teu navegador web no contempla suport d'àudio \n »HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Pots obtenir un navegador web modern que \n »podrà reproduir l'àudio, a <a href=\"http://getfirefox.com\">\n » http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Arxiu original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "Arxiu WebM (Vorbis codec)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Imatge per %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "Arxiu WebM (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Afegir a la col.lecció"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Afegeix el teu mitjà"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (la col.lecció de %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s per a <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Editar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Esborrar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Realment vols esborrar %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Relment eliminar %(media_title)s de %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Eliminar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Hola %(username)s,\n%(comment_author)s ha comentat el teu post (%(comment_url)s) a %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Mitjà de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Navegant mitjà per a <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Afegeix un comentari"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Afegir aquest comentari"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Afegir una nova col.lecció"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Aqui pots seguir l'estat del mitjà que s'està processant per la teva galeria"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Les teves 10 últimes pujades correctes"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Perfil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Lamentablement no s'ha trobat l'usuari que cercàveu."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Cal que verifiqueu l'adreça electrònica"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Gairebé esteu! Tan sols falta que activeu el vostre compte"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Us hauria d'arribar un correu amb les instruccions per a fer-ho."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Per si no hi fos:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Torna'm a enviar el correu de verificació"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Algú ja ha registrat un compte amb aquest nom d'usuari, però encara l'ha d'activar."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Si siu aqeust usuari però heu perdut el correu de verificació, podeu <a href=\"%(login_url)s\">entrar</a> i tornar-lo a enviar."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Aqui hi ha un espai per explicar de tu als demés"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Edita el perfil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Aquest usuari encara no ha escrit res al seu perfil."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "View all of %(username)s's media"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Aqui és on apareixerà el teu mitjà, però sembla que encara no hi has afegit res."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Sembla que no hi ha cap mitjà aqui encara..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "Icona RSS"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Tots els drets reservats"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Més nou"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Més antic →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Anar a la pàgina:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "més nou"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "més antic"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "No s'ha pogut llegir l'arxiu d'imatge"
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Ups!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Pots usar <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> per donar format."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Estic segur que vull esborrar això"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Estic segur que vull esborrar aquest element de la col.lecció"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Sel.leccionar --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Incluir una nota"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "comentat al teu post"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Uups, el teu comentari era buit."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "El teu comentari s'ha publicat!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Si et plau, comprova les teves entrades i intenta-ho de nou."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Has de sel.leccionar o afegir una col.lecció"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" ja és a la col.lecció \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" afegir a la col.lecció \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Has esborrat el mitjà"
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "El mitjà no s'ha esborrat perque no has marcat que n'estiguessis segur."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Ets a punt d'esborrar el mitjà d'un altre usuari. Prossegueix amb cautela."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Has esborrat l'element de la col.lecció"
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "L'element no s'ha eliminat perque no has marcat que n'estiguessis segur."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Ets a punt d'esborrar un element de la col.lecció d'un altre usuari. Prossegueix amb cautela."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Has esborrat la col.lecció \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "La col.lecció no s'ha esborrat perquè no has marcat que n'estiguessis segur."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Ets a punt d'esborrar la col.lecció d'un altre usuari. Prossegueix amb cautela."
diff --git a/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..53e3fedf
--- /dev/null
+++ b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..c78c08ac
--- /dev/null
+++ b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1254 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Morten Juhl-Johansen Zölde-Fejér <morten@writtenandread.net>, 2012
+# Olle Jonsson <olle.jonsson@gmail.com>, 2012
+# ttrudslev <tanja.trudslev@gmail.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Danish (http://www.transifex.com/projects/p/mediagoblin/language/da/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: da\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Brugernavn"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Kodeord"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Email adresse"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Brugernavn eller email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Desværre, registrering er ikke muligt på denne instans"
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Desværre, det brugernavn er allerede brugt"
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Desværre, en bruger er allerede oprettet for den email"
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Din email adresse er blevet bekræftet. Du kan nu logge på, ændre din profil, og indsende billeder!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Bekræftelsesnøglen eller brugerid er forkert"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Du er nødt til at være logget ind, så vi ved hvem vi skal emaile!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Du har allerede bekræftet din email adresse!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Email til godkendelse sendt igen."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "En email er blevet sendt med instruktioner til at ændre dit kodeord."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Vi kunne ikke sende en kodeords nulstillings email da dit brugernavn er inaktivt, eller din konto's email adresse er ikke blevet godkendt."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Du kan nu logge ind med dit nye kodeord."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titel"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Beskrivelse af arbejdet"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Du kan bruge\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> til formattering."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Tags"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Separer tags med kommaer."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Titeldelen af dette medie's adresse. Du behøver normalt ikke ændre dette."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licens"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Bio"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Websted"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Denne adresse indeholder fejl"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Email mig når andre kommenterer på mine medier"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Titlen kan ikke være tom"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Beskrivelse af denne samling"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Titeldelen af denne samlings's adresse. Du behøver normalt ikke ændre dette."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Gammelt kodeord"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Skriv dit gamle kodeord for at bevise det er din konto."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Ny kodeord"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Du er ved at ændre en anden brugers' medier. Pas på."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Du er ved at ændre en bruger's profil. Pas på."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Profilændringer gemt"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Kontoindstillinger gemt"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Du har allerede en samling ved navn \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Du er ved at ændre en anden bruger's samling. Pas på."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Forkert kodeord"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Kan ikke linke til tema... intet tema sat\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Desværre, jeg understøtter ikke den filtype :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Tillad"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Forbyd"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Navn"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Navnet af OAuth klienten"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Beskrivelse"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Type"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Dette felt er nødvendigt for offentlige klienter"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Klienten {0} er blevet registreret!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Forkert fil for medietypen."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Fil"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Du må give mig en fil"
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Juhuu! Delt!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Bekræft din email!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Log ind"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Har du endnu ikke en konto?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Opret en her!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Opret en konto!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Udforsk"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Hey, velkommen til denne MediaGoblin side!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "For at tilføje dine egne medier, skrive kommentarer, og mere, du kan logge ind med din MediaGoblin konto."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Har du ikke en endnu? Det er let!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin logo"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Afbryd"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Gem ændringer"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Redigerer %(username)s profil"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Desværre, fandt ikke den bruger."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Næsten færdig! Din konto skal stadig aktiveres."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Der skulle komme email om et par øjeblikke med instrukser om hvordan."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Hvis det ikke gør:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Gensend verificeringsemail"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Her kan du fortælle andre om dig selv."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Ret profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Hovsa!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..e2fcf85d
--- /dev/null
+++ b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..e2147070
--- /dev/null
+++ b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1265 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# piratenpanda <benjamin@lebsanft.org>, 2011
+# cwebber <cwebber@dustycloud.org>, 2011
+# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011-2012
+# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2013
+# Jakob Kramer <jakob.kramer@gmx.de>, 2011, 2012
+# Jakob Kramer <jakob.kramer@gmx.de>, 2012-2013
+# Jan-Christoph Borchardt <hey@jancborchardt.net>, 2011
+# Jan-Christoph Borchardt <hey@jancborchardt.net>, 2011, 2012
+# Keyzo <kyoo@kyoo.ch>, 2011
+# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011
+# Art O. Pal <artopal@fastmail.fm>, 2011
+# spaetz <sebastian@sspaeth.de>, 2012
+# Vinzenz Vietzke <vinz@vinzv.de>, 2012
+# Vinzenz Vietzke <vinz@vinzv.de>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-28 10:43+0000\n"
+"Last-Translator: Elrond <elrond+mediagoblin.org@samba-tng.org>\n"
+"Language-Team: German (http://www.transifex.com/projects/p/mediagoblin/language/de/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Benutzername"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Passwort"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "E-Mail-Adresse"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Benutzername oder E-Mail-Adresse"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Benutzername oder E-Mail-Adresse"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Ungültiger Benutzername oder E-Mail-Adresse."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Dieses Feld akzeptiert keine E-Mail-Adressen."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Dieses Feld benötigt eine E-Mail-Adresse."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Benutzerregistrierung ist auf diesem Server leider deaktiviert."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Leider gibt es bereits einen Benutzer mit diesem Namen."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Leider gibt es bereits einen Benutzer mit dieser E-Mail-Adresse."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Dein GNU MediaGoblin Konto wurde hiermit aktiviert. Du kannst dich jetzt anmelden, dein Profil bearbeiten und Medien hochladen."
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Der Aktivierungsschlüssel oder die Nutzerkennung ist falsch."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Du musst angemeldet sein, damit wir wissen, wer die Email bekommt."
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Deine E-Mail-Adresse wurde bereits aktiviert."
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Aktivierungsmail wurde erneut versandt."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Falls jemand mit dieser E-Mail-Adresse (Groß- und Kleinschreibung wird unterschieden!) registriert ist, wurde eine E-Mail mit Anleitungen verschickt, wie Du Dein Passwort ändern kannst."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Es konnte niemand mit diesem Benutzernamen gefunden werden."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Es wurde eine E-Mail mit der Anleitung zur Änderung des Passwortes an Dich gesendet."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Die E-Mail zur Wiederherstellung des Passworts konnte nicht verschickt werden, weil dein Benutzername inaktiv oder deine E-Mail-Adresse noch nicht aktiviert wurde."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Du kannst dich jetzt mit deinem neuen Passwort anmelden."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titel"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Beschreibung des Werkes"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Die Texte lassen sich durch <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> formatieren."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Schlagwörter"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Kommaseparierte Schlagwörter"
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Kurztitel"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Bitte gib einen Kurztitel ein"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Der Titelteil der Medienadresse. Normalerweise muss hier nichts geändert werden."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Lizenz"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Biographie"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Webseite"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Diese Adresse ist fehlerhaft"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Bevorzugte Lizenz"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Dies wird Deine Standardlizenz in den Upload-Forumularen sein."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Mir eine E-Mail schicken, wenn andere meine Medien kommentieren"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Der Titel kann nicht leer sein"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Beschreibung dieser Sammlung"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Der Titelteil dieser Sammlungsadresse. Du musst ihn normalerweise nicht ändern."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Altes Passwort"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Gib dein altes Passwort ein, um zu bestätigen, dass du dieses Konto besitzt."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Neues Passwort"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Diesen Kurztitel hast du bereits vergeben."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Du bearbeitest die Medien eines anderen Nutzers. Sei bitte vorsichtig."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Sie haben den Anhang %s hinzugefügt!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Du kannst nur dein eigenes Profil bearbeiten."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Du bearbeitest das Profil eines anderen Nutzers. Sei bitte vorsichtig."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Das Profil wurde aktualisiert"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Kontoeinstellungen gespeichert"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Du musst die Löschung deines Kontos bestätigen."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Du hast bereits eine Sammlung mit Namen »%s«!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Eine Sammlung mit diesem Kurztitel existiert bereits für diesen Benutzer."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Du bearbeitest die Sammlung eines anderen Benutzers. Sei vorsichtig."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Falsches Passwort"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Theme kann nicht verknüpft werden … Kein Theme gesetzt\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Für dieses Theme gibt es kein asset-Verzeichnis\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Trotzdem wurde eine alte Verknüpfung gefunden; sie wurde entfernt\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "Das CSRF cookie ist nicht vorhanden. Das liegt vermutlich an einem Cookie-Blocker oder ähnlichem.<br/>Bitte stelle sicher, dass Cookies von dieser Domäne erlaubt sind."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Entschuldigung, dieser Dateityp wird nicht unterstützt."
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Videokonvertierung fehlgeschlagen"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Aufnahmeort"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "In <a href=\"%(osm_url)s\">OpenStreetMap</a> öffnen"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Erlauben"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Verweigern"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Name"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Der Name des OAuth-Clients"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Beschreibung"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Dies wird für Benutzer sichtbar sein, die deiner\nAnwendung erlauben, sich als sie zu authentifizieren.."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Typ"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Vertraulich</strong> - Der Client kann\n Anfragen an die GNU MediaGoblin Instanz stellen, die nicht durch den \n Benutzer-Agent (z.B. serverseitiger Client) unterbunden werden können.<br />\n <strong>Öffentlich</strong> - Der Client kann keine vertraulichen \n Anfragen an die GNU MediaGoblin Instanz stellen (z.B. clientseitiger\n JavaScript Client)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Weiterleitungs-URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "Die Weiterleitungs-URI für die Anwendung, dieses Feld\n ist <strong>Pflicht</strong> für öffentliche Clients."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Dieses Feld ist Pflicht für öffentliche Clients"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Client {0} wurde registriert!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "OAuth-Client-Verbindungen"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Deine OAuth-Clients"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Hinzufügen"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Die Datei stimmt nicht mit dem gewählten Medientyp überein."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Datei"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Du musst eine Datei angeben."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "JAAA! Geschafft!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Sammlung »%s« hinzugefügt!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Bitte bestätige Deine E-Mail-Adresse!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "abmelden"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Anmelden"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "<a href=\"%(user_url)s\">%(user_name)s</a>s Konto"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Kontoeinstellungen ändern"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Medienverarbeitung"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Abmelden"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Medien hinzufügen"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Neues Album erstellen"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Bild eines gestressten Goblins"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Neuste Medien"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Hier kann man den Status von zu verarbeitenden Medien in diesem MediaGoblin-Exemplar sehen."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Medien in Bearbeitung"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Keine Medien in Bearbeitung"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Die folgenden Uploads sind fehlgeschlagen:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Keine fehlgeschlagenen Einträge!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Die letzten zehn erfolgreichen Uploads"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Noch keine verarbeiteten Einträge!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Dein neues Passwort"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Passwort setzen"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Passwort wiederherstellen"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Anweisungen senden"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Hallo %(username)s,\n\num dein GNU-MediaGoblin-Passwort zu ändern, öffne folgende URL\nin deinem Webbrowser:\n\n%(verification_url)s\n\nWenn du denkst, dass es sich hierbei um einen Fehler handelt,\nignoriere einfach diese E-Mail und bleib ein glücklicher Goblin!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Anmeldevorgang fehlgeschlagen!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Hast du noch keines?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Registriere dich einfach hier!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Passwort vergessen?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Neues Nutzerkonto registrieren!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Registrieren"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Hallo %(username)s,\n\num deinNutzerkonto bei GNU MediaGoblin zu aktivieren, musst du folgende Adresse in deinem Webbrowser öffnen:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Läuft mit <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, einem <a href=\"http://gnu.org/\">GNU</a>-Projekt."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Veröffentlicht unter der <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a> (<a href=\"%(source_link)s\">Quellcode</a>)."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Entdecken"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Hallo du, willkommen auf dieser MediaGoblin-Seite!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Diese Webseite setzt <a href=\"http://mediagoblin.org\">MediaGoblin</a> ein, eine großartige Software für Medienhosting."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Melde Dich mit Deinem MediaGoblin-Konto an, um eigene Medien hinzuzufügen, andere zu kommentieren und vieles mehr."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Hast du noch keinen? Das geht ganz einfach!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Registriere dich auf dieser Seite</a> oder <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Installiere MediaGoblin auf deinem eigenen Server</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin Logo"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Bearbeite Anhänge von %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Anhänge"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Anhang hinzufügen"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Änderungen speichern"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Soll das Konto »%(user_name)s« und alle zu ihm gehörigen Medien / Kommentare wirklich gelöscht werden?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Ja, ich möchte mein Konto wirklich löschen"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Dauerhaft löschen"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "%(media_title)s bearbeiten"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "%(username)ss Kontoeinstellungen ändern"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Mein Konto löschen"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Bearbeite %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "%(username)ss Profil bearbeiten"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Medien mit Schlagwort: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Download"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Entschuldige, dieses Audiostück wird nicht funktionieren, weil dein Webbrowser kein HTML5-Audio unterstützt."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Hol dir auf <a href=\"http://getfirefox.com\">http://getfirefox.com</a> einen modernen Webbrowser, der dieses Audiostück abspielen kann!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Originaldatei"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM-Datei (Vorbis-Codec)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Bild für %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF-Datei"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspektive"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Vorderseite"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Modell herunterladen"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Dateiformat"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Objekthöhe"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Entschuldige, dieses Video wird nicht funktionieren, weil dein Webbrowser kein HTML5-Video unterstützt."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Hol dir auf <a href=\"http://getfirefox.com\">http://getfirefox.com</a> einen modernen Webbrowser, der dieses Video abspielen kann!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM-Datei (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Eine Sammlung hinzufügen"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Deine Medien"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (ein Album von %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s von <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Bearbeiten"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Löschen"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Möchtest du %(title)s wirklich löschen?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Wirklich »%(media_title)s« aus »%(collection_title)s« entfernen?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Entfernen"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Alben von %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Alben von <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Hallo %(username)s,\n%(comment_author)s hat dein Medium (%(comment_url)s) auf %(instance_name)s kommentiert.\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "%(username)ss Medien"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>s Medien mit dem Schlagwort <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>s Medien"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Medien von <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Einen Kommentar schreiben"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Kommentar absenden"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Hinzugefügt"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Originaldatum"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "»%(media_title)s« zu einem Album hinzufügen"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Eine neue Sammlung hinzufügen"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Du kannst hier den Status der Medien verfolgen, die sich gerade in Bearbeitung befinden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Deine zehn letzten erfolgreichen Uploads"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)ss Profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Dieser Benutzer konnte leider nicht gefunden werden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "E-Mail Aktivierung benötigt"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Fast fertig! Dein Konto muss noch freigeschaltet werden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Gleich solltest du eine E-Mail erhalten, die beschreibt was noch zu tun bleibt."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Falls sie nicht ankommt:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Aktivierungsmail erneut senden"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Jemand hat bereits ein Konto mit diesem Benutzernamen registriert, aber es muss noch aktiviert werden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Wenn dir dieses Konto gehört und die Aktivierungsmail verloren gegangen ist, kannst du dich <a href=\"%(login_url)s\">anmelden</a> und sie erneut senden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Hier kannst Du Dich selbst beschreiben."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Profil bearbeiten"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Dieser Benutzer hat (noch) keine Daten in seinem Profil."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Sammlungen durchstöbern"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Alle Medien von %(username)s anschauen"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Hier erscheinen deine Medien, sobald du etwas hochgeladen hast."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Scheinbar gibt es hier noch nichts …"
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(entfernen)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "In den Sammlungen"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Zu einer Sammlung hinzufügen"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "Feed-Symbol"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom-Feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Alle Rechte vorbehalten"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Neuere"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Ältere →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Zu Seite:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "neuer"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "älter"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Schlagwörter"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Die Bilddatei konnte nicht gelesen werden."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Hoppla!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Ein Fehler trat auf"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Funktion nicht erlaubt"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "So nicht!</p><p>Du wolltest eine Funktion verwenden zu der Du nicht die nötigen Rechte Rechte besitzt. Wolltest Du etwa schon wieder alle Nutzerkonten löschen?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Tut uns Leid, aber unter der angegebenen Adresse gibt es keine Seite!</p><p>Wenn du sicher bist, dass die Adresse stimmt, wurde die Seite eventuell verschoben oder gelöscht."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "Jahr"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "Monat"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "Woche"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "Tag"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "Stunde"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "Minute"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Kommentar"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Die Texte lassen sich durch <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> formatieren."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Ja, wirklich löschen"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Ich bin sicher, dass ich dieses Objekt aus der Sammlung entfernen möchte"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Album"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Auswählen --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Notiz anfügen"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "hat dein Medium kommentiert"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Hoppla, der Kommentartext fehlte."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Dein Kommentar wurde angenommen!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Bitte prüfe deinen Einträge und versuche erneut."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Du musst eine Sammlung auswählen oder hinzufügen"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "»%s« ist bereits in der Sammlung »%s«"
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "»%s« zur Sammlung »%s« hinzugefügt"
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Du hast das Medium gelöscht."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Das Medium wurde nicht gelöscht, da nicht angekreuzt hast, dass du es wirklich löschen möchtest."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Du versuchst Medien eines anderen Nutzers zu löschen. Sei bitte vorsichtig."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Du hast das Objekt aus der Sammlung gelöscht."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Das Objekt wurde nicht aus der Sammlung entfernt, weil du nicht bestätigt hast, dass du dir sicher bist."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Du bist dabei ein Objekt aus der Sammlung eines anderen Nutzers zu entfernen. Sei vorsichtig."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Du hast die Sammlung »%s« gelöscht"
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Die Sammlung wurde nicht gelöscht, weil du nicht bestätigt hast, dass du dir sicher bist."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Du bist dabei eine Sammlung eines anderen Nutzers zu entfernen. Sei vorsichtig."
diff --git a/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..1b22b786
--- /dev/null
+++ b/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1257 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2013-06-16 20:06-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+
+#: mediagoblin/auth/forms.py:25
+msgid "Username"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:29 mediagoblin/auth/forms.py:44
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:33
+msgid "Email address"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:40
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:51
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:42
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:43
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:44
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:109
+msgid "Sorry, a user with that name already exists."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:113
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:43
+msgid "Sorry, registration is disabled on this instance."
+msgstr ""
+
+#: mediagoblin/auth/views.py:133
+msgid ""
+"Your email address has been verified. You may now login, edit your "
+"profile, and submit images!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:139
+msgid "The verification key or user id is incorrect"
+msgstr ""
+
+#: mediagoblin/auth/views.py:157
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:165
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:178
+msgid "Resent your verification email."
+msgstr ""
+
+#: mediagoblin/auth/views.py:209
+msgid ""
+"If that email address (case sensitive!) is registered an email has been "
+"sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:220
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:223
+msgid "An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:230
+msgid ""
+"Could not send password recovery email as your username is inactive or "
+"your account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:287
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie "
+"blocker or somesuch.<br/>Make sure to permit the settings of cookies for "
+"this domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can "
+"not be\n"
+" intercepted by the user agent (e.g. server-side "
+"client).<br />\n"
+" <strong>Public</strong> - The client can't make "
+"confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-"
+"side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr ""
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr ""
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr ""
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr ""
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid "Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> "
+"project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a"
+" href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, "
+"an extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your"
+" MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an "
+"account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" "
+"href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on "
+"your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:171
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:187
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:193
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/image.html:36
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/image.html:39
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at "
+"%(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid "You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid "An email should arrive in a few moments with instructions on how to do so."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to"
+" be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can "
+"<a href=\"%(login_url)s\">log in</a> and resend it."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr ""
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're "
+"sure the address is correct, maybe the page you're looking for has been "
+"moved or deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> "
+"for formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed "
+"with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were "
+"sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid "You are about to delete another user's collection. Proceed with caution."
+msgstr ""
+
diff --git a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..645af16b
--- /dev/null
+++ b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..873869f0
--- /dev/null
+++ b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1255 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# aleksejrs <deletesoftware@yandex.ru>, 2013
+# aleksejrs <deletesoftware@yandex.ru>, 2011-2012
+# Fernando Inocencio <faigos@gmail.com>, 2011
+# tiguliano <john_w1954@fastmail.fm>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-01 21:16+0000\n"
+"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
+"Language-Team: Esperanto (http://www.transifex.com/projects/p/mediagoblin/language/eo/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: eo\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Uzantnomo"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Pasvorto"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Retpoŝtadreso"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Uzantonomo aŭ retpoŝtadreso"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Salutnomo aŭ retpoŝtadreso"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Nevalida ensalutnomo aŭ retpoŝtadreso."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Ĉi tiu kampo ne akceptas retpoŝtadresojn."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Ĉi tiu kampo postulas retpoŝtadreson."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Bedaŭrinde, registrado estas malaktivigita en tiu ĉi instalaĵo."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Bedaŭrinde, uzanto kun tiu nomo jam ekzistas."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Ni bedaŭras, sed konto kun tiu retpoŝtadreso jam ekzistas."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Via retpoŝtadreso estas konfirmita. Vi povas nun ensaluti, redakti vian profilon, kaj alŝuti bildojn!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "La kontrol-kodo aŭ la uzantonomo ne estas korekta"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Vi devas esti ensalutita, por ke ni sciu, al kiu sendi la retleteron!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Vi jam konfirmis vian retpoŝtadreson!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Resendi vian kontrol-mesaĝon."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Se tiu retpoŝtadreso (majuskloj gravas!) estas registrita, tien senditas retletero kun instrukcio pri kiel ŝanĝi vian pasvorton."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Trovitas neniu kun tiu ensalutnomo."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Senditas retletero kun instrukcio pri kiel ŝanĝi vian pasvorton."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Ni ne povas sendi pasvortsavan retleteron, ĉar aŭ via konto estas neaktiva, aŭ ĝia retpoŝtadreso ne estis konfirmita."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Nun vi povas ensaluti per via nova pasvorto."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titolo"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Priskribo de ĉi tiu verko"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Vi povas uzi por markado la lingvon\n «<a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>»."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etikedoj"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Dividu la etikedojn per komoj."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "La distingiga adresparto"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "La distingiga adresparto ne povas esti malplena"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "La dosiertitol-bazita parto de la dosieradreso. Ordinare ne necesas ĝin ŝanĝi."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Permesilo"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Bio"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Retejo"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Ĉi tiu adreso enhavas erarojn"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Permesila prefero"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Tiu ĉi permesilo estos antaŭelektita en la alŝutformularoj."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Retpoŝtu min kiam aliaj komentas pri miaj alŝutaĵoj."
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "La titolo ne povas malpleni."
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Priskribo de la kolekto"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "La distingiga adresparto de ĉi tiu kolekto. Ordinare ne necesas ĝin ŝanĝi."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "La malnova pasvorto"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Enigu vian malnovan pasvorton por pruvi, ke ĉi tiu konto estas via."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "La nova pasvorto"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Ĉi tiu uzanto jam havas dosieron kun tiu distingiga adresparto."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Vi priredaktas dosieron de alia uzanto. Agu singardeme."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Vi aldonis la kundosieron %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Vi povas redakti nur vian propran profilon."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Vi redaktas profilon de alia uzanto. Agu singardeme."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Profilŝanĝoj estis konservitaj"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Kontagordoj estis konservitaj"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Vi bezonas konfirmi la forigon de via konto."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Vi jam havas kolekton kun la nomo «%s»!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Ĉi tiu uzanto jam havas kolekton kun tiu distingiga adresparto."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Vi redaktas kolekton de alia uzanto. Agu singardeme."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Malĝusta pasvorto"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Via pasvorto estas sukcese ŝanĝita"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Alligo de etoso ne eblas… ne estas elektita ekzistanta etoso\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Mankas dosierujo kun aspektiloj por la etoso\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Tamen trovitas — kaj forigitas — malnova simbola ligilo al dosierujo.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Mi pardonpetas, mi ne subtenas tiun dosiertipon :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Malsukcesis transkodado de filmo"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Loko"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Vidi sur <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nomo"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "La nomo de la OAuth-kliento"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Priskribo"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Tipo"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Aldoni"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "La provizita dosiero ne konformas al la informtipo."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Dosiero"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Vi devas provizi dosieron."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Hura! Alŝutitas!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Kolekto «%s» aldonitas!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Konfirmu viecon de la retpoŝtadreso!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "elsaluti"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Ensaluti"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Konto de <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Ŝanĝi kontagordojn"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Kontrolejo pri dosierpreparado."
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Elsaluti"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Aldoni dosieron"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Krei novan kolekton"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Bildo de zorgigita koboldo"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Laste aldonitaj dosieroj"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Ĉi tie vi povas observi la staton de prilaborado de alŝutaĵoj en ĉi tiu servilo."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Dosieroj preparataj"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Neniu dosieroj preparatas"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Preparado de ĉi tiuj alŝutaĵoj malsukcesis:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Ne ekzistas malsukcesaj eroj!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "La dek lastaj sukcesaj alŝutoj"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Ankoraŭ ne ekzistas eroj prilaboritaj!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Enigu vian novan pasvorton"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Difini pasvorton"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Ekhavo de nova pasvorto"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Sendi instrukcion"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Saluton, %(username)s,\n\npor ŝanĝi vian pasvorton ĉe GNUa MediaGoblin, sekvu la jenan retadreson per via TTT-legilo:\n\n%(verification_url)s\n\nSe vi pensas, ke ĉi tiu retletero estas sendita erare, simple ignoru ĝin kaj plu restu feliĉa koboldo!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Ensaluto malsukcesis!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Ĉu ankoraŭ sen konto?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Kreu ĝin ĉi tie!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Ĉu vi forgesis vian pasvorton?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Kreu konton!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Krei"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Sal %(username)s,\n\npor aktivigi vian GNU MediaGoblin konton, malfermu la sekvantan URLon en via retumilo:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Funkcias per <a href=\"http://mediagoblin.org/\" title='Versio %(version)s'>MediaGoblin</a>, unu el la <a href=\"http://gnu.org/\">projektoj de GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Disponigita laŭ la permesilo <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. Haveblas<a href=\"%(source_link)s\">fontotekstaro</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Ĉirkaŭrigardi"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Saluton, kaj bonvenon al ĉi tiu MediaGoblina retpaĝaro!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Ĉi tiu retpaĝaro funkcias per <a href=\"http://mediagoblin.org\">MediaGoblin</a>, eksterordinare bonega programaro por gastigado de aŭd‐vid‐dosieroj."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Por aldoni viajn proprajn dosierojn, afiŝi komentariojn ktp, vi povas ensaluti je via MediaGoblina konto."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Ĉu vi ankoraŭ ne havas tian? Ne malĝoju!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Emblemo de MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Aldoni kundosierojn por %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Kundosieroj"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Aldoni kundosieron"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Nuligi"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Konservi ŝanĝojn"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Ŝanĝado de pasvorto de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Konservi"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Ĉu efektive forigi la uzantokonton «%(user_name)s» kaj ĉiujn ĝiajn dosierojn/komentojn?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Jes, efektive forigi mian konton"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Forigi senrevene"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Priredaktado de %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Ŝanĝado de kontagordoj de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Ŝanĝi la pasvorton"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Forigi mian konton."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Redaktado de %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Redaktado de l’profilo de %(username)s'"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Dosieroj kun etikedo: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Elŝuti"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Originalo"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Bedaŭrinde, ĉi tiu sonregistraĵo ne ludiĝos, \n\tĉar via TTT-legilo ne povas ludi\n\tsonaĵojn, afiŝitajn laŭ HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Vi povas akiri modernan TTT-legilon, kapablan \n\tsonigi la registraĵon ĉe <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "originalan dosieron"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebMan dosieron (kun Vorbisa kodaĵo)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Bildo de «%(media_title)s»"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF-dosiero"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Deantaŭe"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Desupre"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Deflanke"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Elŝuti la modelon"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Informaranĝo"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Alto de la objekto"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Bedaŭrinde, ĉi tiu filmo ne montriĝos\n ĉar via TTT-legilo ne subtenas sufiĉe\n filmojn laŭ HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Vi povas elŝuti modernan TTT-legilon, kapablan \n montri la filmon, de <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "la WebM-dosieron (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Aldonado de kolekto"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Aldono de via dosiero"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (kolekto de %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Ŝanĝi"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Forigi"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Ĉu vere forigi %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Ĉu vere forigi %(media_title)s el %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Forigi"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Kolektoj de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Kolektoj de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Saluton, %(username)s.\n%(comment_author)s komentis ĉe via alŝutaĵo (%(comment_url)s) ĉe %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Dosieroj de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Dosieroj de <a href=\"%(user_url)s\">%(username)s</a> kun la etikedo <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Dosieroj de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Просмотр файлов пользователя <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Aldoni komenton"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Aldoni ĉi tiun komenton"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "antaŭ %(formatted_time)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Aldonita"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Kreita"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Aldoni «%(media_title)s» al kolekto"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Aldoni novan kolekton"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Ĉi tie vi povas informiĝi pri la stato de preparado de dosieroj por via galerio."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Viaj 10 lastaj sukcesaj alŝutoj."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profilo de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Uzanto ne trovita."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Necesas konfirmo de retpoŝtadreso"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Preskaŭ finite! Restas nur validigi vian konton."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Post kelkaj momentoj devas veni retletero kun instrukcio pri kiel tion fari."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Se tio ne okazas:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Resendi kontrolmesaĝon"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Iu registris konton kun tiu ĉi uzantonomo, sed ĝi devas ankoraŭ esti aktivigita."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Se vi estas tiu sed vi perdis vian kontrolmesaĝon, vi povas <a href=\"%(login_url)s\">ensaluti</a> kaj resendi ĝin."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Jen estas spaceto por rakonti pri vi al aliaj."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Redakti profilon"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Ĉi tiu uzanto ne jam aldonis informojn pri si."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Vidi kolektojn"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Rigardi ĉiujn dosierojn de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Ĝuste ĉi tie aperos viaj dosieroj, sed vi ŝajne ankoraŭ nenion alŝutis."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Ĉi tie ŝajne estas ankoraŭ neniuj dosieroj…"
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(forigi)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "En kolektoj:"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Aldoni al kolekto"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "flusimbolo"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom-a informfluo"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Ĉiuj rajtoj estas rezervitaj"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Pli novaj"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Malpli novaj →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Iri al paĝo:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "pli nova"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "malpli nova"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Markita per"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Malsukcesis lego de la bildodosiero"
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Oj!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Okazis eraro"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "jaro(j)"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "monato(j)"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "semajno(j)"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "tago(j)"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "horo(j)"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "minuto(j)"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Komenti"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Vi povas uzi por markado la lingvon «<a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>»."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Jes, mi volas forigi ĉi tion."
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Jes, mi volas forigi ĉi tiun dosieron el la kolekto"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Kolekto"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Elektu --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Rimarko"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "komentis je via afiŝo"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Ve, komentado estas malebligita."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Oj, via komento estis malplena."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Via komento estis afiŝita!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Bonvolu kontroli vian enigitaĵon kaj reprovi."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Necesas elekti aŭ aldoni kolekton"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "«%s» jam estas en la kolekto «%s»"
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "«%s» estis aldonita al la kolekto «%s»"
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Vi forigis la dosieron."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "La dosiero ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Vi estas forigonta dosieron de alia uzanto. Estu singardema."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Vi forigis la dosieron el la kolekto."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "La dosiero ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Vi estas forigonta dosieron el kolekto de alia uzanto. Agu singardeme."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Vi forigis la kolekton «%s»"
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "La kolekto ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Vi estas forigonta kolekton de alia uzanto. Agu singardeme."
diff --git a/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..c5e50f53
--- /dev/null
+++ b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..8c2f046f
--- /dev/null
+++ b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1263 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# aleksejrs <deletesoftware@yandex.ru>, 2011, 2012
+# ekenbrand <ekenbrand@hotmail.com>, 2011
+# nvjacobo <jacobo@gnu.org>, 2011-2012
+# Javier Di Mauro <javierdimauro@gmail.com>, 2011
+# case <juangsub@gmail.com>, 2011
+# juanman <juanma@kde.org.ar>, 2011, 2012
+# larjona <larjona99@gmail.com>, 2012
+# larjona <larjona99@gmail.com>, 2013
+# Mario Rodriguez <msrodriguez00@gmail.com>, 2011
+# Manuel Urbano Santos <mu@member.fsf.org>, 2011
+# shackra <shackra@riseup.net>, 2012
+# Elesa <stardustprincess17@hotmail.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-02 21:23+0000\n"
+"Last-Translator: larjona <larjona99@gmail.com>\n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/mediagoblin/language/es/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Nombre de usuario"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Contraseña"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Dirección de correo electrónico"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Nombre de usuario o correo electrónico"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Nombre de usuario o email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Nombre de usuario o correo electrónico inválido."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Este campo no acepta direcciones de correo."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Este campo requiere una dirección de correo."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Lo sentimos, el registro está deshabilitado en este momento."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Lo sentimos, ya existe un usuario con ese nombre."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Lo sentimos, ya existe un usuario con esa dirección de email."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Tu dirección de correo electrónico ha sido verificada. ¡Ahora puedes iniciar sesión, editar tu perfil, y enviar imágenes!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "La clave de verificación o la identificación de usuario son incorrectas"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "¡Debes iniciar sesión para que podamos saber a quién le enviamos el correo electrónico!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "¡Ya has verificado tu dirección de correo!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Se reenvió tu correo electrónico de verificación."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Si esa dirección de correo (¡sensible a mayúsculas y minúsculas!) está registrada, se ha enviado un correo con instrucciones para cambiar la contraseña."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "No se ha podido encontrar a nadie con ese nombre de usuario."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Un correo electrónico ha sido enviado con instrucciones sobre cómo cambiar tu contraseña."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "No se pudo enviar un correo electrónico de recuperación de contraseñas porque tu nombre de usuario está inactivo o la dirección de su cuenta de correo electrónico no ha sido verificada."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Ahora tu puedes iniciar sesión usando tu nueva contraseña."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Título"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Descripción de esta obra"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Puedes usar\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> para el formato."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etiquetas"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Separa las etiquetas por comas."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Ficha"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "La ficha no puede estar vacía"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "El título de esta parte de la dirección de los contenidos. Por lo general no es necesario cambiar esto."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licencia"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Bio"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Sitio web"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "La dirección contiene errores"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Preferencias de licencia"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Ésta será tu licencia predeterminada en los formularios de subida."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Envíame un correo cuando otros escriban comentarios sobre mi contenido"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "El título no puede estar vacío"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Descripción de esta colección"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "El título de la dirección de esta colección. Generalmente no necesitas cambiar esto."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Vieja contraseña"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Escriba la anterior contraseña para demostrar que esta cuenta te pertenece."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nueva contraseña"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Una entrada con esa ficha ya existe para este usuario."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Estás editando el contenido de otro usuario. Procede con precaución."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "¡Has añadido el adjunto %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Sólo puedes editar tu propio perfil."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Estás editando un perfil de usuario. Procede con precaución."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Los cambios de perfil fueron salvados"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "las configuraciones de cuenta fueron salvadas"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Necesitas confirmar el borrado de tu cuenta."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "¡Ya tienes una colección llamada \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Una colección con esa ficha ya existe para este usuario/a."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Estás editando la colección de otro usuario/a. Ten cuidado."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Contraseña incorrecta"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Se ha cambiado la contraseña correctamente"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "No se puede enlazar al tema... no hay un tema seleccionado\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "No hay directorio activo para este tema\n\n\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Sin embargo, se encontró un enlace simbólico de un directorio antiguo; ha sido borrado.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "No se pudo enlazar \"%s\": %s existe y no es un enlace simbólico\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "Omitiendo \"%s\"; ya está establecido.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "Se encontró un enlace antiguo para \"%s\"; se eliminará.\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "No se encuentra la cookie CSRF. Esto suele ser debido a un bloqueador de cookies o similar.<br/> Por favor asegúrate de permitir las cookies para este dominio."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Lo sentidos, No soportamos ese tipo de archivo :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "ha fallado la ejecución de unoconv, comprueba el fichero de registro (log)"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Ha fallado la conversión de vídeo"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Locación"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Ver en <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Permitir"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Denegar"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nombre"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "El nombre del cliente OAuth"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Descripción"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Esto será visible para los usuarios que permitan tu aplicación\n\npara que puedan autenticarse."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Tipo"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Confidencial</strong> - El cliente puede hacer peticiones a la instancia GNU MediaGoblin que no pueden ser interceptadas por el agente de usuario (ejemplo: un cliente del lado del servidor).<br /><strong>Público</strong> - El cliente no puede hacer peticiones confidenciales a la instancia GNU MediaGoblin (ejemplo: un cliente JavaScript del lado del servidor)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Redireccionar URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "La URI para redireccionar las aplicaciones, este campo es <strong>requerido</strong> para los clientes públicos."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Este campo es requerido para los clientes públicos"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "¡El cliente {0} ha sido registrado!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "Conexiones de cliente OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Tus clientes OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Añadir "
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Archivo inválido para el formato seleccionado."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Archivo"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Debes proporcionar un archivo."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "¡Yuju! ¡Enviado!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "¡Colección \"%s\" añadida!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "¡Verifica tu email!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "cerrar sesión"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Iniciar sesión"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Cuenta de <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Cambiar la configuración de la cuenta"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Panel de procesamiento de contenido"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Cerrar sesión"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Añadir contenido"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Crear nueva colección"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Imagen de un goblin estresándose"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "El contenido más reciente"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Aquí puedes llevar un seguimiento del estado del contenido que se está procesando en esta instancia."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Procesando contenido"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "No hay contenidos en procesamiento"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Estos archivos no pudieron ser procesados:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "¡No han fallado entradas!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Últimos 10 envíos con éxito"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "¡Aún no hay entradas procesadas!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Coloca tu nueva contraseña "
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Coloca la contraseña"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Recuperar contraseña"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Enviar instrucciones"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Hola %(username)s,\n\nPara cambiar tu contraseña de GNU MediaGoblin, abre la siguiente URL en un navegador:\n\n%(verification_url)s \n\nSi piensas que esto es un error, simplemente ignora este mensaje y sigue siendo un duende feliz."
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "¡Hubo un fallo al iniciar sesión!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "¿No tienes una cuenta?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "¡Crea una aquí!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "¿Olvidaste tu contraseña?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "¡Crea una cuenta!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Crear"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Hola %(username)s,\n\npara activar tu cuenta de GNU MediaGoblin, abre la siguiente URL en tu navegador:\n\n%(verification_url)s "
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Funciona con <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, un proyecto <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Publicado bajo la <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\"> Código fuente</a> disponible."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Explorar"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Hola, ¡bienvenido a este sitio de MediaGoblin!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Este sitio está montado con <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un extraordinario programa libre para alojar, gestionar y compartir contenido multimedia."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Para añadir tus propios contenidos, dejar comentarios y más, puedes iniciar sesión con tu cuenta de MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "¿Aún no tienes una? ¡Es fácil!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Crear una cuenta en este sitio</a>\n o\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instalar MediaGoblin en tu propio servidor</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Logo de MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Editando archivos adjuntos a %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Adjuntos"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Agregar adjunto"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Guardar cambios"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Cambiando la contraseña de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Guardar"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "¿Realmente quieres borrar el usuario '%(user_name)s' y todos sus contenidos/comentarios?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Sí, borrar mi cuenta"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Eliminar permanentemente"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Editando %(media_title)s "
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Cambio de %(username)s la configuración de la cuenta "
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Cambiar tu contraseña."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Borrar mi cuenta"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Editando %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Editando el perfil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Contenido etiquetado con: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Descargar"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Lo sentimos, este audio audio no funcionará porque \n\ttu navegador web no soporta audio de \n\tHTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Tú puedes obtener un navegador más moderno que \n\tpueda reproducir el audio <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Archivo original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "Archivo WebM (códec Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Imágenes para %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "Fichero PDF"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Alternar Rotar"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspectiva"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Frente"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Arriba"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Lateral"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Descargar modelo"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Formato de Archivo"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Altura del Objeto"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Lo siento, este vídeo no funcionará\n porque tu navegador no soporta \n vídeo HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "¡Puedes conseguir un navegador moderno \n que pueda reproducir este vídeo en <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "Archivo WebM (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Añadir una colección"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Añade tu contenido "
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (%(username)s's collection)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s por <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Editar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Borrar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "¿Realmente deseas eliminar %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "¿Realmente quieres quitar %(media_title)s de %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Quitar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Colecciones de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Colecciones de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Hola %(username)s,\n%(comment_author)s comentó tu publicación (%(comment_url)s) en %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Contenido de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Contenido de <a href=\"%(user_url)s\">%(username)s</a> con etiqueta <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Contenido de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Explorando contenido de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Añadir un comentario"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Añade un comentario "
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "hace %(formatted_time)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Agregado"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Creado"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Añadir “%(media_title)s” a una colección"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Añadir una nueva colección"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Aquí puedes hacer un seguimiento del contenido que está siendo procesado."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Tus últimos 10 envíos exitosos"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Perfil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Lo sentimos, no se encontró ese usuario."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Es necesario que verifiques tu cuenta mediante el correo de notiicación"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "¡Casi hemos terminado! Solo falta activar la cuenta."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "En unos momentos te debería llegar un correo electrónico con las instrucciones para hacerlo."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "En caso de que no:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Reenviar correo electrónico de verificación"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Alguien ya registró una cuenta con ese nombre de usuario, pero todavía no ha sido activada."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Si tú eres esa persona, pero has perdido tu correo electrónico de verificación, puedes <a href=\"%(login_url)s\">iniciar sesión</a> y reenviarlo."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Aquí hay un lugar para que le cuentes a los demás sobre ti."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Editar perfil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Este usuario (todavía) no ha completado su perfil."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Explorar colecciones"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Ver todo el contenido de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Aquí es donde estará ubicado tu contenido, pero parece que aún no has añadido nada."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Parece que aún no hay ningún contenido aquí..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(borrar)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "En la colección"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Añadir a una colección"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "Icono feed"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Todos los derechos reservados"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Más nuevo"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Más viejo →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Ir a la página:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "Más nuevo"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "Más viejo"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Marcado con"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "No se pudo leer el archivo de imagen."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "¡Ups!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Ha ocurrido un error"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Operación no permitida"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "¡Lo siento Dave, no puedo permitir que hagas eso!</p><p>Has intentado realizar una operación no permitida. ¿Has vuelto a intentar borrar todas las cuentas de usuario?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Parece que no hay ninguna página en esta dirección. ¡Lo siento!</p><p>Si estás seguro de que la dirección es correcta, quizá han borrado o movido la página que estás buscando."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "año"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "mes"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "semana"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "día"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "hora"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "minuto"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Comentario"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Puedes usar <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> para el formato."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Estoy seguro de que quiero borrar esto"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Estoy seguro/a de que quiero quitar este ítem de la colección"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Colección"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Selecciona --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Incluir una nota"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "comentó tu publicación"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Lo siento, los comentarios están desactivados."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Ups, tu comentario estaba vacío."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "¡Tu comentario ha sido publicado!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Por favor, revisa tus entradas e inténtalo de nuevo."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Tienes que seleccionar o añadir una colección"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "%s\" ya está en la colección \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" añadido a la colección \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Eliminaste el contenido"
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "El contenido no se eliminó porque no marcaste que estabas seguro."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Estás a punto de eliminar un contenido de otro usuario. Procede con precaución."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Borraste el ítem de la colección."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "El ítem no fue removido porque no confirmaste que estuvieras seguro/a."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Estás a punto de borrar un ítem de la colección de otro usuario. Procede con cuidado."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Borraste la colección \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "La colección no fue borrada porque no confirmaste que estuvieras seguro/a."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Estás a punto de borrar la colección de otro usuario. Procede con cuidado."
diff --git a/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..3422ad97
--- /dev/null
+++ b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..08e73e1a
--- /dev/null
+++ b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1252 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Numb <amir007ag@gmail.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Persian (http://www.transifex.com/projects/p/mediagoblin/language/fa/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: fa\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "نام کاربری"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "گذرواٰژه"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "آدرس ایمیل"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "متاسفانه،ثبتنام به طور موقت غیر فعال است."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "متاسفانه کاربری با این نام کاربری وجود دارد."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "ایمیل شما تایید شد.شما می توانید حالا وارد شوید،نمایه خود را ویرایش کنید و تصاویر خود را ثبت کنید!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "این کد تاییدیه یا شناسه کاربری صحیح نیست."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "ایمیل تاییدیه باز ارسال شد."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "عنوان"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "برچسب"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "زندگینامه"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "وبسایت"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "شما در حال ویرایش رسانه کاربر دیگری هستید.با احتیاط عمل کنید"
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "شما در حال ویرایش نمایه کاربر دیگری هستید.با احتیاط عمل کنید."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "فایلی نا معتبر برای نوع رسانه داده شده."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "فایل"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "شما باید فایلی ارايه بدهید."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "هورا!ثبت شد!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "ورود"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "پنل رسیدگی به رسانه ها"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "ورود با خطا انجام شد!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "آیا حساب کاربری ندارید؟"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "در اینجا یکی بسازید!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "ساخت یک حساب کاربری!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "ساختن"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "سلام %(username)s,\n\nبرای فعال سازی شناسه کاربری گنو مدیاگوبلین خود ،پیوند زیر را در مرورگر خود باز کنید.\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "لوگو مدیاگوبلین"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "انصراف"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "ذخیره تغییرات"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "ویرایش %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "در حال ویرایش نمایه %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>'s رسانه های"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "نمایه %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "متاسفانه کاربر مورد نظر یافت نشد."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "به زودی ایمیلی حاوی شرح کار ها برای شما ارسال خواهد شد."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "در این حالت وجود ندارد."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "باز ارسال ایمیل تاییدیه"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "اگر شما آن کاربر هستید و ایمیل تایید خود را گم کرده اید،, می توانید <a href=\"%(login_url)s\">log in</a> و دوباره آنرا بفرستید.."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "ویرایش نمایه"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "نمایش تمامی رسانه های %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "اوه"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..7bc860a0
--- /dev/null
+++ b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..6103c439
--- /dev/null
+++ b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1261 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# ianux <a5565930@nepwk.com>, 2011
+# alcazar <alexispay@gmail.com>, 2012
+# chesuidayeur <chesuidayeur@yahoo.fr>, 2011
+# Bibit <crash_bibit@hotmail.com>, 2013
+# joehillen <joehillen@gmail.com>, 2011
+# hellpe <hell_pe@no-log.org>, 2013
+# MarkTraceur <marktraceur@gmail.com>, 2011
+# maxineb <maxineb@members.fsf.org>, 2011
+# joar <transifex@wandborg.se>, 2011
+# Valentin Villenave <valentin@villenave.net>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: French (http://www.transifex.com/projects/p/mediagoblin/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Nom d'utilisateur"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Mot de passe"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Adresse e-mail"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Nom d'utilisateur ou email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Nom d'utilisateur ou adresse de courriel invalide."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "L'inscription n'est pas activée sur ce serveur, désolé."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Un utilisateur existe déjà avec ce nom, désolé."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Désolé, il existe déjà un utilisateur ayant cette adresse e-mail."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Votre adresse e-mail a bien été vérifiée. Vous pouvez maintenant vous identifier, modifier votre profil, et soumettre des images !"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "La clé de vérification ou le nom d'utilisateur est incorrect."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Vous devez être authentifié afin que nous sachions à qui envoyer l'e-mail !"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Votre adresse e-mail a déjà été vérifiée !"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "E-mail de vérification renvoyé."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Nom d'utilisateur introuvable."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Un email contenant les instructions pour changer votre mot de passe viens de vous être envoyé"
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Impossible d'envoyer un email de récupération de mot de passe : votre compte est inactif ou bien l'email de votre compte n'a pas été vérifiée."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titre"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Descriptif pour ce travail"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Vous pouvez utiliser\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> pour le formattage."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Tags"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Séparez les champs avec des virgules."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Légende"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "La légende ne peut pas être laissée vide."
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Le titre présent dans l'URL du média. Vous n'avez généralement pas besoin de le modifier"
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licence"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Bio"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Site web"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Cette adresse contiens des erreurs"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Me prévenir par email lorsque d'autres commentent mes médias"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Le titre ne peut être vide"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Description de cette collection"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Le titre affiché dans l'URL de la collection. Vous n'avez généralement pas besoin d'y toucher."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Ancien mot de passe."
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Entrez votre ancien mot de passe pour prouver que vous êtes bien le propriétaire de ce compte."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nouveau mot de passe"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Une entrée existe déjà pour cet utilisateur avec la même légende."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Vous vous apprêtez à modifier le média d'un autre utilisateur. Veuillez prendre garde."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Vous avez ajouté la pièce jointe %s !"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Vous ne pouvez modifier que votre propre profil."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Vous vous apprêtez à modifier le profil d'un utilisateur. Veuillez prendre garde."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Les changements apportés au profile ont étés sauvegardés"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Les changements des préférences du compte ont étés sauvegardés"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Vous devez confirmer la suppression de votre compte."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Vous avez déjà une collection appelée \"%s\" !"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Vous éditez la collection d'un autre utilisateurs. Faites attention."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Mauvais mot de passe"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Impossible de lier le thème... Aucun thème associé\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Aucun répertoire \"asset\" pour ce thème\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Désolé, mais je ne prends pas en charge cette extension de fichier :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "L'encodage de la vidéo à échoué"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Position"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Regarder sur <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Autoriser"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Refuser"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nom"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Description"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Type"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "URL de redirection"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "L'URI de redirection pour l'application, ce champ est <strong>requis</strong> pour les clients publics"
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Ce champ est requis pour les clients publics"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Le client {0} as été enregistré !"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Ajouter"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Le fichier envoyé ne correspond pas au type de média."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Fichier"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Il vous faut fournir un fichier."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Youhou, c'est envoyé !"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Collection \"%s\" ajoutée !"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Vérifiez votre adresse e-mail !"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "Déconnexion"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "S'identifier"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Changer les paramètres du compte"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Panneau pour le traitement des médias"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Ajouter des médias"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Créer une nouvelle collection"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Tout derniers media"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Ici, vous pouvez suivre l'état des médias en cours de traitement par cette instance."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Médias en transformation"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Aucun média en transformation"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Le traitement de ces ajouts a échoué :"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Aucune entrée ayant échoué !"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "10 derniers envois terminés"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Aucune entrée traitée jusqu'à présent !"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Enregistrez votre nouveau mot de passe"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Enregistrez votre mot de passe"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Récupérer le mot de passe"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Envoyer les instructions"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Bonjour %(username)s,\n\nPour changer votre mot de passe GNU MediaGoblin, ouvrez l'URL suivante dans \nvotre navigateur internet :\n\n%(verification_url)s\n\nSi vous pensez qu'il s'agit d'une erreur, ignorez simplement cet email et restez\nun goblin heureux !"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "La connexion a échoué!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Pas encore de compte ?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Créez-en un ici !"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Vous avez oublié votre mot de passe ?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Créer un compte !"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Créer"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Bonjour %(username)s,\n\npour activer votre compte sur GNU MediaGoblin, veuillez vous rendre à l'adresse suivante avec votre navigateur web:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Disponible sous la licence <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Code source</a> disponible."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Explorer"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Bonjour, et bienvenue sur ce site MediaGoblin !"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Ce site fait tourner <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un logiciel d'hébergement de média extraordinairement génial."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Pour ajouter vos propres médias, commenter, et bien plus encore, vous pouvez vous connecter avec votre compte MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Vous n'en avez pas ? C'est facile !"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Logo MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Éditer les pièces jointes de %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Pièces jointes"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Ajouter une pièce jointe"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Annuler"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Enregistrer les modifications"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Supprimer définitivement"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Modification de %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Changement des préférences du compte de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Modification de %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Modification du profil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Médias taggés avec : %(tag_name)s "
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Télécharger"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Désolé, mais ce fichier audio ne se lancera pas car\nvotre navigateur web ne supporte pas l'audio HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Vous pouvez obtenir un navigateur à jour capable de lire cette vidéo sur <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Fichier original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "fichier WebM (codec Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Image de %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "fichier WebM (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Ajouter une collection"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Ajoutez votre média"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Éditer"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Effacer"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Voulez-vous vraiment supprimer %(title)s ?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Voulez vous vraiment retirer %(media_title)s de %(collection_title)s ?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Retirer"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Bonjour %(username)s,\n%(comment_author)s a commenté votre post (%(comment_url)s) sur %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Medias de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Médias de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Parcourir les médias de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Ajouter un commentaire"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Ajouter ce commentaire"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Ajouter une nouvelle collection"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Vous pouvez suivre l'état des médias en cours de traitement pour votre galerie ici."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Vos 10 derniers envois réussis"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "profil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Impossible de trouver cet utilisateur, désolé."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Vérification d'email nécessaire"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Presque fini ! Votre compte a encore besoin d'être activé."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Un e-mail devrait vous parvenir dans quelques instants ; il vous indiquera comment procéder."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Si la vérification n'est pas arrivée à bon port :"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Renvoyer l'e-mail de vérification"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Quelqu'un a enregistré un compte avec ce nom, mais il doit encore être activé."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Si c'est de vous qu'il s'agit, mais que vous avez perdu l'e-mail de vérification, vous pouvez vous <a href=\"%(login_url)s\">identifier</a> et le renvoyer."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Voici un endroit pour parler aux autres de vous-même."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Modifier le profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Cet utilisateur n'a pas (encore) rempli son profil."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Voir tous les médias de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "C'est là où vos médias apparaîssent, mais vous ne semblez pas avoir encore ajouté quoi que ce soit."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Il ne semble pas y avoir de média là, pour l'instant ..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "icone de flux"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "flux Atom"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Tous droits réservés"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Le plus récent"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Le plus vieux →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Aller à la page :"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "le plus récent"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "le plus vieux"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Taggé avec"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Impossible de lire l'image."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Zut !"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Une erreur est survenue"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Opération non autorisée"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Je regrette Dave, cela m'est malheureusement impossible !</p><p>Vous avez essayé d'effectuer une action pour laquelle vous n'avez pas de permission. Avez-vous tenté de supprimer tous les comptes utilisateur à nouveau ?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Il ne semble pas y avoir de page à cette adresse. Désolé ! </p><p>Si vous êtes sûr que l'adresse est correcte, peut-être que la page que vous recherchez a été déplacée ou supprimée."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Vous pouvez utilisez les <a href=\"http://daringfireball.net/projects/markdown/basics\">Balises</a> pour la mise en page."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Je suis sûr de vouloir supprimer cela"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Je suis certain de vouloir retirer cet élément de la collection"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Sélectionner --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Inclure une note"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "a commenté votre post"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Oups, votre commentaire était vide."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Votre commentaire a été posté !"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Veuillez vérifier vos entrées et réessayer."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Vous devez sélectionner ou ajouter une collection"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" est déjà dans la collection \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" as été ajouté à la collection \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Vous avez supprimé le media."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Ce media n'a pas été supprimé car vous n'avez pas confirmer que vous étiez sur."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Vous êtes sur le point de supprimer des médias d'un autre utilisateur. Procédez avec prudence."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Vous avez supprimé cet élément de la collection."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "L'élément n'as pas été supprimé car vous n'avez pas confirmé votre certitude."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Vous vous apprêtez à supprimer un élément de la collection d'un autre utilisateur. Procédez avec attention."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Vous avez supprimé la collection \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "La collection n'as pas été supprimée car vous n'avez pas confirmé votre certitude"
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Vous vous apprêtez à supprimer la collection d'un autre utilisateur. Procédez avec attention."
diff --git a/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..09412b0a
--- /dev/null
+++ b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..4a5c2b52
--- /dev/null
+++ b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1254 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# GenghisKhan <genghiskhan@gmx.ca>, 2013
+# GenghisKhan <genghiskhan@gmx.ca>, 2012
+# GenghisKhan <genghiskhan@gmx.ca>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-01 07:11+0000\n"
+"Last-Translator: GenghisKhan <genghiskhan@gmx.ca>\n"
+"Language-Team: Hebrew (http://www.transifex.com/projects/p/mediagoblin/language/he/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: he\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "שם משתמש"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "סיסמה"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "כתובת דוא״ל"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "שם משתמש או דוא״ל"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "שם משתמש או דוא״ל"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "שם משתמש או דוא״ל שגוי."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "שדה זה לא לוקח כתובות דוא״ל."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "שדה זה מצריך כתובת דוא״ל."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "צר לי, רישום הינו מנוטרל על שרת זה."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "צר לי, משתמש עם שם זה כבר קיים."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "צר לי, משתמש עם דוא״ל זה כבר קיים."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "כתובת הדוא״ל שלך אומתה. כעת באפשרותך להתחבר, לערוך את דיוקנך, ולשלוח תמונות!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "מפתח האימות או זהות משתמש הינם שגויים"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "עליך להתחבר על מנת שנדע אל מי לשלוח את הדוא״ל!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "כבר אימתת את כתובת הדוא״ל שלך!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "שלח שוב את דוא״ל האימות שלך."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "במידה וכתובת הדוא״ל הזו (תלוי רישיות!) רשומה דוא״ל נשלח עם הוראות בנוגע לכיצד לשנות את סיסמתך."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "לא היה ניתן למצוא מישהו עם שם משתמש זה."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "דוא״ל נשלח בצירוף הוראות בנוגע לכיצד ניתן לשנות את סיסמתך."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "לא היה ניתן לשלוח דוא״ל לשחזור סיסמה מאחר ושם המשתמש שלך אינו פעיל או שכתובת הדוא״ל של חשבונך לא אומתה."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "כעת ביכולתך להתחבר באמצעות סיסמתך החדשה."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "כותרת"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "תיאור של מלאכה זו"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "ביכולתך להשתמש בתחביר\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> לעיצוב."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "תגיות"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "הפרד תגיות בעזרת פסיקים."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "חשופית"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "החשופית לא יכולה להיות ריקה"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "אזור הכותרת של כתובת מדיה זו. לרוב אין הכרח לשנות את חלק זה."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "רשיון"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "ביו"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "אתר רשת"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "כתובת זו מכילה שגיאות"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "עדיפות רשיון"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "זה יהיה הרשיוןן המשתמט (ברירת מחדל) שלך בטופסי העלאה."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "שלח לי דוא״ל כאשר אחרים מגיבים על המדיה שלי"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "הכותרת לא יכולה להיות ריקה"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "תיאור אוסף זה"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "אזור הכותרת של כתובת אוסף זה. לרוב אין הכרח לשנות את חלק זה."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "סיסמה ישנה"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "הזן את סיסמתך הישנה כדי להוכיח שאתה הבעלים של חשבון זה."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "סיסמה חדשה"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "רשומה עם חשופית זו כבר קיימת עבור משתמש זה."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "אתה עורך מדיה של משתמש אחר. המשך בזהירות."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "הוספת את התצריף %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "באפשרותך לערוך רק את הדיוקן שלך."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "אתה עורך דיוקן של משתמש. המשך בזהירות."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "שינויי דיוקן נשמרו"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "הגדרות חשבון נשמרו"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "עליך לאמת את המחיקה של חשבונך."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "כבר יש לך אוסף שקרוי בשם \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "אוסף עם חשופית זו כבר קיים עבור משתמש זה."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "אתה עורך אוסף של משתמש אחר. המשך בזהירות."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "סיסמה שגויה"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "סיסמתך שונתה בהצלחה"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "לא ניתן לקשר אל מוטיב... לא הוגדר מוטיב\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "אין מדור נכס עבור מוטיב זה\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "בכל אופן, קישור מדור symlink נמצא; הוסר.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "לא היתה אפשרות לקשר את \"%s\": %s קיים ואינו קישור סמלי (symlink)\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "מדלג על \"%s\"; כבר מוגדר.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "קישור ישן נמצא עבור \"%s\"; מסיר כעת.\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "עוגיית CSRF לא נוכחת. זה קרוב לוודאי נובע משום חוסם עוגייה או משהו בסגנון.<br/>הבטח קביעה של עוגיות עבור תחום זה."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "צר לי, אינני תומך בטיפוס קובץ זה :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "unoconv נכשל לפעול, בדוק קובץ יומן"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "המרת וידאו נכשלה"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "מיקום"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "הצגה אצל <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "התר"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "אסור"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "שם"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "השם של לקוח OAuth"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "תיאור"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "זה יראה למשתמשים שמתירים\n ליישומים שלך לאמת אותם."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "טיפוס"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>סודי</strong> - הלקוח יכול\n ליצור בקשות אל שרת GNU MediaGoblin שלא יכולות להיבלם\n על ידי user agent (למשל לקוח server-side).<br />\n <strong>פומבי</strong> - הלקוח לא יכול ליצור בקשות\n סודיות אל של GNU MediaGoblin (למשל לקוח\n ‫JavaScript מתופעל client-side)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "שדה זה הינו דרוש עבור לקוחות פומביים"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "הלקוח {0} נרשם!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "חיבורי לקוח OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "לקוחות OAuth שלך"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "הוסף"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "ניתן קובץ שגוי עבור טיפוס מדיה."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "קובץ"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "עליך לספק קובץ."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "הידד! נשלח!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "אוסף \"%s\" התווסף!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "אמת את הדוא״ל שלך!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "התנתקות"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "התחברות"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "החשבון של <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "שנה הגדרות חשבון"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "לוח עיבוד מדיה"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "התנתקות"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "הוספת מדיה"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "צור אוסף חדש"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "תמונה של גובלין מתאמץ יתר על המידה"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "המדיה האחרונה ביותר"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "כאן ביכולתך לעקוב אחר המצב של המדיה שמתעבדת בשרת זה."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "מדיה באמצע-עיבוד"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "אין מדיה באמצע-עיבוד"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "העלאות אלה נכשלו להתעבד:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "אין רשומות כושלות!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "10 העלאות מוצלחות אחרונות"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "אין רישומים מעובדים, עדיין!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "הגדר את סיסמתך החדשה"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "הגדר סיסמה"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "שחזר סיסמה"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "שלח הוראות"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "שלום %(username)s,\n\nבכדי לשנות את סיסמתך אצל GNU MediaGoblin, עליך לפתוח את הכתובת הבאה \nבתוך דפדפן הרשת שלך:\n\n%(verification_url)s\n\nבמידה ואתה חושב שמדובר בשגיאה, פשוט התעלם מן דוא״ל זה והמשך להיות\nגובלין מאושר!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "התחברות נכשלה!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "אין לך חשבון עדיין?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "צור חשבון כאן!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "שכחת את סיסמתך?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "צור חשבון!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "צור"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "שלום %(username)s,\n\nבכדי להפעיל את חשבונך אצל GNU MediaGoblin, עליך לפתוח את הכתובת הבאה\nבתוך דפדפן הרשת שלך:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "ממונע על ידי <a href=\"http://mediagoblin.org/\" title='גירסה %(version)s'>MediaGoblin</a>, פרויקט <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "משוחרר תחת הרשיון <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">קוד מקור</a> זמין."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "לחקור"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "שלום לך, ברוך בואך אל אתר MediaGoblin זה!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "אתר זה מריץ <a href=\"http://mediagoblin.org\">MediaGoblin</a>, חתיכת תוכנת אירוח מדיה יוצאת מן הכלל."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "בכדי להוסיף את המדיה שלך, להשים תגובות, ועוד, ביכולתך להתחבר עם חשבון MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "אין ברשותך חשבון עדיין? זה קל!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">צור חשבון באתר זה</a>\n או\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">התקן את MediaGoblin על שרתך</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "לוגו MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "עריכת תצריפים עבור %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "תצריפים"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "הוספת תצריף"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "ביטול"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "שמור שינויים"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "משנה כעת את הסיסמה של %(username)s'"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "שמור"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "באמת למחוק את המשתמש '%(user_name)s' ואת כל המדיה/התגובות הקשורות?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "כן, באמת למחוק את חשבוני"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "מחק לצמיתות"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "ערוך %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "שינוי הגדרות חשבון עבור %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "שנה את סיסמתך."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "מחק את החשבון שלי"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "עריכת %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "עריכת דיוקן עבור %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "מדיה מתויגת עם: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "הורד"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "מקורית"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "צר לי, אודיו זה לא יעבוד מכיוון \n\tשדפדפן הרשת שלך לא תומך \n\tאודיו של HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "ביכולתך להשיג דפדפן רשת מודרני \n\tשכן מסוגל לנגן את אודיו זה אצל <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "קובץ מקורי"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "קובץ WebM (קודק Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "תמונה עבור %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "קובץ PDF"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "החלף סיבוב"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "נקודת מבט"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "לפנים"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "ראש"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "צד"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "הורד מודל"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "פורמט קובץ"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "גובה אובייקט"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "צר לי, וידאו זה לא יעבוד מכיוון \n שדפדפן הרשת שלך לא תומך \n וידאו של HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "ביכולתך להשיג דפדפן רשת מודרני \n שכן מסוגל לנגן את וידאו זה אצל <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "קובץ WebM ‫(640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "הוסף אוסף"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "הוספת המדיה שלך"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (אוסף של %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s מאת <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "ערוך"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "מחק"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "באמת למחוק את %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "באמת להסיר את %(media_title)s מן %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "הסר"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "אוספים של %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "אוספים של <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "שלום %(username)s,\n%(comment_author)s הגיב/ה על פרסומך (%(comment_url)s) אצל %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "המדיה של %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "מדיה משתמש <a href=\"%(user_url)s\">%(username)s</a> עם תגית <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "המדיה של <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ עיון במדיה מאת <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "הוסף תגובה"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "הוסף את תגובה זו"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "מלפני %(formatted_time)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "התווסף"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "נוצר"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "הוסף את “%(media_title)s” אל אוסף"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "הוסף אוסף חדש"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "ביכולתך לעקוב כאן אחר מצב של מדיה שמצויה בתהליך עיבוד עבור הגלריה שלך."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "10 ההעלאות המוצלחות שלך"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "הדיוקן של %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "צר לי, משתמש נתון לא נמצא."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "נדרש אימות דוא״ל"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "כמעת סיימנו! חשבונך עדיין צריך אקטיבציה."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "דוא״ל צפוי להגיע בעוד מספר רגעים בצירוף הוראות בנוגע לכיצד לעשות כך."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "במידה וזה לא:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "שלח דוא״ל אימות"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "מישהו רשם חשבון עם שם משתמש זה, אך עליו להיות מופעל."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "אם אתה אכן אדם זה אולם איבדת את דוא״ל האימות שלך, ביכולתך <a href=\"%(login_url)s\">להתחבר</a> ולשלוחו מחדש."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "הנה מקום לומר לאחרים אודותייך."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "ערוך דיוקן"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "משתמש זה לא מילא דיוקן (עדיין)."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "עיון באוספים"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "צפיה בכל המדיה של %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "כאן זה המקום בו המדיה שלך תופיע, אולם לא נראה שהוספת משהו עדיין."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "לא נראה שיש כאן מדיה כלשהי עדיין..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(הסר)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "הוסף אל אוסף"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "צלמית ערוץ"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "ערוץ Atom"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "כל הזכויות שמורות"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "חדש יותר ←"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "→ ישן יותר"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "מעבר אל עמוד:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "חדש יותר"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "ישן יותר"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "מתויגת עם"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "לא היה ניתן לקרוא את קובץ התמונה."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "אופס!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "אירעה שגיאה"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "פעולה לא מורשית"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "צר לי דוד, אני לא יכול להתיר לך לעשות זאת!</p><p>ניסית לבצע פעולה שאינך מורשה לעשות. האם ניסית למחוק את כל החשבונות של המשתמשים שוב?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "לא נראה שקיים עמוד בכתובת זו. צר לי!</p><p>אם אתה בטוח שהכתובת הינה מדויקת, ייתכן שהעמוד שאתה מחפש כעת הועבר או נמחק."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "שנה"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "חודש"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "שבוע"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "יום"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "שעה"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "דקה"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "תגובה"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "ביכולתך לעשות שימוש בתחביר <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> לעיצוב."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "אני בטוח שברצוני למחוק זאת"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "אני בטוח שברצוני להסיר את פריט זה מן האוסף"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "אוסף"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- בחר --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "הכללת פתק"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "הגיב/ה על פרסומך"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "מצטערים, תגובות מנוטרלות."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "אופס, תגובתך היתה ריקה."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "תגובתך פורסמה!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "אנא בדוק את רשומותיך ונסה שוב."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "עליך לבחור או להוסיף אוסף"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" כבר קיים באוסף \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" התווסף אל האוסף \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "מחקת את מדיה זו."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "המדיה לא נמחקה מכיוון שלא סימנת שאתה בטוח."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "בחרת למחוק מדיה של משתמש אחר. המשך בזהירות."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "מחקת את הפריט מן אוסף זה."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "הפריט לא הוסר מכיוון שלא סימנת שאתה בטוח."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "בחרת למחוק פריט מן אוסף של משתמש אחר. המשך בזהירות."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "מחקת את האוסף \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "האוסף לא הוסר מכיוון שלא סימנת שאתה בטוח."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "בחרת למחוק אוסף של משתמש אחר. המשך בזהירות."
diff --git a/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..d22f6ee6
--- /dev/null
+++ b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..c9f814fc
--- /dev/null
+++ b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Aleksandr Brezhnev <abrezhnev@gmail.com>, 2012
+# Emilio Sepúlveda <emisepulvedam@gmail.com>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Interlingua (http://www.transifex.com/projects/p/mediagoblin/language/ia/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ia\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Nomine de usator"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Contrasigno"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Adresse de e-posta"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr ""
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr ""
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr ""
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titulo"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etiquettas"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Sito web"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr ""
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "File"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr ""
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr ""
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Initiar session"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Crear un conto!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Cancellar"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profilo de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr ""
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..596ab843
--- /dev/null
+++ b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..77896b87
--- /dev/null
+++ b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# tryggvib <tryggvib@fsfi.is>, 2012
+# tryggvib <tryggvib@fsfi.is>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-05 22:51+0000\n"
+"Last-Translator: tryggvib <tryggvib@fsfi.is>\n"
+"Language-Team: Icelandic (Iceland) (http://www.transifex.com/projects/p/mediagoblin/language/is_IS/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: is_IS\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Notandanafn"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Lykilorð"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Netfang"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Notandanafn eða tölvupóstur"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Notandanafn eða netfang"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Ógilt notandanafn eða netfang"
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Þessi reitur tekur ekki við netföngum."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "í þennan reit verður að slá inn netfang."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Því miður er nýskráning ekki leyfð á þessu svæði."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Því miður er nú þegar til notandi með þetta nafn."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Því miður þá er annar notandi í kerfinu með þetta netfang skráð."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Netfangið þitt hefur verið staðfest. Þú getur núna innskráð þig, breytt kenniskránni þinni og sent inn efni!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Staðfestingarlykillinn eða notendaauðkennið er rangt"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Þú verður að hafa innskráð þig svo við vitum hvert á að senda tölvupóstinn!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Þú hefur staðfest netfangið þitt!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Endursendi staðfestingartölvupóst"
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Ef þetta netfang (há- og lágstafir skipta máli) er skráð hjá okkur hefur tölvupóstur verið sendur með leiðbeiningum um hvernig þú getur breytt lykilorðinu þínu."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Gat ekki fundið neinn með þetta notandanafn."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Tölvupóstur hefur verið sendur með leiðbeiningum um hvernig þú átt að breyta lykilorðinu þínu."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Gat ekki sent tölvupóst um endurstillingu lykilorðs því notandanafnið þitt er óvirkt eða þá að þú hefur ekki staðfest netfangið þitt."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Þú getur núna innskráð þig með nýja lykilorðinu þínu."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titill"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Lýsing á þessu efni"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Þú getur notað\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> til að stílgera textann."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Efnisorð"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Aðskildu efnisorðin með kommum."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Vefslóðarormur"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Vefslóðarormurinn getur ekki verið tómur"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Titilhlutinn í vefslóð þessa efnis. Þú þarft vanalega ekki að breyta þessu."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Leyfi"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Lýsing"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Vefsíða"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Þetta netfang inniheldur villur"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Leyfiskjörstilling"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Þetta verður sjálfgefna leyfið þegar þú vilt hlaða upp efni."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Senda mér tölvupóst þegar einhver bætir athugasemd við efnið mitt"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Þessi titill getur verið innihaldslaus"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Lýsing á þessu albúmi"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Titilhlutinn í vefslóð þessa albúms. Þú þarft vanalega ekki að breyta þessu."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Gamla lykilorðið"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Skráðu gamla lykilorðið þitt til að sanna að þú átt þennan aðgang."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nýtt lykilorð"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Efni merkt með þessum vefslóðarormi er nú þegar til fyrir þennan notanda."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Þú ert að breyta efni annars notanda. Farðu mjög varlega."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Þú bættir við viðhenginu %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Þú getur bara breytt þinni eigin kenniskrá."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Þú ert að breyta kenniskrá notanda. Farðu mjög varlega."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Breytingar á kenniskrá vistaðar"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Aðgangsstillingar vistaðar"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Þú verður að samþykkja eyðingu á notandaaðganginum þínum."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Þú hefur nú þegar albúm sem kallast \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Albúm með þessu vefslóðarormi er nú þegar til fyrir þennan notanda."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Þú ert að breyta albúmi annars notanda. Farðu mjög varlega."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Vitlaust lykilorð"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Það tókst að breyta lykilorðinu þínu"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Get ekki hlekkjað í þema... ekkert þema stillt\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Engin eignamappa fyrir þetta þema\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Fann samt gamlan táknrænan tengil á möppu; fjarlægður.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "Gat ekki tengt \"%s\": %s er til og er ekki sýndartengill\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "Hoppa yfir \"%s\"; hefur nú þegar verið sett upp.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "Gamall tengill fannst fyrir \"%s\"; fjarlægi.\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "CSRF smygildi ekki til staðar. Þetta er líklegast orsakað af smygildishindrara eða einhverju þess háttar.<br/>Athugaðu hvort þú leyfir ekki alveg örugglega smygildi fyrir þetta lén."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Ég styð því miður ekki þessa gerð af skrám :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "tekst ekki að keyra unoconv, athugaðu annálsskrá"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Myndbandsþverkótun mistókst"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Staðsetning"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Skoða á <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Leyfa"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Banna"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nafn"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Nafn OAuth biðlarans"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Lýsing"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Þetta verður sýnilegt öðrum notendum sem leyfir\n forritinu þínu að skrá sig inn sem þeir."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Tegund"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Trúnaður</strong> - Biðlarinn getur\n sent beiðnir til GNU MediaGoblin vefsvæðisins sem geta ekki verið\n truflaðar af notandaforriti (t.d. forriti á vefþjóni).<br />\n <strong>Opinbert</strong> - Biðlarinn getur ekki gert trúnaðarbundnar\n beiðnir til GNU MediaGoblin vefsvæðisins (t.d. Javascript biðlara\n hjá notanda)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Áframsendingarvefslóð"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "Áframsendingarvefslóðin fyrir forritin, þessi reitur\n er <strong>nauðsynlegur</strong> fyrir opinbera biðlara."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Þessi reitur er nauðsynlegur fyrir opinbera biðlara"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Biðlarinn {0} hefur verið skráður!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "Biðlarartengingar OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "OAuth-biðlararnir þínir"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Bæta við"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Ógild skrá gefin fyrir þessa margmiðlunartegund."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Skrá"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Þú verður að gefa upp skrá."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Jibbí jei! Það tókst að senda inn!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Albúmið \"%s\" var búið til!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Staðfestu netfangið þitt!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "útskrá"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Innskrá"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Notandaaðgangur: <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Breyta stillingum notandaaðgangs"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Margmiðlunarvinnsluskiki"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Skrá út"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Senda inn efni"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Búa til nýtt albúm"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Mynd af durt í stresskasti"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Nýlegt efni"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Hér getur þú fylgst með margmiðlunarefni sem verið er að vinna á þessu vefsvæði."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Efni í vinnslu"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Ekkert efni í vinnslu"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Það mistókst að fullvinna þessar innsendingar:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Engar bilaðar innsendingar!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Síðustu 10 árangursríku innsendingarnar"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Ekkert fullunnið efni enn!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Skrifaðu inn nýja lykilorðið þitt"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Skrá lykilorð"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Endurstilla lykilorð"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Senda leiðbeiningar"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Hæ %(username)s,\n\ntil að breyta GNU MediaGoblin lykilorðinu þínu opnar þú eftirfarandi vefslóð í \nvafranum þínum:\n\n%(verification_url)s\n\nEf þú heldur að það sé einhver vitleysa í gangi husnar þú bara þennan póst og heldur áfram að vera\nánægður durtur!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Mistókst að skrá þig inn."
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Ertu ekki með notendaaðgang?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Búðu til aðgang hérna!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Gleymdirðu lykilorðinu þínu?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Búðu til nýjan aðgang!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Búa til"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Hæ %(username)s,\n\ntil að virkja GNU MediaGoblin aðganginn þinn, opnaðu þá eftirfarandi vefslóði í\nvafranum þínum:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Keyrt af <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, sem er <a href=\"http://gnu.org/\">GNU</a> verkefni."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Gefið út undir <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Frumkóti</a> aðgengilegur."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Skoða"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Hæ! Gakktu í bæinn á þetta MediaGoblin vefsvæði!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Þetta vefsvæði keyrir á <a href=\"http://mediagoblin.org\">MediaGoblin</a> sem er ótrúlega frábær hugbúnaður til að geyma margmiðlunarefni."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Til að senda inn þitt efni, gera athugasemdir og fleira getur þú skráð þig inn með þínum MediaGoblin aðgangi."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Ertu ekki með aðgang? Það er auðvelt að búa til!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Búa til aðgang á þessari síðu</a>\neða\n<a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Settu upp þinn eigin margmiðlunarþjón</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin einkennismerkið"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Breyti viðhengjum við: %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Viðhengi"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Bæta við viðhengi"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Hætta við"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Vista breytingar"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Breyti lykilorði fyrir notandann: %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Vista"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Virkilega eyða notanda '%(user_name)s' og tengt efni/athugasemdir?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Já, ég vil örugglega eyða aðganginum mínum"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Eytt algjörlega"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Breyti %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Breyti notandaaðgangsstillingum fyrir: %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Breyta lykilorðinu þínu."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Eyða aðganginum mínum"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Breyti %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Breyti kenniskrá notandans: %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Efni merkt með: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Sækja af Netinu"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Upphafleg skrá"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Því miður mun þessi hljóðskrá ekki virka því \n\tvafrinn þinn styður ekki HTML5 \n\thljóðskrár."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Þú getur náð í nýlegan vafra sem \n\tgetur spilað hljóðskrár á <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Upphaflega skráin"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM skrá (Vorbis víxlþjöppun)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Mynd fyrir %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF skrá"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Stilla snúning af eða á"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Sjónhorf"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Framhlið"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Toppur"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Hlið"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Hala niður líkani"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Skráarsnið"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Hæð hlutar"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Því miður mun þetta myndband ekki virka því\n vafrinn þinn styður ekki HTML5 \n myndbönd."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Þú getur náð í nýlegan vafra sem \n sem getur spilað myndbandið á <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM skrá (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Búa til albúm"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Sendu inn efni"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (albúm sem %(username)s á)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s sem <a href=\"%(user_url)s\">%(username)s</a> bjó til"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Breyta"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Eyða"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Virkilega eyða %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Virkilega fjarlægja %(media_title)s úr %(collection_title)s albúminu?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Fjarlægja"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Albúm sem %(username)s á"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Albúm sem <a href=\"%(user_url)s\">%(username)s</a> á"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Hæ %(username)s,\n%(comment_author)s skrifaði athugasemd við færsluna þína (%(comment_url)s) á %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Efni sem %(username)s á"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Efni sem <a href=\"%(user_url)s\">%(username)s</a> á og er merkt með <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Efni sem <a href=\"%(user_url)s\">%(username)s</a> á"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Skoða efnið sem <a href=\"%(user_url)s\">%(username)s</a> setti inn"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Bæta við athugasemd"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Senda inn þessa athugasemd"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "Fyrir %(formatted_time)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Bætt við"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Skapað"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Setja '%(media_title)s' í albúm"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Búa til nýtt albúm"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Þú getur fylgst með hvernig gengur að vinna með margmiðlunarefnið fyrir safnið þitt hérna."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Síðustu 10 árangursíku innsendingarnar þínar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Kenniskrá fyrir: %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Því miður fannst notandinn ekki."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Staðfesting á netfangi nauðsynleg"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Næstum því búið! Notandaaðgangurinn þinn verður að vera staðfestur."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Tölvupóstur ætti að berast til þín eftir smástund með leiðbeiningum um hvernig þú átt að gera það."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Ef það gerist ekki:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Endursenda staðfestingarpóst"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Einhver hefur búið til aðgang með þessu notandanafni en hefur ekki enn virkjað aðganginn."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Ef þú ert þessi aðili en hefur týnt staðfestingarpóstinum getur þú <a href=\"%(login_url)s\">skráð þig inn</a> og endursent hann"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Hér er svæði til að segja öðrum frá þér."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Breyta kenniskrá"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Þessi notandi hefur ekki fyllt inn í upplýsingar um sig (ennþá)."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Skoða albúm"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Skoða efnið sem %(username)s á"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Þetta er staðurinn þar sem efnið þitt birtist en þú virðist ekki hafa sent neitt inn ennþá."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Það virðist ekki vera neitt efni hérna ennþá..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(fjarlægja)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "Sett í albúm"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Setja í albúm"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "fréttaveituteikn"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom fréttaveita"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Öll réttindi áskilin"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Nýrri"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Eldri →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Fara á síðu:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "nýrri"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "eldri"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Merkt með"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Gat ekki lesið myndskrána."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Obbosí!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Villa kom upp"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Aðgerð ekki leyfileg"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Fyrirgefðu Davíð. Ég get ekki leyft þér að gera þetta!</p></p>Þú hefur reynt að framkvæma aðger sem þú hefur ekki leyfi til. Varstu að reyna að eyða öllum notendunum aftur?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Því miður! Það virðist ekki vera nein síða á þessari vefslóð.</p><p>Ef þú ert viss um að vefslóðin sé rétt hefur vefsíðan sem þú ert að leita að kannski verið flutt eða fjarlægð."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "ár"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "mánuður"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "vika"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "dagur"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "klukkustund"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "mínúta"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Athugasemd"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Þú getur notað <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> til að stílgera textann"
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Ég er viss um að ég vilji eyða þessu"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Ég er viss um að ég vilji fjarlægja þetta efni úr albúminu"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Albúm"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Velja --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Bæta við minnispunktum"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "skrifaði athugasemd við færsluna þína"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Því miður, athugasemdir eru óvirkar."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Obbosí! Athugasemdin þín var innihaldslaus."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Athugasemdin þín var skráð!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Vinsamlegast kíktu á innsendingarnar þínar og reyndu aftur."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Þú verður að velja eða búa til albúm"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" er nú þegar í albúminu \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" sett í albúmið \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Þú eyddir þessu efni."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Efninu var ekki eytt þar sem þú merktir ekki við að þú værir viss."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Þú ert í þann mund að fara að eyða efni frá öðrum notanda. Farðu mjög varlega."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Þú tókst þetta efni úr albúminu."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Þetta efni var ekki fjarlægt af því að þú merktir ekki við að þú værir viss."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Þú ert í þann mund að fara að eyða efni úr albúmi annars notanda. Farðu mjög varlega."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Þú eyddir albúminu \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Þessu albúmi var ekki eytt vegna þess að þu merktir ekki við að þú værir viss."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Þú ert í þann mund að fara að eyða albúmi annars notanda. Farðu mjög varlega."
diff --git a/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..62575b62
--- /dev/null
+++ b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..c782fc62
--- /dev/null
+++ b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1256 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Francesco Apruzzese <cescoap@gmail.com>, 2012
+# gdb <gaedeb01@gmail.com>, 2013
+# pikappa469 <pikappa469@alice.it>, 2011
+# nunni <robi@nunnisoft.ch>, 2011
+# Damtux <sun_lion@live.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Italian (http://www.transifex.com/projects/p/mediagoblin/language/it/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Nome utente"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Password"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Indirizzo email"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Nome utente o indirizzo email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Spiacente, la registrazione è disabilitata su questa istanza."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Spiacente, esiste già un utente con quel nome."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Siamo spiacenti, un utente con quell'indirizzo email esiste già."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Il tuo indirizzo email è stato verificato. Ora puoi accedere, modificare il tuo profilo e caricare immagini!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "La chiave di verifica o l'id utente è sbagliato"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Devi effettuare l'accesso così possiamo sapere a chi inviare l'email!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Hai già verificato il tuo indirizzo email!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Rispedisci email di verifica"
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Ti è stata inviata un'email con le istruzioni per cambiare la tua password."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Impossibile inviare l'email di recupero password perchè il tuo nome utente è inattivo o il tuo indirizzo email non è stato verificato."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Ora puoi effettuare l'accesso con la nuova password."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titolo"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Descrizione di questo lavoro"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Puoi usare il\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> per la formattazione."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Tags"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Separa le tags con la virgola."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Il titolo è parte dell'indirizzo del file. Nella maggior parte dei casi non c'è bisogno di cambiarlo."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licenza"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Biografia"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Sito web"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Questo indirizzo contiene errori"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Inviami messaggi email quando altre persone commentano i miei files multimediali"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Password vecchia"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Inserisci la vecchia password per dimostrare di essere il proprietario dell'account."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nuova password"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Stai modificando files multimediali di un altro utente. Procedi con attenzione."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Stai modificando il profilo di un utente. Procedi con attenzione."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Cambiamenti del profilo salvati"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Impostazioni del profilo salvate"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Password errata"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Mi dispiace, non supporto questo tipo di file :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Transcodifica video fallita"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Posizione"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Visualizza su <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Aggiungi"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "File non valido per il tipo di file multimediale indicato."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "File"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Devi specificare un file."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Evviva! Caricato!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifica la tua email!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Accedi"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Cambia le impostazioni dell'account"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Pannello di elaborazione files multimediali"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Aggiungi files multimediali"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Files multimediali più recenti"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Qui è possibile tenere traccia dello stato dei file multimediali in fase di elaborazione in questa istanza."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Files multimediali in elaborazione"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Nessun file multimediale in elaborazione"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "L'elaborazione di questi files caricati è fallita:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Ultimi 10 caricamenti riusciti"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Imposta la nuova password"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Imposta password"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Recupera Password"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Invia istruzioni"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Ciao %(username)s,\n\nper cambiare la tua password MediaGoblin apri il seguente URL nel\ntuo browser web:\n\n%(verification_url)s\n\nSe pensi che questo sia un errore, ignora semplicemente questa email e continua ad essere \nun goblin felice!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Accesso fallito!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Non hai ancora un account?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Creane uno qui!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Hai dimenticato la tua password?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Crea un account!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Crea"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Ciao %(username)s,\n\nper attivare il tuo account GNU MediaGoblin, apri il seguente URL nel \ntuo navigatore web.\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Rilasciato con licenza <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Codice sorgente</a> disponibile."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Esplora"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Ciao, benvenuto in questo sito MediaGoblin!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Questo sito sta utilizzando <a href=\"http://mediagoblin.org\">Mediagoblin</a>, un ottimo programma per caricare e condividere files multimediali."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Per aggiungere i tuoi file multimediali, scrivere commenti e altro puoi accedere con il tuo account MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Non ne hai già uno? E' semplice!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Simbolo di MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Stai modificando gli allegati di %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Allegati"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Aggiungi allegato"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Annulla"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Salva i cambiamenti"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Elimina definitivamente"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Stai modificando %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Stai cambiando le impostazioni dell'account di %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Stai modificando il profilo di %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "File taggato con: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Scarica"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Originale"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Spiacente ma è impossibile leggere questo file audio perché\n\til tuo browser web non supporta l'HTML5 \n\taudio."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Puoi scaricare un browser web moderno,\n\t in grado di leggere questo file audio, qui <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "File originario"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "File WebM (codec Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "File WebM (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Aggiungi il tuo file multimediale"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Modifica"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Elimina"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Vuoi davvero eliminare %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Ciao %(username)s,\n%(comment_author)s ha commentato il tuo post (%(comment_url)s) su %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Files multimediali di %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Files multimediali di <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Stai guardando i files multimediali di <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Aggiungi un commento"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Aggiungi questo commento"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Puoi controllare lo stato dei files multimediali in elaborazione per la tua galleria qui."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "I tuoi ultimi 10 caricamenti riusciti"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "profilo di %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Mi dispiace, utente non trovato."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "E' necessario verificare l'indirizzo email"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Quasi finito! Il tuo account deve ancora essere attivato."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "In breve dovresti ricevere un email contenente istruzioni su come fare."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Nel caso non fosse:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Rispedisci email di verifica"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Qualcuno ha registrato un account con questo nome utente, ma deve ancora essere attivato."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Se sei quella persona ma hai perso l'email di verifica, puoi <a href=\"%(login_url)s\">accedere</a> e rispedirla."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Ecco un posto dove raccontare agli altri di te."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Modifica profilo"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Questo utente non ha (ancora) compilato il proprio profilo."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Visualizza tutti i files multimediali di %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Qui è dove appariranno i tuoi files multimediali, ma sembra che tu non abbia ancora aggiunto niente."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Sembra che non ci sia ancora nessun file multimediale qui..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "feed icon"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Tutti i diritti riservati"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Più recente"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Più vecchio →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Vai alla pagina:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "più recente"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "più vecchio"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Taggato con"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Impossibile leggere il file immagine."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Oops!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Puoi usare il <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> per la formattazione."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Sono sicuro di volerlo eliminare"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "ha commentato il tuo post"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Oops, il tuo commento era vuoto."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Il tuo commento è stato aggiunto!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Hai eliminato il file."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Il file non è stato eliminato perchè non hai confermato di essere sicuro."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Stai eliminando un file multimediale di un altro utente. Procedi con attenzione."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..3c82d1ff
--- /dev/null
+++ b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..97d68127
--- /dev/null
+++ b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Avery <averym@gmail.com>, 2011
+# parlegon <parlegon@gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Japanese (http://www.transifex.com/projects/p/mediagoblin/language/ja/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ja\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "ユーザネーム"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "パスワード"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "メールアドレス"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "申し訳ありませんが、このインスタンスで登録は無効になっています。"
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "申し訳ありませんが、その名前を持つユーザーがすでに存在しています。"
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "メアドが確認されています。これで、ログインしてプロファイルを編集し、画像を提出することができます!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "検証キーまたはユーザーIDが間違っています"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "検証メールを再送しました。"
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "タイトル"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "タグ"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "スラグ"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "スラグは必要です。"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "自己紹介"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "URL"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "そのスラグを持つエントリは、このユーザーは既に存在します。"
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "あなたは、他のユーザーのメディアを編集しています。ご注意ください。"
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "あなたは、他のユーザーのプロファイルを編集しています。ご注意ください。"
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "追加"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr ""
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "ファイル"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "ファイルを提供する必要があります。"
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "投稿終了!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "ログイン"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "まだアカウントを持っていませんか?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "ここで作成!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "パスワードを忘れましたか?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "アカウントを作成!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "%(username)s様へ\n\nGNU MediaGoblinアカウントを検証にするには、このURLを開いてください。\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "こんにちは、このMediaGoblinサイトへようこそ!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "投稿する"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "%(media_title)sを編集中"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "%(username)sさんのプロフィールを編集中"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "ダウンロード"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "編集"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "削除"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>さんのコンテンツ"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)sさんのプロフィール"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "申し訳ありませんが、そのユーザーは見つかりませんでした。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "メールは、その方法の指示でいくつかの瞬間に到着します。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "到着しない場合は、"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "確認メールを再送信"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "あなたの確認メールを紛失した場合、<a href=\"%(login_url)s\">ログイン</a>して再送できます。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "プロフィールを編集"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "%(username)sさんのコンテンツをすべて見る"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr ""
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..7d37ab7c
--- /dev/null
+++ b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..5333de02
--- /dev/null
+++ b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1252 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Jin-hoon Kim <newvgund@gmail.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Korean (Korea) (http://www.transifex.com/projects/p/mediagoblin/language/ko_KR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ko_KR\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "사용자 이름"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "비밀번호"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "email 주소"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "사용자 이름 또는 email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "죄송합니다. 지금은 가입 하실 수 없습니다."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "죄송합니다. 해당 사용자 이름이 이미 존재 합니다."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "죄송합니다. 사용자와 해당 이메일은 이미 등록되어 있습니다."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "해당 email 주소가 이미 인증 되어 있습니다. 지금 로그인하시고 계정 정보를 수정하고 사진을 전송해 보세요!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "인증 키 또는 사용자 ID가 올바르지 않습니다."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "로그인을 하셔야 고블린에서 메일을 보낼 수 있습니다!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "이미 인증받은 email 주소를 가지고 있습니다!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "인증 메일을 다시 보내 주세요."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "비밀번호를 변경하는 방법에 대한 설명서가 메일로 전송 되었습니다."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "사용자의 이름이 존재하지 않거나, 사용자의 email 주소가 인증되지 않아 비밀번호 복구 메일을 보낼 수 없습니다."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "이제 새로운 비밀번호로 로그인 하실 수 있습니다."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "제목"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "이 작업에 대한 설명"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "포멧팅을 사용하려면\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> 링크를 참고 하세요."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "태그"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "태그는 , 로 구분 됩니다."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "'슬러그'"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "'슬러그'는 공백일 수 없습니다."
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "제목은 미디어 주소의 일부분 입니다. 수정하지 않아도 됩니다."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "License"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "소개"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "웹 주소"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "주소에 에러가 있습니다."
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "제 미디어에 대한 컨텍을 원한다면, 메일을 보내주세요."
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "제목은 공백일 수 없습니다."
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "모음집에 대한 설명"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "예전 비밀번호"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "계정 확인을 위해, 이전 비밀 번호를 입력해 주세요."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "새로운 비밀번호"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "해당 유저에 대한 '슬러그'가 이미 존재합니다."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "다른 사용자의 미디어를 수정하고 있습니다. 조심해서 수정하세요."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "사용자의 계정 정보를 수정하고 있습니다. 조심해서 수정하세요."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "계정 정보가 저장 되었습니다."
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "계정 설정이 저장 되었습니다."
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "\"%s\" 모음집을 이미 가지고 있습니다!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "다른 유저의 모음집을 수정 중 입니다. 주의하세요."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "잘못된 비밀번호"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "테마에 연결할 수 없습니다... 테마 셋이 없습니다.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "이 테마를 위한 에셋 디렉토리가 없습니다.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "그런데, 오래된 디렉토리 심볼릭 링크를 찾았습니다; 지워졌습니다.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "죄송합니다. 해당 타입의 파일은 지원하지 않아요 :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "비디오 변환에 실패 했습니다."
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "장소"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr " <a href=\"%(osm_url)s\">OpenStreetMap</a>으로 보기"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "허용"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "거부"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "이름"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "설명"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "종류"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "리다이렉트 URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "이 항목은 공개 사용자들을 위해 꼭 필요 합니다."
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "사용자 {0}님이 등록 되었습니다!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "추가"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "알수없는 미디어 파일 입니다."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "파일"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "파일을 등록하셔야 합니다."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "이햐!! 등록했습니다!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "\"%s\" 모음집이 추가되었습니다!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "메일을 확인하세요!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "로그인"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "계정 설정 변경"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "미디어 작업 패널"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "미디어 추가"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "가장 최근에 등록된 미디어"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "미디어 작업중..."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "작업중인 미디어가 없습니다."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "다음 작업을 하는 중에 업로드에 실패하였습니다.:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "지난 10개의 업로드 목록"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "새로운 비밀번호를 설정 하세요."
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "비밀번호 설정"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "비밀번호 복구"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "설명서 보내기"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "안녕하세요 %(username)s,\n\nGNU MediaGoblin의 사용자 계정 비밀번호를 바꾸시려면, 인터넷 창을 여시고 아래 URL을 통해 접속 하세요. :\n\n%(verification_url)s\n\n오류라고 생각 된다면, 이 메일을 무시하시고 고블린을 즐기세요!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "로그인에 실패 했습니다!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "아직 계정이 없으세요?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "이곳에서 새로 만드세요!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "비밀번호를 잊으셨나요?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "계정을 새로 만듭니다!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "생성"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "안녕하세요 %(username)s님,\n\nGNU MediaGoblin 계정을 활성화 하시려면, 아래의 URL 주소를 브라우져로 접속하세요.\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Released under the <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Source code</a> available."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "탐색"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "안녕하세요! 미디어 고블린 사이트에 온걸 환영 합니다!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "이사이트는 <a href=\"http://mediagoblin.org\">MediaGoblin</a>으로 작동 중입니다. 이는 특이한 미디어 호스팅 소프트웨어중 하나 입니다."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "자신의 미디어를 추가하고, 댓글을 남기세요! 미디어 고블린 계정으로 내역을 확인 하실 수 있습니다!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "아직 아무것도 없으시다구요? 매우 쉽습니다!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin 로고"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "%(media_title)s의 첨부 수정 중..."
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "첨부"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "첨부 추가"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "취소"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "저장"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "영구적으로 삭제"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "%(media_title)s 편집중..."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "%(username)s'의 계정 설정 변경중..."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "%(collection_title)s 편집 중"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "%(username)s의 계정 정보 수정중..."
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "미디어는 다음으로 태그 되었습니다.: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "다운로드"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "원본"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "사용중이신 웹 브라우져가 HTML5를 지원하지 않아\n\t오디오 파일을 재생할 수 없습니다.\n\t죄송합니다."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "사운드 파일을 재생 하시려면\n\t이곳에서 최신의 브라우져를 다운받으세요! <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "원본 파일"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM 파일 (Vorbis 코덱)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "%(media_title)s 이미지"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM 파일 (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "모음집 추가"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "미디어 등록하기"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (%(username)s의 모음집)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>의 %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "수정"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "삭제"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "%(title)s 을 지우시겠습니까?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "%(collection_title)s의 %(media_title)s을 삭제 하시겠습니까?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "지우기"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "안녕하세요 %(username)s님,\n%(comment_author)s 가 (%(comment_url)s) 게시물에 %(instance_name)s 덧글을 등록 하였습니다.\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "%(username)s의 미디어"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>의 미디어"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ <a href=\"%(user_url)s\">%(username)s</a>의 미디어를 보고 있습니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "덧글 달기"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "덧글 추가"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "새 모음집 추가"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "갤러리에서 미디어 작업을 하면 해당 내용을 추적할 수 있습니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "지난 10개의 업로드 목록"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)s의 계정 정보"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "죄송합니다. 해당 유저를 찾지 못했습니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "email 인증이 필요합니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "이미 완료했습니다! 사용자 계정은 활성화 되어 있습니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "곧 email 을 통해 지침서가 도착할 겁니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "이런경우는 작동하지 않습니다.:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "인증메일 다시 보내기"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "누군가 해당 사용자 이름으로 등록은 했으나, 아직 활성화 하지 않았습니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "정상적인 계정이나, 인증 메일을 잃어버리셨다면 <a href=\"%(login_url)s\">로그인</a> 을 하시고 인증 메일을 새로 보내주세요."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "당신에 대해 소개해 보세요."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "계정 정보 수정"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "이 사용자는 계정 정보를 입력하지 않았습니다."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "%(username)s의 모든 미디어 보기"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "이곳에 등록한 미디어가 나타나게 됩니다. 하지만 아직 아무런 미디어를 등록하지 않으셨네요."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "아직 어떠한 미디어도 존재하지 않습니다."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "피드 아이콘"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom 피드"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "All rights reserved"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← 최근"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "이전 →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "페이지로 이동:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "최근"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "이전"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "태그 정보"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "이미지 파일을 읽을 수 없습니다."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "웁스!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "포멧팅을 위해 <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> 을 사용할 수 있습니다.."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "이걸 지우고 싶습니다."
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "이 모음집의 항목을 삭제하는 것을 확인 했습니다."
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- 선택 --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "노트 추가"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "게시물에 덧글이 달렸습니다."
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "오우, 댓글이 비었습니다."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "댓글이 등록 되었습니다!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "확인을 하시고 다시 시도하세요."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "모음집을 추가하거나 기존 모음집을 선택하세요."
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" 모음집이 이미 존재 합니다. \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" 모음집을 추가했습니다. \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "미디어를 삭제 했습니다."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "확인 체크를 하지 않았습니다. 미디어는 삭제되지 않았습니다."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "다른 사람의 미디어를 삭제하려고 합니다. 다시 한번 확인하세요."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "모음집에 있는 항목을 삭제 했습니다."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "확인을 하지 않았습니다. 항목은 삭제하지 않았습니다."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "다른 사용자의 모음집에 있는 항목을 삭제하였습니다. 주의하세요."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "\"%s\" 모음집을 삭제하셨습니다."
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "확인을 하지 않았습니다. 모음집은 삭제하지 않았습니다."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "다른 사용자의 모음집을 삭제하려고 합니다. 주의하세요."
diff --git a/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..4e6e51ce
--- /dev/null
+++ b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..14e4fb33
--- /dev/null
+++ b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# schendje <mail@jefvanschendel.nl>, 2011, 2012
+# mvanderboom <mvanderboom@gmail.com>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Dutch (http://www.transifex.com/projects/p/mediagoblin/language/nl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: nl\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Gebruikersnaam"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Wachtwoord"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "E-mail adres"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Gebruikersnaam of email-adres"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Sorry, registratie is uitgeschakeld op deze instantie."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Sorry, er bestaat al een gebruiker met die naam."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Sorry, een gebruiker met dat e-mailadres bestaat al."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Uw e-mailadres is geverifieerd. U kunt nu inloggen, uw profiel bewerken, en afbeeldingen toevoegen!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "De verificatie sleutel of gebruikers-ID is onjuist"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Je moet ingelogd zijn, anders weten we niet waar we de e-mail naartoe moeten sturen!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Je hebt je e-mailadres al geverifieerd!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Verificatie e-mail opnieuw opgestuurd."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Een e-mail met instructies om je wachtwoord te veranderen is verstuurd."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Email kon niet verstuurd worden omdat je gebruikersnaam inactief is of omdat je e-mailadres nog niet geverifieerd is."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Je kunt nu inloggen met je nieuwe wachtwoord."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titel"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Beschrijving van dit werk"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Voor opmaak kun je <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> gebruiken."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etiket"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Hou labels gescheiden met komma's."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Slug"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "De slug kan niet leeg zijn"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Het titelgedeelte van het adres van deze media. Normaal gesproken hoef je deze niet te veranderen."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licentie"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Bio"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Website"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Dit adres bevat fouten"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Oud wachtwoord"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Vul je oude wachtwoord in om te bewijzen dat dit jouw account is"
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nieuw wachtwoord"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Er bestaat al een met die slug voor deze gebruiker."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "U bent de media van een andere gebruiker aan het aanpassen. Ga voorzichtig te werk."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "U bent een gebruikersprofiel aan het aanpassen. Ga voorzichtig te werk."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Profielaanpassingen opgeslagen"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Accountinstellingen opgeslagen"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Verkeerd wachtwoord"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Sorry, dat bestandstype wordt niet ondersteunt."
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Locatie"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Bekijken op <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Voeg toe"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Verkeerd bestandsformaat voor mediatype opgegeven."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Bestand"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "U moet een bestand aangeven."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Mooizo! Toegevoegd!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifieer je e-mailadres!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Inloggen"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Accountinstellingen aanpassen"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Mediaverwerkingspaneel"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Voeg media toe"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Nieuwste media"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Media te verwerken"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Geen media om te verwerken"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Deze toevoegingen konden niet verwerkt worden:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Voer je nieuwe wachtwoord in"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Wachtwoord opslaan"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Wachtwoord herstellen"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Stuur instructies"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Hoi %(username)s,\n\nOm je wachtwoord voor GNU MediaGoblin te veranderen, moet je dit adres in je webbrowser openen:\n\n%(verification_url)s\n\nAls je denkt dat dit niet klopt, kun je deze e-mail gewoon negeren."
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Inloggen is mislukt!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Heeft u nog geen account?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Maak er hier een!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Wachtwoord vergeten?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Maak een account aan!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Creëer"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Hallo %(username)s , open de volgende URL in uw webbrowser om uw GNU MediaGoblin account te activeren: %(verification_url)s "
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Uitgegeven onder de <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>-licentie. <a href=\"%(source_link)s\">Broncode</a> available."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Verkennen"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Hoi, welkom op deze MediaGoblin website!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Deze website draait <a href=\"http://mediagoblin.org\">MediaGoblin</a>, een buitengewoon goed stuk software voor mediahosting."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Heb je er nog geen? Het is heel eenvoudig!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin logo"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Annuleren"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Wijzigingen opslaan"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Permanent verwijderen"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "%(media_title)s aanpassen"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "%(username)ss accountinstellingen aanpassen"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Het profiel aanpassen van %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Media met het label: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Origineel"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Sorry, dit geluidsfragment zal niet werken omdat\n»uw web-browser geen HTML5 ondersteunt⏎\n»audio."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "U kunt een moderne web-browser die \n\taudio kan afspelen vinden op <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Afbeelding voor %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Voeg media toe"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Pas aan"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Verwijderen"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Zeker weten dat je %(title)s wil verwijderen?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Media van %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Media van <a href=\"%(user_url)s\"> %(username)s </a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Blader door media van <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Geef een reactie"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Voeg dit bericht toe"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Hier kun je de status zien van de media die verwerkt worden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profiel van %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Sorry, die gebruiker kon niet worden gevonden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Emailverificatie is nodig"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Bijna klaar! Je account moet nog geactiveerd worden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Een e-mail zou in een paar ogenblikken aan moeten komen met instructies hiertoe."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Zoniet:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Stuur de verificatie e-mail opnieuw op."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Iemand heeft een account met deze gebruikersnaam gemaakt, maar hij moet nog geactiveerd worden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Als u die persoon bent, maar de verificatie e-mail verloren hebt, kunt u <a href=\"%(login_url)s\">inloggen</a> en hem nogmaals verzenden."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Hier is een plekje om anderen over jezelf te vertellen."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Profiel aanpassen."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Deze gebruiker heeft zijn of haar profiel (nog) niet ingevuld."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Bekijk alle media van %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Dit is waar je nieuwe media zal verschijnen, maar het lijkt erop dat je nog niets heb toegevoegd."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Het lijkt erop dat er nog geen media is."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "feed icoon"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Alle rechten voorbehouden"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Nieuwer"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Ouder →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Ga naar pagina:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "nieuwer"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "ouder"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Getagged met"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Kon het afbeeldingsbestand niet lezen."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Oeps!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Voor opmaak kun je &lt;a href=\"http://daringfireball.net/projects/markdown/basics\"&gt;Markdown&lt;/a&gt; gebruiken."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Ik weet zeker dat ik dit wil verwijderen."
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Oeps, je bericht was leeg."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Je bericht is geplaatst!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Je hebt deze media verwijderd."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Deze media was niet verwijderd omdat je niet hebt aangegeven dat je het zeker weet."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Je staat op het punt de media van iemand anders te verwijderen. Pas op."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..9cbd03b2
--- /dev/null
+++ b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..6a11d5da
--- /dev/null
+++ b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# velmont <odin.omdal@gmail.com>, 2013
+# velmont <odin.omdal@gmail.com>, 2011-2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-31 15:40+0000\n"
+"Last-Translator: velmont <odin.omdal@gmail.com>\n"
+"Language-Team: Norwegian Nynorsk (Norway) (http://www.transifex.com/projects/p/mediagoblin/language/nn_NO/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: nn_NO\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Brukarnamn"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Passord"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Epost"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Brukarnamn eller epost"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Brukarnamn eller epost"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Ugyldig brukarnamn eller passord."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Dette feltet tek ikkje epostadresser."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Dette feltet krev ei epostadresse."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Registrering er slege av. Orsak."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Ein konto med dette brukarnamnet finst allereide."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Ein brukar med den epostadressa finst allereie."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Kontoen din er stadfesta. Du kan no logga inn, endra profilen din og lasta opp filer."
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Stadfestingsnykelen eller brukar-ID-en din er feil."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Du må vera innlogga, slik me veit kven som skal ha eposten."
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Du har allereie verifisiert epostadressa."
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Send ein ny stadfestingsepost."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Dersom denne epostadressa er registrert, har ein epost med instruksjonar for å endra passord vorte sendt til han."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Fann ingen med det brukarnamnet."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Sender epost med instruksjonar for å endra passordet ditt."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Kunne ikkje senda epost. Brukarnamnet ditt er inaktivt eller uverifisert."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Du kan no logga inn med det nye passordet ditt."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Tittel"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Skildring av verk"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Du kan bruka <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> til formattering."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Merkelappar"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Separer merkelappar med komma."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Nettnamn"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Nettnamnet kan ikkje vera tomt"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Nettnamnet (adressetittel) for verket di. Trengst ikkje endrast."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Lisens"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Presentasjon"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Heimeside"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Adressa inneheld feil"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Lisens-val"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Dette vil vera standardvalet ditt for lisens."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Send meg epost når andre kjem med innspel på verka mine."
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Tittelen kjan ikkje vera tom"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Forklaringa til denne samlinga"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Tittel-delen av denne samlinga si adresse. Du treng normalt sett ikkje endra denne."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Gamalt passort"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Skriv inn det gamle passordet ditt for å stadfesta at du eig denne kontoen."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nytt passord"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Eit innlegg med denne adressetittelen finst allereie."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Trå varsamt, du endrar nokon andre sine verk."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "La til vedlegg %s."
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Du kan berre enda din eigen profil."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Trå varsamt, du endrar nokon andre sin profil."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Lagra endring av profilen"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Lagra kontoinstellingar"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Du må stadfesta slettinga av kontoen din."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Du har allereie ei samling med namn «%s»."
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Ei samling med den nettadressa finst allereie for denne brukaren."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Du endrar ein annan brukar si samling. Trå varsamt."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Feil passord"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Endra passord"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Cannot link theme... no theme set\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "No asset directory for this theme\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "However, old link directory symlink found; removed.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "Kunne ikkje lenkja «%s»: %s eksisterer og er ikkje ei symlenkje\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "Hopper over «%s»: allereie satt opp.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "Gamal lenkje funnen for «%s»; fjernar.\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "Finn ikkje CSRF-cookien. Dette er truleg grunna ein cookie-blokkar.<br/>\nSjå til at du tillet cookies for dette domenet."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Orsak, stør ikkje den filtypen :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "klarte ikkje køyra unoconv, sjekk logg-fil"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Skjedde noko gale med video transkodinga"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Stad"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Sjå på <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Godta"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Nekt"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Namn"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Namnet til OAuth-klienten"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Forklaring"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Dette vil vera synleg for brukarar som godtek applikasjonen din til å autentisera dei."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Type"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Confidential</strong> - Konfidensielt, på engelsk: The client can\n make requests to the GNU MediaGoblin instance that can not be\n intercepted by the user agent (e.g. server-side client).<br />\n<strong>Public</strong> - Open, på engelsk: The client can't make confidential\n requests to the GNU MediaGoblin instance (e.g. client-side\n JavaScript client)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Omdirigering URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "Omdirigerings-URI-en for programmene. Denne feltet <strong>krevst</strong> for opne (public) klientar."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Dette feltet krevst for opne (public) klientar"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Klienten {0} er registrert."
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "OAuth klient-tilkoplingar"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Dine OAuth-klientar"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Legg til"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Ugyldig fil for medietypen."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Fil"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Du må velja ei fil."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Johoo! Opplasta!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "La til samlinga «%s»."
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifiser epostadressa di."
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "Logg ut"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Logg inn"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "<a href=\"%(user_url)s\">%(user_name)s</a> sin konto"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Endra kontoinstellingar"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Verkprosesseringspanel"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Logg ut"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Legg til verk"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Lag ny samling"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Bilete av stressa goblin"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Nyaste verk"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Hald oppsyn med statusen for prosessering av verka dine her."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Verk under prosessesering"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Ingen verk vert prosessert"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Klarte ikkje prosessera desse opplasta filene:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Ingen feila filer."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Dei siste ti opplastningane"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Ingenting prossesert, enno."
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Lag nytt passord"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Set passord"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Gløymd passordet?"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Send instruksjonar"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Hei %(username)s,\n\nfor å endra MediaGoblin-passordet ditt, opna fylgjande URL i ein netlesar:\n\n <%(verification_url)s>\n\nDersom du mistenkjer dette er eit misstak, ignorer eposten og hald fram med å vera ein glad goblin!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Innlogging feila"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Har du ingen konto?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Lag ein!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Gløymd passordet?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Lag ein konto."
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Opprett"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Hei %(username)s,\n\nopna fylgjande netadresse i netlesaren din for å aktivera kontoen din:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Drive av <a href=\"http://mediagoblin.org\" title='Version %(version)s'>MediaGoblin</a>, eit <a href=\"http://gnu.org/\">GNU</a>-prosjekt."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Lisensiert med <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Kjeldekode</a> er tilgjengeleg."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Utforsk"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Heihei, velkomen til denne MediaGoblin-sida."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Denne sida køyrer <a href=\"http://mediagoblin.org\">MediaGoblin</a>, eit superbra program for å visa fram dine kreative verk."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Vil du leggja til eigne verk og innpel, so må du logga inn."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Har du ikkje ein enno? Det er enkelt!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Opprett ein konto på denne sida</a>\n eller\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set opp din eigen MediaGoblin-server</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Endrar vedlegg for %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Vedlegg"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Legg ved vedlegg"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Bryt av"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Lagra"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Endrar passordet til %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Lagra"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Sletta brukar '%(user_name)s' og alle relaterte verk og kommentarar?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Ja, slett kontoen min"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Slett permanent"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Endrar %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Endrar kontoinnstellingane til %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Endra passordet ditt."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Slett kontoen min"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Endrar %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Endrar profilen til %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Verk merka med: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Last ned"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Opphavleg"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Orsak, dette lydklippet fungerer ikkje fordi netlesaren din ikkje stør HTML5-lyd."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Du kan skaffa ein moderne netlesar som kan spela av dette lydklippet hjå <a href=\"http://opera.com/download\">http://opera.com/download</a>."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Opphavleg fil"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM-fil (Vorbis-kodek)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Bilete for %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF-fil"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Slå av/på rotering"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspektiv"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Front"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Topp"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Side"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Last ned modell"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Filformat"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Objekthøgd"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Orsak, denne videoen fungerer ikkje\nfordi netlesaren din ikkje stør\nHTML5 video."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Du kan skaffa deg ein moderne netlesar som kan spela denne videoen hjå <a href=http://opera.com>http://opera.com</a> eller <a href=\"http://getfirefox.com\">http://getfirefox.com</a>."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM fil (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Legg til ei samling"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Legg til verk"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (%(username)s si samling)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s av <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Endra"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Slett"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Vil du verkeleg sletta %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Fjerna %(media_title)s frå %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Fjern"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "%(username)s sine samlingar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> sine samlingar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Hei %(username)s,\n%(comment_author)s kommenterte innlegget ditt (%(comment_url)s) hjå %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Verka til %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> sine verk merka <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> sine verk"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Ser på <a href=\"%(user_url)s\">%(username)s</a> sine verk"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Legg att innspel"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Legg til dette innspelet"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "%(formatted_time)s sidan"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Lagt til"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Oppretta"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Putt «%(media_title)s» i samling"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Legg til ei ny samling"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Sjå status for prosessering av verka dine her."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Dine ti siste opplastningar."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)s sin profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Fann ingen slik brukar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Epostverifisering trengst."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Nesten ferdig. Du treng berre aktivera kontoen."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Ein epost med instruksjonar kjem straks."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "I tilfelle det ikkje skjer:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Send ein ny epost"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Dette brukarnamnet finst allereie, men det er ikkje aktivert."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Viss dette er deg, kan du <a href=\"%(login_url)s\">logga inn</a> for å få tilsendt ny epost med stadfestingslenkje."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Her kan du fortelja om deg sjølv."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Endra profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Brukaren har ikkje fylt ut profilen sin (enno)."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Sjå gjennom samlingar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Sjå alle %(username)s sine verk"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Her kjem verka dine."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Ser ikkje ut til at det finst nokon verk her nett no."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(fjern)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "I samling"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Putt i samling"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr " "
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom-kjelde"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Alle rettar reservert"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Nyare"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Eldre →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Gå til side:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "nyare"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "eldre"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Merka med"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Klarte ikkje lesa biletefila."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Oops."
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Noko gjekk gale"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Ulovleg operasjon"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Orsak Dave, eg kan ikkje la deg gjera det!&lt;HAL2000&gt;</p>\n<p>Du prøvde å gjera noko du ikkje har løyve til. Prøvar du å sletta alle brukarkonti no igjen?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Ser ikkje ut til å finnast noko her. Orsak.</p>\n<p>Dersom du er sikker på at adressa finst, so er ho truleg flytta eller sletta."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "år"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "månad"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "veke"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "dag"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "time"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "minutt"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Innspel"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Du kan bruka <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> til formatterring."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Eg er sikker eg vil sletta dette"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Eg er sikker på at eg vil fjerna dette frå samlinga"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Samling"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Vel --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Legg ved eit notat"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "kom med innspel på innlegget ditt"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Innspel er avslege"
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Vops, innspelet ditt var tomt."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Innspelet ditt er lagt til."
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Sjekk filene dine og prøv omatt."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Du må velja eller laga ei samling"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "«%s» er allereie i samling «%s»"
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "«%s» lagt til samling «%s»"
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Du sletta verket."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Sletta ikkje verket."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Du er i ferd med å sletta ein annan brukar sine verk. Trå varsamt."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Du fjerna fila frå samlinga."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Fila var ikkje fjerna fordi du ikkje var sikker."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Du er i ferd med å fjerna ei fil frå ein annan brukar si samling. Trå varsamt."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Samlinga «%s» sletta"
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Sletta ikkje samlinga."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Du er i ferd med å sletta ein annan brukar si samling. Trå varsamt."
diff --git a/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..8b318329
--- /dev/null
+++ b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..78ab219a
--- /dev/null
+++ b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Daniel Koć <kocio@aster.pl>, 2012
+# Sergiusz Pawlowicz <transifex@pawlowicz.name>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-28 13:51+0000\n"
+"Last-Translator: Sergiusz Pawlowicz <transifex@pawlowicz.name>\n"
+"Language-Team: Polish (http://www.transifex.com/projects/p/mediagoblin/language/pl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: pl\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Użytkownik"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Hasło"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Adres e-mail"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Nazwa konta lub adres poczty elektronicznej"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Użytkownik lub adres e-mail"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Nieprawidłowa nazwa konta albo niewłaściwy adres poczty elektronicznej."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Niniejsze pole nie jest przeznaczone na adres poczty elektronicznej."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Niniejsze pole wymaga podania adresu poczty elektronicznej."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Niestety rejestracja w tym serwisie jest wyłączona."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Niestety użytkownik o takiej nazwie już istnieje."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Niestety użytkownik z tym adresem e-mail już istnieje."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Twój adres e-mail został zweryfikowany. Możesz się teraz zalogować, wypełnić opis swojego profilu i wysyłać grafiki!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Nieprawidłowy klucz weryfikacji lub identyfikator użytkownika."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Musisz się zalogować żebyśmy wiedzieli do kogo wysłać e-mail!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Twój adres e-mail już został zweryfikowany!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Wyślij ponownie e-mail weryfikujący."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Jeśli ten adres poczty elektronicznej istnieje (uwzględniając wielkość liter!), wysłano na niego list z instrukcją, w jaki sposób możesz zmienić swoje hasło."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Nie potrafię znaleźć nikogo o tej nazwie użytkownika."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Wysłano e-mail z instrukcjami jak zmienić hasło."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Nie udało się wysłać e-maila w celu odzyskania hasła, ponieważ twoje konto jest nieaktywne lub twój adres e-mail nie został zweryfikowany."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Teraz możesz się zalogować używając nowego hasła."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Tytuł"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Opis tej pracy"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Możesz formatować tekst za pomocą składni \n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Znaczniki"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Rozdzielaj znaczniki przecinkami."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Slug"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Slug nie może być pusty"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Fragment adresu mediów zawierający tytuł. Zwykle nie ma potrzeby aby go zmieniać."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licencja"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Biogram"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Strona internetowa"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Ten adres zawiera błędy"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Ulubiona licencja"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "To będzie twoja domyślna licencja dla wgrywanych mediów."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Powiadamiaj mnie e-mailem o komentarzach do moich mediów"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Tytuł nie może być pusty"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Opis tej kolekcji"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Część adresu zawierająca tytuł. Zwykle nie musisz tego zmieniać."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Stare hasło"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Wprowadź swoje stare hasło aby udowodnić, że to twoje konto."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nowe hasło"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Adres z tym slugiem dla tego użytkownika już istnieje."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Edytujesz media innego użytkownika. Zachowaj ostrożność."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Dodałeś załącznik %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Masz możliwość edycji tylko własnego profilu."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Edytujesz profil innego użytkownika. Zachowaj ostrożność."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Zapisano zmiany profilu"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Zapisano ustawienia konta"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Musisz potwierdzić, że chcesz skasować swoje konto."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Kolekcja \"%s\" już istnieje!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Kolekcja tego użytkownika z takim slugiem już istnieje."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Edytujesz kolekcję innego użytkownika. Zachowaj ostrożność."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Nieprawidłowe hasło"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Twoje hasło zostało zmienione"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Nie można podlinkować motywu... nie wybrano motywu\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Brak katalogu danych dla tego motywu\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Znaleziono stary odnośnik symboliczny do katalogu; usunięto.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "Nie mogę zrobić odnośnika \"%s\": %s istnieje i nie jest odnośnikiem\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "Opuszczam \"%s\"; już jest gotowe.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "Znaleziono stary odnośnik dla \"%s\"; usuwam.\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "Ciasteczko CSFR nie jest dostępne. Najprawdopodobniej stosujesz jakąś formę blokowania ciasteczek.<br/>Upewnij się, że nasz serwer może zakładać ciasteczka w twojej przeglądarce."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "NIestety, nie obsługujemy tego typu plików :-("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "nie dało się uruchomić unoconv, sprawdź log"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Konwersja wideo nie powiodła się"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Położenie"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Zobacz na <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Zezwól"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Odrzuć"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nazwa"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Nazwa klienta OAuth"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Opis"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "To będzie widoczne dla użytkowników, pozwalając⏎ twojej aplikacji uwierzytelniać się jako oni."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Typ"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Confidential</strong> - Klient może wysyłać żądania\n do instancji GNU MediaGoblin, która nie może zostać\n przechwycona przez agenta (np. klient po stronie serwera).<br />\n <strong>Public</strong> - Klient nie może wysyłać poufnych\n żądań do instakcji GNU MediaGoblin (np. skrypt JavaScript\n po stronie klienta)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Przekierowanie URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "Przekierowanie URI dla aplikacji, to pole\n jest <strong>wymagane</strong> dla publicznych klientów."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "To pole jest wymagane dla klientów publicznych"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Klient {0} został zarejestrowany!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "Połączenia do OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Twoi klienci OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Dodaj"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Niewłaściwy plik dla tego rodzaju mediów."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Plik"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Musisz podać plik."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Hura! Wysłano!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Kolekcja \"%s\" została dodana!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Zweryfikuj swój adres e-mail!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "wyloguj się"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Zaloguj się"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "konto <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Zmień ustawienia konta"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Panel przetwarzania mediów"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Wyloguj się"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Dodaj media"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Utwórz nową kolekcję"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Grafika zestresowanego goblina"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Najnowsze media"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Tu możesz śledzić stan przetwarzania mediów na tym serwerze."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Przetwarzane media"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Żadne media nie są obecnie przetwarzane"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "NIe udało się przesłać tych plików:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Brak nieprzetworzonych wpisów!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Ostatnie 10 udanych wysyłek"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Na razie nie przetworzono żadnego wpisu!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Podaj swoje nowe hasło"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Podaj hasło"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Odtwórz hasło"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Wyślij instrukcje"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Cześć %(username)s,\n\naby zmienić twoje hasło dla GNU MediaGoblin, otwórz następującą stronę w swojej przeglądarce:\n\n%(verification_url)s\n\nJeśli sądzisz, że to pomyłka, po prostu zignoruj tę wiadomość i bądź nadal szczęśliwym goblinem!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Logowanie nie powiodło się!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Nie masz jeszcze konta?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Utwórz je tutaj!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Zapomniałeś hasła?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Utwórz konto!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Utwórz"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Cześć %(username)s,\n\naby aktywować twoje konto GNU MediaGoblin, otwórz następującą stronę w swojej przeglądarce:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Napędzane przez oprogramowanie <a href=\"http://mediagoblin.org/\" title='w wersji %(version)s'>MediaGoblin</a>, będące częścią projektu <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Opublikowane na licencji <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. Dostępny jest <a href=\"%(source_link)s\">kod źródłowy</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Odkrywaj"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Cześć, witaj na stronie MediaGoblin!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Ten serwis działa w oparciu o <a href=\"http://mediagoblin.org\">MediaGoblin</a>, świetne oprogramowanie do publikowania mediów."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Aby dodawać swoje pliki, komentować i wykonywać inne czynności, możesz się zalogować na swoje konto MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Jeszcze go nie masz? To proste!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Załóż konto na tym serwerze</a>\n albo\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Uruchom MediaGoblin na swoim własnym serwerze</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Logo MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Edycja załączników do %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Załączniki"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Dodaj załącznik"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Zapisz zmiany"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Zmieniam hasło użytkownika %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Zachowaj"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Czy naprawdę skasować użytkownika '%(user_name)s' oraz usunąć wszystkie jego pliki i komentarze?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Tak, naprawdę chcę skasować swoje konto"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Usuń na stałe"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Edytowanie %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Zmiana ustawień konta %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Zmień swoje hasło."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Usuń moje konto"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Edycja %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Edycja profilu %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Media ze znacznikami: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Pobierz"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Oryginał"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Niestety, ten plik dźwiękowy nie zostanie odtworzony, \n\tponieważ ta przeglądarka nie obsługuje znaczników \n\tdźwięku w HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Proszę pobrać przeglądarkę, która obsługuje \n\tdźwięk w HTML5, pod adresem <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Oryginalny plik"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "plik WebM (kodek Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Grafika dla %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "Plik PDF"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Obróć"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspektywa"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Początek"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Góra"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Krawędź"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Pobierz model"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Format pliku"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Wysokość obiektu"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Niestety ten materiał nie będzie widoczny⏎, ponieważ twoja przeglądarka nie⏎ osbługuje formatu HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Możesz pobrać porządną przeglądarkę, która jest w stanie odtworzyć ten materiał filmowy, ze strony <a href=\"http://getfirefox.com/\">⏎ http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "plik WebM (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Dodaj kolekcję"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Dodaj swoje media"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (kolekcja użytkownika %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s użytkownika <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Edytuj"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Usuń"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Na pewno usunąć %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Na pewno usunąć %(media_title)s z %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Usuń"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "kolekcja użytkownika %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "kolekcje użytkownika <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Witaj %(username)s,\n%(comment_author)s skomentował twój wpis (%(comment_url)s) na %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Media użytkownika %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "pliki użytkownika <a href=\"%(user_url)s\">%(username)s</a> z tagiem <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "media użytkownika <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Przeglądanie mediów użytkownika <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Dodaj komentarz"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Dodaj komentarz"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "%(formatted_time)s temu"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Dodano"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Utworzono"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Dodaj “%(media_title)s” do kolekcji"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Dodaj nową kolekcję"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Tutaj możesz śledzić stan mediów przesyłanych do twojej galerii."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Ostatnie 10 twoich udanych wysyłek"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profil użytkownika %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Niestety, nie znaleziono takiego użytkownika."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Wymagana weryfikacja adresu e-mail."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Prawie gotowe! Twoje konto oczekuje na aktywację."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Za kilka chwil powinieneś otrzymać e-mail z instrukcjami jak to zrobić."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Jeśli nie nadejdzie:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Wyślij ponownie e-mail weryfikujący"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Ktoś zarejestrował konto o tej nazwie, ale nadal oczekuje ono na aktywację."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Jeśli jesteś tą osobą, ale zgubiłeś swój e-mail weryfikujący, to możesz się <a href=\"%(login_url)s\">zalogować</a> i wysłać go ponownie."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "W tym miejscu można się przedstawić innym."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Edytuj profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Ten użytkownik nie wypełnił (jeszcze) opisu swojego profilu."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Przeglądaj kolekcje"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Zobacz wszystkie media użytkownika %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Tu będą widoczne twoje media, ale na razie niczego tu jeszcze nie ma."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Tu nie ma jeszcze żadnych mediów..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(usuń)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "Znajduje się w kolekcji "
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Dodaj do kolekcji"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "ikona kanału"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Kanał Atom"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Wszystkie prawa zastrzeżone"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Nowsze"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Starsze →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Idź do strony:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "nowsze"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "starsze"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Znaczniki:"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Nie udało się odczytać pliku grafiki."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Ups!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Wystąpił błąd"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Operacja niedozwolona"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Misiaczku, nie możesz tego uczynić!</p><p>Próbowałeś wykonać działanie, do którego nie masz uprawnień. Czy naprawdę chciałeś skasować znowu wszystkie konta?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Wygląda na to, że nic tutaj nie ma!</p><p>Jeśli jesteś pewny, że adres jest prawidłowy, być może strona została skasowana lub przeniesiona."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "rok"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "miesiąc"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "tydzień"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "dzień"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "godzina"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "minuta"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Komentarz"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Możesz formatować przy pomocy składni <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Na pewno chcę to usunąć"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Na pewno chcę usunąć ten element z kolekcji"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Kolekcja"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- wybierz --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Dodaj notatkę"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "komentarze do twojego wpisu"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Komentowanie jest wyłączone."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Ups, twój komentarz nie zawierał treści."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Twój komentarz został opublikowany!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Sprawdź swoje wpisy i spróbuj ponownie."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Musisz wybrać lub dodać kolekcję"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" już obecne w kolekcji \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" dodano do kolekcji \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Media zostały usunięte."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Media nie zostały usunięte ponieważ nie potwierdziłeś, że jesteś pewien."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Za chwilę usuniesz media innego użytkownika. Zachowaj ostrożność."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Element został usunięty z kolekcji."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Ten element nie został usunięty, ponieważ nie zaznaczono, że jesteś pewien."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Zamierzasz usunąć element z kolekcji innego użytkownika. Zachowaj ostrożność."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Usunięto kolekcję \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Ta kolekcja nie została usunięta, ponieważ nie zaznaczono, że jesteś pewien."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Zamierzasz usunąć kolekcję innego użytkownika. Zachowaj ostrożność."
diff --git a/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..5e83a7f2
--- /dev/null
+++ b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..fecb844c
--- /dev/null
+++ b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1256 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# osc <snd.noise@gmail.com>, 2013
+# Rafael Ferreira <rafael.f.f1@gmail.com>, 2013
+# osc <snd.noise@gmail.com>, 2011
+# ufa <ufa@technotroll.org>, 2011
+# Canopus <viniciussm@rocketmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/mediagoblin/language/pt_BR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: pt_BR\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Nome de Usuário"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Senha"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Endereço de email"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Nome de usuário ou email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Nome de usuário ou email inválido."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Este campo não aceita endereços de email."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Este campo requer um endereço de email."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Desculpa, o registro está desativado neste momento."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Desculpe, um usuário com este nome já existe."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Desculpe, um usuário com esse email já está cadastrado"
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "O seu endereço de e-mail foi verificado. Você pode agora fazer login, editar seu perfil, e enviar imagens!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "A chave de verificação ou nome usuário estão incorretos."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Você precisa entrar primeiro para sabermos para quem mandar o email!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Você já verificou seu email!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "O email de verificação foi enviado novamente."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Se esse endereço de email (sensível a maiúsculo/minúsculo!) estiver registrado, um email será enviado com instruções para alterar sua senha."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Não foi possível encontrar alguém com esse nome de usuário."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Um email foi enviado com instruções para trocar sua senha."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Não foi possível enviar o email de recuperação de senha, pois seu nome de usuário está inativo ou o email da sua conta não foi confirmado."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Agora você pode entrar usando sua nova senha."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Título"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Descrição desse trabalho"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Você pode usar\n<a href=\"http://daringfireball.net/projects/markdown/basics\">\nMarkdown</a> para formatação."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etiquetas"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Separe as etiquetas com vírgulas."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Arquivo"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "O arquivo não pode estar vazio"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "A parte do título do endereço dessa mídia. Geralmente você não precisa mudar isso."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licença"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Biografia"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Website"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Este endereço contém erros"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Licença preferida"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Esta será sua licença padrão nos formulários de envio."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Me enviar um email quando outras pessoas comentarem em minhas mídias"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "O título não pode ficar vazio"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Descrição desta coleção"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "A parte do título do endereço dessa coleção. Geralmente você não precisa mudar isso."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Senha antiga"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Digite sua senha antiga para provar que esta conta é sua."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nova senha"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Uma entrada com esse arquivo já existe para esse usuário"
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Você está editando a mídia de outro usuário. Tenha cuidado."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Você adicionou o anexo %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Você só pode editar o seu próprio perfil."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Você está editando um perfil de usuário. Tenha cuidado."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "As mudanças no perfil foram salvas"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "As mudanças na conta foram salvas"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Você precisa confirmar a exclusão da sua conta."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Você já tem uma coleção chamada \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Já existe uma coleção com este arquivo para este usuário."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Você está editando a coleção de um outro usuário. Prossiga com cuidado."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Senha errada"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Não é possível fazer link de tema... nenhum tema definido\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "Cookie CSFR não está presente. Isso é provavelmente o resultado de um bloqueador de cookies ou algo do tipo.<br/>Tenha certeza de autorizar este domínio a configurar cookies."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Desculpe, não tenho suporte a este tipo de arquivo :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Conversão do vídeo falhou"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Localização"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Ver no <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Permitir"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Negar"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nome"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "O nome do cliente OAuth"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Descrição"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Tipo"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Redirecionar URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Este campo é necessário para clientes públicos"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "O cliente {0} foi registrado!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Seus clientes OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Adicionar"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Arquivo inválido para esse tipo de mídia"
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Arquivo"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Você deve fornecer um arquivo."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Eba! Enviado!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Coleção \"%s\" adicionada!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifique seu email!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "sair"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Entrar"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Conta de <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Mudar configurações da conta"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Painel de processamento de mídia"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Sair"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Adicionar mídia"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Criar nova coleção"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Imagem do goblin se estressando"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Mídia mais recente"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Mídia em processo"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Nenhuma mídia em processo"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Esses envios não foram processados:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Nenhuma entrada falhou!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Últimos 10 envios bem sucedidos"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Ainda não há entradas processadas!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Defina a sua nova senha"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Definir senha"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Recuperar senha"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Mandar instruções"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Olá %(username)s,\n\npara alterar sua senha do GNU MediaGoblin, abra a seguinte URL\nno seu navegador web:\n\n%(verification_url)s\n\nSe você acha que isso é um erro, desconsidere esse email e continue sendo um goblin feliz"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Autenticação falhou"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Ainda não tem conta?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Crie uma aqui!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Esqueceu sua senha?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Criar uma conta!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Criar"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Olá %(username)s,\n\nPara ativar sua conta GNU MediaGoblin, visite este endereço no seu navegador:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Fornecido pelo <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, um projeto <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Lançado sob a <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Código fonte</a> disponível."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Explorar"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Olá, bem-vindo a este site MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Este site roda o <a href=\"http://mediagoblin.org\">MediaGoblin</a>, um programa excelente para hospedar, gerenciar e compartilhar mídia."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Para adicionar sua própria mídia, publicar comentários e mais outras coisas, você pode entrar com sua conta MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr " Ainda não tem uma conta? É facil!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Logo MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Editando os anexos de %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Anexos"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Adicionar anexo"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Salvar mudanças"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Realmente deletar o usuário '%(user_name)s' e todas as mídias e comentários associados?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Sim, realmente deletar minha conta"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Deletar permanentemente"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Editando %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Alterando as configurações da conta de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Deletar minha conta"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Editando %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Editando perfil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Etiquetas desta mídia: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Baixar"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Desculpe, este áudio não irá reproduzir porque \n »seu navegador não oferece suporte a áudio \n »HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Você pode obter um navegador moderno\n »capaz de reproduzir o áudio em <a href=\"http://getfirefox.com\">\n » http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Arquivo original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "Arquivo WebM (codec Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Imagem para %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Alternar Rotação"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspectiva"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Frente"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Cima"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Lado"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Baixar o modelo"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Formato de Arquivo"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Altura do Objeto"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Desculpe, este vídeo não irá reproduzir porque\n seu navegador não suporta vídeo\n HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Você pode obter um navegador moderno\n capaz de reproduzir este vídeo em <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "Arquivo WebM (640p, VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Adicionar uma coleção"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Adicionar sua mídia"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (Coleção de %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Editar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Apagar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Realmente apagar %(title)s ?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Realmente remover %(media_title)s de %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Apagar"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Coleções de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Coleções de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Olá %(username)s,\n %(comment_author)s comentou na sua publicação (%(comment_url)s) em %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Mídia de %(username)s's"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Mídias de <a href=\"%(user_url)s\">%(username)s</a> com a etiqueta <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Mídia de <a href=\"%(user_url)s\"> %(username)s </a> "
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Vendo mídia de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Adicionar um comentário"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Adicionar este comentário"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Adicionar \"%(media_title)s\" a uma coleção"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Adicionar uma nova coleção"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Você pode verificar como a mídia esta sendo processada para sua galeria aqui"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Seus últimos 10 envios bem sucedidos"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Perfil de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Desculpe, esse usuário não foi encontrado."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Verificação de email necessária"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Quase pronto! Sua conta ainda precisa ser ativada."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Um email deve chegar em instantes com instruções de como fazê-lo."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Caso contrário:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Reenviar email de verificação"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Alguém registrou uma conta com esse nome de usuário, mas ainda precisa ser ativada."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Se você é essa pessoa, mas você perdeu seu e-mail de verificação, você pode <a href=\"%(login_url)s\">efetuar login</a> e reenviá-la."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Aqui é o lugar onde você fala de si para os outros."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Editar perfil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Esse usuário não preencheu seu perfil (ainda)."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Ver coleções"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Ver todas as mídias de %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Aqui é onde sua mídia vai aparecer, mas parece que você não adicionou nada ainda."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Parece que ainda não há nenhuma mídia por aqui..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(apagar)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Adicionar a uma coleção"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "ícone feed"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Todos os direitos reservados"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Novos"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Antigos →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Ir a página:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "mais nova"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "mais antiga"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Etiquetas"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Não foi possível ler o arquivo de imagem."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Oops"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Um erro ocorreu"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Operação não permitida"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Me desculpe Dave, não posso deixar você fazer isso!</p><p>Você tentou executar uma função sem autorização. Por acaso estava novamente tentando deletar todas as contas de usuários?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Parece que não há uma página com este endereço. Desculpe!</p><p>Se você tem certeza que este endereço está correto, talvez a página que esteja procurando tenha sido movida ou deletada."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Comentário"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Você pode usar <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> para formatação."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Eu tenho certeza de que quero apagar isso"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Tenho certeza que quero remover este item da coleção"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Coleção"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Selecionar --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Incluir uma nota"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "comentou na sua publicação"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Ops, seu comentário estava vazio."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Seu comentário foi postado!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Por favor, verifique suas entradas e tente novamente."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Você deve selecionar ou adicionar uma coleção"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" já está na coleção \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" adicionado à coleção \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Você deletou a mídia."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "A mídia não foi apagada porque você não marcou que tinha certeza."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Você vai apagar uma mídia de outro usuário. Tenha cuidado."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Você deletou o item da coleção."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "O item não foi apagado porque você não marcou que tinha certeza."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Você está prestes a remover um item da coleção de um outro usuário. Prossiga com cuidado."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Você deletou a coleção \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "A coleção não foi apagada porque você não marcou que tinha certeza."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Você está prestes a deletar a coleção de um outro usuário. Prossiga com cuidado."
diff --git a/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..8cfdf339
--- /dev/null
+++ b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..af2d94d6
--- /dev/null
+++ b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# George Pop <gapop@hotmail.com>, 2011
+# George Pop <gapop@hotmail.com>, 2011-2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 20:40+0000\n"
+"Last-Translator: George Pop <gapop@hotmail.com>\n"
+"Language-Team: Romanian (http://www.transifex.com/projects/p/mediagoblin/language/ro/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ro\n"
+"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Nume de utilizator"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Parolă"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Adresa de e-mail"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Numele de utilizator sau adresa de e-mail"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Numele de utilizator sau adresa de e-mail"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Nume de utilizator sau adresă de e-mail nevalidă."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Această rubrică nu este pentru adrese de e-mail."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Această rubrică trebuie completată cu o adresă de e-mail."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Ne pare rău, dar înscrierile sunt dezactivate pe acest server."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Ne pare rău, există deja un utilizator cu același nume."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Există deja un utilizator înregistrat cu această adresă de e-mail."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Adresa ta de e-mail a fost verificată. Poți să te autentifici, să îți completezi profilul și să trimiți imagini!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Cheie de verificare sau user ID incorect."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Trebuie să fii autentificat ca să știm cui să trimitem mesajul!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Adresa ta de e-mail a fost deja verificată!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "E-mail-ul de verificare a fost retrimis."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Dacă adresa de e-mail este în baza noastră de date, atunci se va trimite imediat un mesaj cu instrucțiuni pentru schimbarea parolei. Țineți cont de litere mari / litere mici la introducerea adresei!"
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Nu există nimeni cu acest nume de utilizator."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "S-a trimis un e-mail cu instrucțiuni pentru schimbarea parolei."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "E-mailul pentru recuperarea parolei nu a putut fi trimis deoarece contul tău e inactiv sau adresa ta de e-mail nu a fost verificată."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Acum te poți autentifica cu noua parolă."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titlu"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Descrierea acestui fișier"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Poți folosi\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> pentru formatare."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Cuvinte-cheie"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Desparte cuvintele-cheie prin virgulă."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Identificator"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Identificatorul nu poate să lipsească"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Partea corespunzătoare titlului din adresa acestui fișier media. De regulă poate fi lăsată nemodificată."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licența"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Biografie"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Sit Web"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Această adresă prezintă erori"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Licența preferată"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Aceasta va fi licența implicită pe formularele de upload."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Trimite-mi un e-mail când alții comentează fișierele mele"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Titlul nu poate să fie gol"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Descriere pentru această colecție"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Partea din adresa acestei colecții care corespunde titlului. De regulă nu e necesar să faci o modificare."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Vechea parolă"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Introdu vechea parolă pentru a demonstra că ești titularul acestui cont."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Noua parolă"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Există deja un entry cu același identificator pentru acest utilizator."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Editezi fișierul unui alt utilizator. Se recomandă prudență."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Ai anexat %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Nu poți modifica decât propriul tău profil."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Editezi profilul unui utilizator. Se recomandă prudență."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Modificările profilului au fost salvate"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Setările pentru acest cont au fost salvate"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Trebuie să confirmi ștergerea contului tău."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Ai deja o colecție numită \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "O colecție cu același slug există deja pentru acest utilizator."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Lucrezi pe colecția unui alt utilizator. Se recomandă prudență."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Parolă incorectă"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Parola a fost schimbată cu succes"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Tema nu poate fi atașată... nu există o temă selectată\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Nu există un folder de elemente pentru această temă\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "A fost însă găsit un symlink către vechiul folder; s-a șters.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "Nu s-a putut crea link pentru \"%s\": %s există deja și nu este symlink\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "S-a omis \"%s\"; configurat deja.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "Există deja un link pentru \"%s\"; va fi șters.\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "Lipsește cookie-ul CSRF. Probabil că blocați cookie-urile.<br/>Asigurați-vă că există permisiunea setării cookie-urilor pentru acest domeniu."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Scuze, nu recunosc acest tip de fișier :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "unoconv nu poate fi executat; verificați log-ul"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Transcodarea video a eșuat"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Locul"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Vezi pe <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Permite"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Refuză"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Nume"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Numele clientului OAuth"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Descriere"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Aceste informații vor fi vizibile pentru utilizatorii\n care permit aplicației tale să se autentifice în numele lor."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Tip"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Confidențial</strong> - Client poate\n trimite cereri către instanța GNU MediaGoblin care nu pot fi\n interceptate de către user agent (de ex., clientul de pe server).<br />\n <strong>Public</strong> - Clientul nu poate trimite cereri confidențiale\n către instanța GNU MediaGoblin (de ex., un client\n JavaScript)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "URI redirectare"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "URI-ul de redirectare pentru aplicații, această rubrică\n este <strong>obligatorie</strong> pentru clienții publici."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Această rubrică este obligatorie pentru clienții publici"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Clientul {0} a fost înregistrat!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "Conexiuni client OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Clienții tăi OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Adaugă"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Formatul fișierului nu corespunde cu tipul de media selectat."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Fișier"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Trebuie să selectezi un fișier."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Ura! Trimis!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Colecția \"%s\" a fost creată!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifică adresa de e-mail!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "Ieșire"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Autentificare"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Contul lui <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Modifică setările contului"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Panou de procesare media"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Ieșire"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Trimite fișier"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Creează colecție nouă"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Imagine cu un goblin stresat"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Cele mai recente fișiere"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Aici poți urmări starea fișierelor aflate în curs de procesare pe acest server."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Fișiere în curs de procesare"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Niciun fișier în curs de procesare"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Aceste fișiere nu au putut fi procesate:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Niciun entry cu erori!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Ultimele 10 upload-uri reușite"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Nu există încă niciun entry procesat!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Stabilește noua parolă"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Stabilește parola"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Recuperează parola"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Trimite instrucțiuni"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Bună, %(username)s\n\nPentru a schimba parola ta la GNU MediaGoblin, accesează adresa următoare:\n\n%(verification_url)s\n\nDacă ai primit acest mesaj din greșeală, ignoră-l și fii mai departe un goblin fericit!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Autentificare eșuată!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Nu ai un cont?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Creează-l aici!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Ai uitat parola?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Creează un cont!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Creează"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Bună, %(username)s,\n\npentru activarea contului tău la GNU MediaGoblin, accesează adresa următoare:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Construit cu <a href=\"http://mediagoblin.org/\" title='Versiunea %(version)s'>MediaGoblin</a>, un proiect <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Publicat sub licența <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Codul sursă</a> este disponibil."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Explorează"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Salut, bine ai venit pe acest site MediaGoblin!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Acest site folosește <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un software excepțional pentru găzduirea fișierelor media."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Pentru a adăuga fișierele tale și pentru a comenta te poți autentifica cu contul tău MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Încă nu ai unul? E simplu!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Creați un cont pe acest site</a>\n sau\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instalați MediaGoblin pe serverul dvs.</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "logo MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Editare anexe la %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Anexe"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Atașează"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Anulare"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Salvează modificările"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Se modifică parola pentru %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Salvează"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Sigur dorești ștergerea utilizatorului '%(user_name)s' și a fișierelor/comentariilor acestuia?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Da, doresc ștergerea contului meu"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Șterge definitiv"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Editare %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Se modifică setările contului pentru userul %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Modifică parolă."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Șterge contul meu"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Editare %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Editare profil %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Fișier etichetat cu cuvintele-cheie: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Download"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Ne pare rău, această înregistrare audio nu poate fi redată, deoarece \n\tbrowserul tău nu este compatibil cu funcția audio din HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Poți lua un browser modern \n\tcapabil să redea această înregistrare de la <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Fișierul original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "Fișier WebM (codec Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Imagine pentru %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "Fișier PDF"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Rotire"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspectivă"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Din față"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "De sus"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Lateral"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Descarcă modelul"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Formatul fișierului"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Înălțimea obiectului"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Ne pare rău, dar această înregistrare video nu va funcționa deoarece browser-ul dvs. nu este compatibil cu HTML5 video."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Puteți obține un browser Web modern care poate reda această înregistrare de la <a href=\"http://getfirefox.com\">http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "Fișier WebM (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Creează o colecție"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Adaugă fișierele tale media"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (colecție a lui %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Editare"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Șterge"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Sigur dorești să ștergi %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Sigur dorești să ștergi %(media_title)s din %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Șterge"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Colecțiile utilizatorului %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Colecțiile utilizatorului <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Bună, %(username)s,\n%(comment_author)s a făcut un comentariu la postarea ta (%(comment_url)s) de la %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Fișierele lui %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Fișierele lui <a href=\"%(user_url)s\">%(username)s</a> cu cuvântul-cheie <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Fișierele media ale lui <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "<p>❖ Fișierele media ale lui <a href=\"%(user_url)s\">%(username)s</a></p>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Adaugă un comentariu"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Trimite acest comentariu"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "în urmă cu %(formatted_time)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Adăugat"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Creat"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Adaugă „%(media_title)s” la o colecție"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Creează o nouă colecție"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Aici poți urmări stadiul procesării fișierelor media din galeria ta."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Ultimele tale 10 upload-uri reușite"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profil %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Ne pare rău, nu am găsit utilizatorul căutat."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Este necesară verificarea adresei de e-mail"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Aproape gata! Mai trebuie doar să activezi contul."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Vei primi în scurt timp un e-mail cu instrucțiuni."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Dacă nu-l primești:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Retrimite mesajul de verificare"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Cineva a înregistrat un cont cu acest nume de utilizator, dar contul nu a fost încă activat."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Dacă tu ești persoana respectivă și nu mai ai e-mail-ul de verificare, poți să te <a href=\"%(login_url)s\">autentifici</a> pentru a-l retrimite."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Aici poți spune altora ceva despre tine."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Editare profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Acest utilizator nu și-a completat (încă) profilul."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Vizitează colecțiile"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Vezi toate fișierele media ale lui %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Aici vor apărea fișierele tale media, dar se pare că încă nu ai trimis nimic."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Nu pare să existe niciun fișier media deocamdată..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(șterge)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "Din colecția"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Adaugă la o colecție"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "icon feed"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "feed Atom"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Toate drepturile rezervate"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Mai noi"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Mai vechi →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Salt la pagina:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "mai noi"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "mai vechi"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Etichetat cu cuvintele-cheie"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Fișierul cu imaginea nu a putut fi citit."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Hopa!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "S-a produs o eroare"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Operația nu este permisă"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Îmi pare rău, Dave, nu te pot lăsa să faci asta!</p><p>Ai încercat să faci o operație nepermisă. Ai încercat iar să ștergi toate conturile utilizatorilor?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Nu există nicio pagină la această adresă.</p><p>Dacă sunteți sigur că adresa este corectă, poate că pagina pe care o căutați a fost mutată sau ștearsă."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "anul"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "luna"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "săptămâna"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "ziua"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "ora"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "minutul"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Comentariu"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Poți folosi <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> pentru formatare."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Sunt sigur că doresc să șterg"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Sunt sigur(ă) că vreau să șterg acest articol din colecție"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Colecție"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Selectează --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Adaugă o notiță"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "a făcut un comentariu la postarea ta"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Comentariile sunt dezactivate."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Hopa, ai uitat să scrii comentariul."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Comentariul tău a fost trimis!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Verifică datele și încearcă din nou."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Trebuie să alegi sau să creezi o colecție"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" este deja în colecția \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" a fost adăugat la colecția \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Ai șters acest fișier"
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Fișierul nu a fost șters deoarece nu ai confirmat că ești sigur."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Urmează să ștergi fișierele media ale unui alt utilizator. Se recomandă prudență."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Ai șters acest articol din colecție."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Articolul nu a fost șters pentru că nu ai confirmat că ești sigur(ă)."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Urmează să ștergi un articol din colecția unui alt utilizator. Se recomandă prudență."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Ai șters colecția \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Colecția nu a fost ștearsă pentru că nu ai confirmat că ești sigur(ă)."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Urmează să ștergi colecția unui alt utilizator. Se recomandă prudență."
diff --git a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..ed28ff43
--- /dev/null
+++ b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..d0ff7bdd
--- /dev/null
+++ b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# aleksejrs <deletesoftware@yandex.ru>, 2013
+# aleksejrs <deletesoftware@yandex.ru>, 2011-2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-01 21:08+0000\n"
+"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n"
+"Language-Team: Russian (http://www.transifex.com/projects/p/mediagoblin/language/ru/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: ru\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Логин"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Пароль"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Адрес электронной почты"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Имя пользователя или адрес электронной почты"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Имя пользователя или адрес электронной почты"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Это поле не для адреса электронной почты."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Это поле — для адреса электронной почты."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Извините, на этом сайте регистрация запрещена."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Извините, пользователь с этим именем уже зарегистрирован."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Сожалеем, но на этот адрес электронной почты уже зарегистрирована другая учётная запись."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Ваш адрес электронной почты потвержден. Вы теперь можете войти и начать редактировать свой профиль и загружать новые изображения!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Неверный ключ проверки или идентификатор пользователя"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Вам надо представиться, чтобы мы знали, кому отправлять сообщение!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Вы уже потвердили свой адрес электронной почты!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Переслать сообщение с подтверждением аккаунта."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Если с этим адресом электронной почты (сравниваемым чувствительно к регистру символов!) есть учётная запись, то на него отправлено сообщение с указаниями о том, как сменить пароль."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Не найдено никого с таким именем пользователя."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Вам отправлено электронное письмо с инструкциями по смене пароля."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Мы не можем отправить сообщение для восстановления пароля, потому что ваша учётная запись неактивна, либо указанный в ней адрес электронной почты не был подтверждён."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Теперь вы можете войти, используя ваш новый пароль."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Название"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Описание этого произведения"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Для разметки можете использовать язык\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Метки"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "(через запятую)"
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Отличительная часть адреса"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Отличительная часть адреса необходима"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Часть адреса этого файла, производная от его названия. Её обычно не требуется изменять."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Лицензия"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Биография"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Сайт"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Этот адрес содержит ошибки"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Предпочитаемая лицензия"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Она будет лицензией по умолчанию для ваших загрузок"
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Уведомлять меня по e-mail о комментариях к моим файлам"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Название не может быть пустым"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Описание этой коллекции"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Отличительная часть адреса этой коллекции, основанная на названии. Обычно не нужно её изменять."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Старый пароль"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Введите свой старый пароль в качестве доказательства, что это ваша учётная запись."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Новый пароль"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "У этого пользователя уже есть файл с такой отличительной частью адреса."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Вы редактируете файлы другого пользователя. Будьте осторожны."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Вы добавили сопутствующий файл %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Вы можете редактировать только свой собственный профиль."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Вы редактируете профиль пользователя. Будьте осторожны."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Изменения профиля сохранены"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Настройки учётной записи записаны"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Вам нужно подтвердить, что вы хотите удалить свою учётную запись."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "У вас уже есть коллекция с названием «%s»!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "У этого пользователя уже есть коллекция с такой отличительной частью адреса."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Вы редактируете коллекцию другого пользователя. Будьте осторожны."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Неправильный пароль"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Ваш пароль сменён успешно"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Невозможно привязать тему… не выбрано существующей темы\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "У этой темы отсутствует каталог с элементами оформления\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Однако найдена (и удалена) старая символическая ссылка на каталог.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Увы, я не поддерживаю этот тип файлов :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Перекодировка видео не удалась"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "На карте"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Посмотреть на <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Описание"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Его увидят пользователи, разрешающие вашему приложению действовать от их имени."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Тип"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Клиент {0} зарегистрирован!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Добавить"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Неправильный формат файла."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Файл"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Вы должны загрузить файл."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Ура! Файл загружен!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Коллекция «%s» добавлена!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Подтвердите ваш адрес электронной почты!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "завершение сеанса"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Войти"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Учётная запись <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Изменить настройки учётной записи"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Панель обработки файлов"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Завершение сеанса"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Добавить файлы"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Создать новую коллекцию"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Изображение нервничающего гоблина"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Самые новые файлы"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Здесь вы можете следить за состоянием обработки файлов для данного сайта."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Обработка файлов в процессе"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Нету файлов для обработки"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Обработка этих файлов вызвала ошибку:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Неудавшихся задач нет!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Последние 10 удавшихся загрузок"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Выполненных задач пока нет!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Введите свой новый пароль"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Установить пароль"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Сброс пароля"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Отправить инструкцию"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Привет, %(username)s,\n\nчтобы сменить свой пароль от GNU MediaGoblin, откройте\nследующий URL вашим веб‐браузером:\n\n%(verification_url)s\n\nЕсли вы думаете, что это какая‐то ошибка, то игнорируйте\nэто сообщение и продолжайте быть счастливым гоблином!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Авторизация неуспешна!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Ещё нету аккаунта?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Создайте здесь!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Забыли свой пароль?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Создать аккаунт!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Создать"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Привет, %(username)s!\n\nЧтобы активировать свой аккаунт в GNU MediaGoblin, откройте в своём веб‐браузере следующую ссылку:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Работает на <a href=\"http://mediagoblin.org/\" title='Версии %(version)s'>MediaGoblin</a>, проекте <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Он опубликован на условиях <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. Доступны <a href=\"%(source_link)s\">исходные тексты</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Смотреть"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Привет! Добро пожаловать на наш MediaGoblin’овый сайт!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Этот сайт работает на <a href=\"http://mediagoblin.org\">MediaGoblin</a>, необыкновенно замечательном ПО для хостинга мультимедийных файлов."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Для добавления собственных файлов, комментирования и т. п. вы можете представиться с помощью вашей MediaGoblin’овой учётной записи."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "У вас её ещё нет? Не проблема!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Символ MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Добавление сопутствующего файла для %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Сопутствующие файлы"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Добавить сопутствующий файл"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Отмена"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Сохранить изменения"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Смена пароля %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Сохранить"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "На самом деле удалить аккаунт «%(user_name)s» и все связанные файлы и комментарии?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Да, на самом деле удалить мою учётную запись"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Удалить безвозвратно"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Редактирование %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Настройка учётной записи %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Сменить пароль"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Удалить мою учётную запись"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Редактирование %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Редактирование профиля %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Файлы с меткой: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Скачать"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Оригинал"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Сожалеем, этот аудиоролик не проиграется, ⏎\n» потому что ваш браузер не поддерживает ⏎\n» аудио в соответствии со стандартом HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Вы можете скачать современный браузер, \n\tспособный проиграть это аудио, с <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Исходный файл"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM‐файл (кодек — Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Изображение «%(media_title)s»"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF-файл"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Перспектива"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Спереди"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Сверху"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Сбоку"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Скачать модель"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Формат файла"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Высота объекта"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Сожалеем, этот ролик не проиграется, ⏎\nпотому что ваш браузер не поддерживает ⏎\nвидео в соответствии со стандартом HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Вы можете скачать современный браузер, способный воспроизводить это видео, с <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM-файл (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Добавление коллекции"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Добавление ваших файлов"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (коллекция пользователя %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s пользователя <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Изменить"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Удалить"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Удалить %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "В самом деле исключить %(media_title)s из %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Исключить"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Коллекции %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Коллекции <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Привет, %(username)s.\nПользователь %(comment_author)s оставил комментарий к вашему файлу (%(comment_url)s) at %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Файлы %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Файлы <a href=\"%(user_url)s\">%(username)s</a> с меткой <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Файлы пользователя <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Просмотр файлов пользователя <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Добавить комментарий"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Добавить этот комментарий"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "%(formatted_time)s назад"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Добавлен"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Создан"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Добавление «%(media_title)s» в коллекцию"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Добавление новой коллекции"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Вы можете следить за статусом обработки файлов для вашей галереи здесь."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Ваши последние 10 удавшихся загрузок"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Профиль пользователя %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Извините, но такой пользователь не найден."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Нужно подтверждение почтового адреса"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Почти закончили! Теперь надо активировать ваш аккаунт."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Через пару мгновений на адрес вашей электронной почты должно прийти сообщение с дальнейшими инструкциями."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "А если нет, то:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Повторно отправить сообщение для подверждения адреса электронной почты"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Кто‐то создал аккаунт с этим именем, но его еще надо активировать."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Если это были вы, и если вы потеряли сообщение для подтверждения аккаунта, то вы можете <a href=\"%(login_url)s\">войти</a> и отправить его повторно."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Здесь вы можете рассказать о себе."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Редактировать профиль"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Этот пользователь не заполнил свой профайл (пока)."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Смотреть коллекции"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Смотреть все файлы %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Ваши файлы появятся здесь, когда вы их добавите."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Пока что тут файлов нет…"
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(исключить)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "В коллекциях:"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Добавить в коллекцию"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "значок ленты"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "лента в формате Atom"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Все права сохранены"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Более новые"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Более старые →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Перейти к странице:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "более новые"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "более старые"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Метки"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Не удалось прочитать файл с изображением."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Ой!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Произошла ошибка"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Операция не позволяется"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "мин"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Комментировать"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Для разметки можете использовать язык <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Я уверен, что хочу удалить это"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Я уверен, что хочу исключить этот файл из коллекции"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Коллекция"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Выберите --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Примечание"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "оставил комментарий к вашему файлу"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Сожалеем: возможность комментирования отключена."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Ой, ваш комментарий был пуст."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Ваш комментарий размещён!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Пожалуйста, проверьте введённое и попробуйте ещё раз."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Необходимо выбрать или добавить коллекцию"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "«%s» — уже в коллекции «%s»"
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "«%s» добавлено в коллекцию «%s»"
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Вы удалили файл."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Файл не удалён, так как вы не подтвердили свою уверенность галочкой."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Вы на пороге удаления файла другого пользователя. Будьте осторожны."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Вы исключили файл из коллекции."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Файл не исключён из коллекции, так как вы не подтвердили своё намерение отметкой."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Вы на пороге исключения файла из коллекции другого пользователя. Будьте осторожны."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Вы удалили коллекцию «%s»"
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Коллекция не удалена, так как вы не подтвердили своё намерение отметкой."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Вы на пороге удаления коллекции другого пользователя. Будьте осторожны."
diff --git a/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..fd48a37f
--- /dev/null
+++ b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..e4d1bacc
--- /dev/null
+++ b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1257 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# martin <zatroch.martin@gmail.com>, 2013
+# martin <zatroch.martin@gmail.com>, 2012-2013
+# Morten Juhl-Johansen Zölde-Fejér <morten@writtenandread.net>, 2012
+# Olle Jonsson <olle.jonsson@gmail.com>, 2012
+# ttrudslev <tanja.trudslev@gmail.com>, 2012
+# martin <zatroch.martin@gmail.com>, 2011-2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-28 07:47+0000\n"
+"Last-Translator: martin <zatroch.martin@gmail.com>\n"
+"Language-Team: Slovak (http://www.transifex.com/projects/p/mediagoblin/language/sk/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sk\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Používateľské meno"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Heslo"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Email adresse"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Použivateľské meno alebo e-mail"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Používateľské meno alebo e-mailová adresa"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Nesprávne používateľské meno alebo e-mailová adresa."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Toto pole neakceptuje e-mailové adresy."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Toto pole vyžaduje e-mailovú adresu."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Prepáč, registrácia na danej inštancii nie je povolená."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Prepáč, rovnaké používateľské meno už existuje."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Prepáč, rovnaká e-mailová adresa už bola použitá na vytvorenie účtu."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Tvoja e-mailová adresa bola overená. Teraz sa môžeš prihlásiť, upravovať profil a vkladať výtvory!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Overovací kľúč, prípadne používateľské meno je nesprávne."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Je potrebné prihlásiť sa, aby sme vedeli kam máme e-mail zaslať!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Už máš overenú e-mailovú adresu!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Opätovne zaslať overovací e-mail."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Pokiaľ daná e-mailová adresa (citlivá na veľkosť písma!) je registrovaná, e-mail z inštrukciami pre zmenu tvojho hesla bol zaslaný."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "Nemožno nájsť nikoho z daným používateľským menom."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "E-mailová správa z inštrukciami na zmenu tvojho hesla bola zaslaná."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Nebolo možné zaslať e-mail na opätovné získanie zabudnutého hesla, nakoľko tvoje používateľské meno je neaktívne, prípadne e-mailová adresa nebola úspešne overená."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Už môžeš použiť nové heslo pri prihlasovaní."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titulok"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Popis výtvoru"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Môžeš využiť\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> pre formátovanie príspevku."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Štítky"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Oddeľ štítky pomocou čiarky."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Unikátna časť adresy"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Unikátna časť adresy nesmie byť prázdna"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Titulná časť adresy daného média. Zmena poľa nepovinná."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Licencia"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Bio"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Webstránka"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Daná adresa obsahuje chybu"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Preferencia licencie"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Nasledovná licencia bude použitá ako východzia pre všetky tvoje výtvory."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Zašli mi e-mail keď ostatní okomentujú môj výtvor"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Titulok nesmie byť prázdny."
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Popis danej kolekcie"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Titulná časť adresy danej kolekcie. Zmena poľa nepovinná."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Staré heslo"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Vlož svoje staré heslo na dôkaz toho, že vlastníš daný účet."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Nové heslo"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Položku s rovnakou unikátnou časťou adresy už niekde máš."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Upravuješ výtvory iného používateľa. Pristupuj zodpovedne. "
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Príloha %s pridaná!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Môžeš upravovať iba svoj vlastný profil."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Upravuješ profil iného používateľa. Pristupuj zodpovedne. "
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Zmeny v profile uložené"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Nastavenia účtu uložené"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Potrebuješ potvrdiť odstránenie svojho účtu."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Už máš kolekciu nazvanú ako \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Kolekcia s týmto štítkom už máš."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Upravuješ kolekciu iného používateľa. Pristupuj zodpovedne. "
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Nesprávne heslo"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Tvoje heslo bolo úspešne zmenené"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Nemožno pripojiť tému... téma nenastavená\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Žiadny priečinok položiek pre túto tému\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Odstránené; hoci bol pôvodný symbolický odkaz adresára nájdený.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "Nemožno odkazovať na \"%s\": %s existuje a nie je symbolickým odkazom\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "Preskakujem \"%s\"; opakovane nastavené.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "Nájdený starý odkaz pre \"%s\"; odstraňujem.\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "CSRF \"cookie\" neprítomný. Toto vidíš najskôr ako výsledok blokovania \"cookie\" súborov a pod.<br/>Uisti sa, že máš povolené ukladanie \"cookies\" pre danú doménu."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Prepáč, nepodporujem tento typ súborov =("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "beh unoconv zlyhal, preskúmajte log záznam"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Konvertovanie videa zlyhalo"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Poloha"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Zobraziť na <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Povoliť"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Zakázať"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Meno"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Meno v rámci OAuth klienta"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Popis"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Toto bude viditeľné pre používateľov,\n ktorí sa môžu identifikovať cez tvoju aplikáciu."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Typ"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Dôverné</strong> - Klient môže\nvytvárať požiadavky na inštanciu GNU MediaGoblin, ktoré nemôžu byť\nzachytené používateľským agentom (napr. klient na strane servera).<br />\n<strong>Verejné</strong> - Klient nemôže vytvárať dôverné\npožiadavky voči GNU MediaGoblin inštancii (napr. JavaScript-ový klient\n na klientskej strane)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "Presmerovacie URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "Presmerovacie URI pre aplikácie, toto pole\nje <strong>požadované</strong> pre verejných klientov."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Dané pole je požadované pre verejných klientov."
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Klient {0} bol registrovaný!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "OAuth klientské spojenia"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Tvoji autorizovaní OAuth klienti"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Pridať"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Nesprávny typ súboru pre dané médium."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Súbor"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Musíš poskytnúť súbor."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Skvelé! Pridané!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "Kolekcia \"%s\" pridaná!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Over si e-mailovú adresu!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "odhlásiť sa"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Prihlásiť sa"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Účet používateľa <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Zmeniť nastavenia účtu"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Sekcia spracovania výtvorov"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Odhlásiť sa"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Pridať výtvor"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Vytvoriť novú kolekciu"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Obrázok hysterického goblina"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Aktuálne výtvory"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Tu môžeš sledovať stav médií spracovávaných na danej inštancii."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Výtvory sa spracúvajú"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Žiadne výtvory v procese spracovania"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Nasledovné nahratia neprešli spracovaním:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Žiadne zlyhané položky!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "Posledných 10 úspešných nahratí"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Zatiaľ žiadne spracované položky!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Nastav svoje nové heslo"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Nastav heslo"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Obnoviť heslo"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Zaslať inštrukcie"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Ahoj %(username)s,\n\npre zmenu svojho hesla k GNU MediaGoblin účtu, otvor nasledujúci odkaz vo svojom prehliadači:\n\n%(verification_url)s\n\nPokiaľ si myslíš, že došlo k omylu, tak jednoducho ignoruj túto správu a buď šťastným goblinom!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Prihlásenie zlyhalo!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Ešte stále nemáš účet?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Vytvor si jeden tu!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Zabudnuté heslo?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Opret en konto!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Vytvoriť"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Ahoj %(username)s,\n\npre aktiváciu tvojho GNU MediaGoblin účtu, otvor nasledujúci odkaz vo\nsvojom prehliadači:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Poháňa nás <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, súčasť projektu <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Uvoľnené pod <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Zdrojový kód</a> plne dostupný."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Preskúmať"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Ahoj, vitaj na tejto MediaGoblin stránke!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Táto stránka používa <a href=\"http://mediagoblin.org\">MediaGoblin</a>, výnimočne skvelý kus softvéru na hostovanie médií."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Pre pridanie vlastných výtvorov, komentárov a viac.. sa prihlás zo svojim MediaGoblin účtom."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Har du ikke en endnu? Det er let!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Vytvoriť účet na tejto stránke</a>\n alebo\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Nastaviť MediaGoblin na vlastnom serveri</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin logo"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Úprava príloh pre %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Prílohy"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Pridať prílohu"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Zrušiť"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Uložiť zmeny"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "Mením heslo používateľa %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Uložiť"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Skutočne odstrániť používateľa '%(user_name)s' a všetky pridružené výtvory/komentáre?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Áno, skutočne odstrániť môj účet"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Odstráňiť permanentne"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Úprava %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Mením nastavenia účtu používateľa %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Zmeniť svoje heslo."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Odstrániť môj účet"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Úprava %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Úprava profilu, ktorý vlastní %(username)s "
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Výtvory označené ako: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Stiahnuť"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Originál"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Prepáč, tento zvukový súbor nepôjde prehrať, \n\tnakoľko tvoj prehliadač nepodporuje HTML5 \n\taudio."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Môžeš získať moderný prehliadač, ktorý\n\ttento zvuk hravo prehrá <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Originálny súbor"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM súbor (Vorbis kodek)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Obrázok pre %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF súbor"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Zapnúť rotáciu"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspektíva"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Čelo"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Vrch"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Strana"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Stiahnuť model"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Súborový formát"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Výška objektu"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Prepáč, tento video súbor nepôjde prehrať, \n\tnakoľko tvoj prehliadač nepodporuje HTML5 \n\tvideo."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Môžeš získať moderný prehliadač, ktorý\n\ttento video súbor hravo prehrá na <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM súbor (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Pridať kolekciu"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Pridaj svoj výtvor"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (kolekcia používateľa %(username)s) "
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s od <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Upraviť"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Odstrániť"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Skutočne odstrániť %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Skutočne odstrániť %(media_title)s z %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Odstrániť"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Kolekcie používateľa %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Kolekcie používateľa <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Ahoj %(username)s,\npoužívateľ %(comment_author)s okmentoval tvoj príspevok (%(comment_url)s) na %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Výtvory, ktoré vlastní %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Výtvory používateľa <a href=\"%(user_url)s\">%(username)s</a> zo štítkom <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Výtvory, ktoré vlastní <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Prehliadanie výtvorov od <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Pridať komentár"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Pridať tento komentár"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "pred %(formatted_time)s "
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Pridané"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Vytvorené"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Pridať “%(media_title)s” do kolekcie"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Pridať novú kolekciu"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Tu môžeš sledovať priebeh spracovania výtvorov pre svoju galériu."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "Tvojich 10 posledných úspešných nahratí"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profil, ktorý vlastní %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Prepáč, daný používateľ nenájdený."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Nutné overenie e-mailovej adresy"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Takmer hotovo! Ešte je potrebné aktivovať tvoj účet."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "E-mail z inštrukciami ako na to by ti mal doraziť každú chvíľu."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "V prípade, že nie:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Opätovne zaslať overovací e-mail."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Účet s týmto používateľským menom je už registrovaný, avšak ešte stále neaktívny."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Pokiaľ si to ty, ale už nemáš uloženú kópiu overovacej správy, tak sa môžeš <a href=\"%(login_url)s\">prihlásiť</a> a preposlať si ju."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Na tomto mieste môžeš povedať o sebe ostatným."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Upraviť profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Dotyčný používateľ (zatiaľ) nevyplnil svoj profil."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Prehliadať kolekcie"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Zobraziť všetky výtvory, ktoré vlastní %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Všetky tvoje výtvory sa objavia práve tu, zatiaľ však nemáš nič pridané."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Pravdepodobne sa tu nenachádzajú žiadne výtvory..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(odstrániť)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "Zahrnuté"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Pridať do kolekcie"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "ikona čítačky"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom čítačka"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Všetky práva vyhradené"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Novšie"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Staršie →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Prejsť na stránku:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "novšie"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "staršie"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Označené ako"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Nemožno prečítať súbor obrázka."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Hopla!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Vyskytla sa chyba"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Nepovolená operácia"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Prepáč Človeče, toto nesmieš!</p><p>Práve si chcel vykonať funkciu, na ktorú nemáš oprávnenie. Opäť si sa pokúšal odstrániť všetky používateľské účty?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Zdá sa, že na tejto adrese sa nič nenachádza. Prepáč!</p><p>Pokiaľ si si istý, že adresa je správna, možno bola hľadaná stránka presunutá, respektíve odstránená."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "rok"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "mesiac"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "týždeň"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "deň"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "hodina"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "minúta"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Komentár"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Môžeš využiť <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> pre formátovanie príspevku."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Jednoznačne to chcem odstrániť"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Skutočne chcem odstrániť danú položku z kolekcie"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Kolekcia"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Vybrať --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Pridať poznámku"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "okmentoval tvoj príspevok"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Prepáč, komentovanie je vypnuté."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Hopla, tvoj komentár bol prázdny."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Tvoj komentár bol pridaný!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Prosím skontroluj svoje položky a skús znova."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Musíš vybrať, prípadne pridať kolekciu"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" sa už nachádza v kolekcii \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s pridané do kolekcie \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Výtvor bol tebou odstránený."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Výtvor nebol odstránený, nakoľko chýbalo tvoje potvrdenie."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Chystáš sa odstrániť výtvory niekoho iného. Pristupuj zodpovedne. "
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "Položka bola z kolekcie odstránená."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Položka nebola odstránená, nakoľko políčko potvrdenia nebolo označné."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Chystáš sa odstrániť položku z kolekcie iného používateľa. Pristupuj zodpovedne. "
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "Kolekcia \"%s\" bola úspešne odstránená."
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Kolekcia nebola odstránená, nakoľko políčko potrvdenia nebolo označené."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Chystáš sa odstrániť kolekciu iného používateľa. Pristupuj zodpovedne. "
diff --git a/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..199e761c
--- /dev/null
+++ b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..35635acf
--- /dev/null
+++ b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1252 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Jure Repinc <jlp@holodeck1.com>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Slovenian (http://www.transifex.com/projects/p/mediagoblin/language/sl/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sl\n"
+"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Uporabniško ime"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Geslo"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "E-poštni naslov"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Oprostite, prijava za ta izvod ni omogočena."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Oprostite, uporabnik s tem imenom že obstaja."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Vaš e-poštni naslov je bil potrjen. Sedaj se lahko prijavite, uredite svoj profil in pošljete slike."
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Potrditveni ključ ali uporabniška identifikacija je napačna"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Ponovno pošiljanje potrditvene e-pošte."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Naslov"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Oznake"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Oznaka"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Oznaka ne sme biti prazna"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Biografija"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Spletna stran"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Vnos s to oznako za tega uporabnika že obstaja."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Urejate vsebino drugega uporabnika. Nadaljujte pazljivo."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Urejate uporabniški profil. Nadaljujte pazljivo."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Za vrsto vsebine je bila podana napačna datoteka."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Datoteka"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Podati morate datoteko."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Juhej! Poslano."
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Prijava"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Podokno obdelovanja vsebine"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Dodaj vsebino"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Vsebina v obdelavi"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "V obdelavi ni nobene vsebine"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Teh vsebin ni bilo moč obdelati:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Prijava ni uspela."
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Še nimate računa?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Ustvarite si ga."
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Ustvarite račun."
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Ustvari"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Pozdravljeni, %(username)s\n\nZa aktivacijo svojega računa GNU MediaGoblin odprite\nnaslednji URL v svojem spletnem brskalniku:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Logotip MediaGoblin"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Prekliči"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Shrani spremembe"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Urejanje %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Urejanje profila – %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Vsebina uporabnika <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Tu lahko spremljate stanje vsebin, ki so v obdelavi za vašo galerijo."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profil – %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Oprostite, tega uporabnika ni bilo moč najti."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Potrebna je potrditev prek e-pošte"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Skoraj ste zaključili. Svoj račun morate le še aktivirati."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "V kratkem bi morali prejeti e-pošto z navodili, kako to storiti."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Če je ne prejmete:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Ponovno pošlji potrditveno e-pošto"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Nekdo je s tem uporabniškim imenom že registriral račun, vendar mora biti še aktiviran."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Če ste ta oseba vi, a ste izgubili potrditveno e-pošto, se lahko <a href=\"%(login_url)s\">prijavite</a> in jo ponovno pošljete."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Na tem mestu lahko drugim poveste nekaj o sebi."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Uredi profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Ta uporabnik še ni izpolnil svojega profila."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Prikaži vso vsebino uporabnika %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Tu bo prikazana vaša vsebina, a trenutno še niste dodali nič."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Videti je, da tu še ni nobene vsebine ..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "Ikona vira"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Ikona Atom"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Opa!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..0f113dcb
--- /dev/null
+++ b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..aabf18db
--- /dev/null
+++ b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Besnik <besnik@programeshqip.org>, 2012-2013
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Albanian (http://www.transifex.com/projects/p/mediagoblin/language/sq/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sq\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Emër përdoruesi"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Fjalëkalim"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Adresë email"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Emër përdoruesi ose email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "Emër përdoruesi ose adresë email e pavlefshme."
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "Kjo fushë nuk është për adresa email."
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "Kjo fushë lyp një adresë email."
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Na njdeni, regjistrimi në këtë instancë të shërbimit është i çaktivizuar."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Na ndjeni, ka tashmë një përdorues me këtë emër."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Na ndjeni, ka tashmë një përdorues me këtë adresë email."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Adresa juaj email u verifikua. Tani mund të bëni hyrjen, të përpunoni profilin tuaj, dhe të parashtroni figura!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Kyçi i verifikimit ose id-ja e përdoruesit është e pasaktë"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Duhet të jeni i futur, që ta dimë kujt t'ia çojmë email-in!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Thuajse e keni verifikuar adresën tuaj email!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Ridërgoni email-in tuaj të verifikimit."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "Nëse ajo adresë email (siç është shkruajtur!) është e regjistruar, është dërguar një email me udhëzime se si të ndryshoni fjalëkalimin tuaj."
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "S'u gjet dot dikush me atë emër përdoruesi."
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Është dërguar një email me udhëzime se si të ndryshoni fjalëkalimin tuaj."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Email-i i ricaktimit të fjalëkalimit nuk u dërgua dot, ngaqë emri juaj i përdoruesit nuk është aktivizuar ose adresa email e llogarisë suaj nuk është verifikuar."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Tani mun të hyni duke përdorur fjalëkalimin tuaj të ri."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titull"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Përshkrim i kësaj pune"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "Mund të përdorni\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> për formatim."
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etiketa"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Ndajini etiketat me presje."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Identifikues"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Identifikuesi s'mund të jetë i zbrazët"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "Titulli i adresës së kësaj medie. Zakonisht nuk keni nevojë ta ndryshoni këtë."
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "Leje"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Jetëshkrim"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Site Web"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "Kjo adresë përmban gabime"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "Parapëlqime licence"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "Kjo do të jetë licenca juaj parazgjedhje për forma ngarkimesh."
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Dërgomë email kur të tjerët komentojnë te media ime"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "Titulli s'mund të jetë i zbrazët"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "Përshkrim i këtij koleksioni"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "Pjesa titull e adresës së këtij koleksioni. Zakonisht nuk keni pse e ndryshoni këtë."
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Fjalëkalimi i vjetër"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "Jepni fjalëkalimin tuaj të vjetër që të provohet se këtë llogari e zotëroni ju."
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Fjalëkalimi i ri"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Ka tashmë një zë me atë identifikues për këtë përdorues."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Po përpunoni media të një tjetër përdoruesi. Hapni sytë."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "Shtuat bashkangjitjen %s!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "Mund të përpunoni vetëm profilin tuaj."
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Po përpunoni profilin e një përdoruesi. Hapni sytë."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Ndryshimet e profilit u ruajtën"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Rregullimet e llogarisë u ruajtën"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "Lypset të ripohoni fshirjen e llogarisë suaj."
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "Keni tashmë një koleksion të quajtur \"%s\"!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "Ka tashmë një koleksion me atë identifikues për këtë përdorues."
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "Po përpunoni koleksionin e një tjetër përdoruesi. Hapni sytë."
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Fjalëkalim i gabuar"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "Nuk krijohet dot lidhje për te tema... nuk ka temë të caktuar\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "Nuk ka drejtori asetesh për këtë temë\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "Sidoqoftë, u gjet simlidhje e vjetër drejtorie lidhjesh; u hoq.\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "Pa cookie CSRF të pranishme. Ka shumë të ngjarë që të jetë punë e një bllokuesi cookie-sh ose të tillë.<br/>Sigurohuni që të lejoni depozitim cookie-sh për këtë përkatësi."
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Na ndjeni, nuk e mbullojmë këtë lloj kartele :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "Ndërkodimi i videos dështoi"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "Vend"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "Shiheni te <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "Lejoje"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "Mohoje"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "Emër"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "Emri i klientit OAuth"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "Përshkrim"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "Kjo do të jetë e dukshme për përdoruesit,\n duke i lejuar kështu zbatimit tuaj\n të kryejë mirëfilltësim si të qe njëri prej tyre."
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Lloj"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>Konfidenciale</strong> - Kklienti mund\n të bëjë kërkesa te instanca GNU MediaGoblin që nuk mund\n të përgjohen nga agjenti i përdoruesit (p.sh. klient te shërbyesi).<br />\n <strong>Publike</strong> - Klienti nuk mund të bëjë kërkesa\n konfidenciale te instanca GNU MediaGoblin (p.sh. klient\n JavaScript i vetë klientit)."
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "URI Ridrejtimi"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "URI ridrejtimi për zbatimin, kjo fushë\n është <strong>e domosdoshme</strong> për klientë publikë."
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "Kjo fushë është e domosdoshme për klientë publikë"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "Klienti {0} u regjistrua!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "Lidhje klienti OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "Klientët tuaj OAuth"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Shtoni"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Kartelë e gabuar e dhënë për llojin e medias."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Kartelë"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Duhet të jepni një kartelë."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Yhaaaaaa! U parashtrua!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "U shtua koleksioni \"%s\"!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifikoni email-in tuaj!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "dilni"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Hyni"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "Llogaria e <a href=\"%(user_url)s\">%(user_name)s</a>"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Ndryshoni rregullime llogarie"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Paneli i përpunimit të medias"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Dilni"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Shtoni media"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "Krijoni koleksion të ri"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "Figurë e gungaçi duke bërë shtriqje"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Mediat më të reja"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "Këtu mund të ndiqni gjendjen e medias që po përpunohet në këtë instancë."
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Media në përpunim"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Pa media në përpunim"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "Nuk arritën të kryheshin këto ngarkime:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "Pa zëra të dështuar!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "10 ngarkimet e fundit të suksesshme"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "Ende pa zëra të përpunuar!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "Caktoni fjalëkalimin tuaj të ri"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "Caktoni fjalëkalim"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Rimerrni fjalëkalimin"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Dërgo udhëzime"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Njatjeta %(username)s,\n\nqë të ndryshoni fjalëkalimin tuaj për GNU MediaGoblin, hapeni URL-në vijuese në \nshfletuesin tuaj web:\n\n%(verification_url)s\n\nNëse mendoni se këtu ka gabim, thjesht shpërfilleni këtë email dhe vazhdoni të jeni\nnjë djallush i lumtur!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Hyrja dështoi!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Nuk keni ende një llogari?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Krijoni një këtu!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Harruat fjalëkalimin tuaj?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Krijoni një llogari!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Krijoje"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Njatjeta %(username)s,\n\nqë të aktivizoni llogarinë tuaj te GNU MediaGoblin hapeni URL-në vijuese te\nshfletuesi juaj web:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Bazuar në <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, një projekt <a href=\"http://gnu.org/\">GNU</a>."
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "Hedhur në qarkullim sipas <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL-së</a>. <a href=\"%(source_link)s\">Kodi burim</a> është i passhëm."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Eksploroni"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Tungjatjeta juaj, mirë se vini te ky site MediaGoblin!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "Ky site përdor <a href=\"http://mediagoblin.org\">MediaGoblin</a>, një program jashtëzakonisht i shkëlqyer për strehim mediash."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "Për të shtuar media tuajën, për të bërë komente, dhe të tjera, mund të hyni përmes llogarisë suaj MediaGoblin."
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Nuk keni ende një të tillë? Është e lehtë!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "Logoja e MediaGoblin-it"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "Po përpunohen bashkangjitjet për %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "Bashkangjitje"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "Shtoni bashkangjitje"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Anuloje"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Ruaji ndryshimet"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Të fshihet vërtet përdoruesi '%(user_name)s' dhe krejt media/komentet përkatëse?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Po, fshijeni vërtet llogarinë time"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "Fshije përgjithmonë"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Po përpunohet %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "Po ndryshohen rregullimet e llogarisë %(username)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Fshije llogarinë time"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "Po përpunohet %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Po përpunohet profili i %(username)s"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Media e etiketuar me:: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "Shkarkojeni"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Origjinal"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "Na ndjeni, zëri s'do të funksionojë, ngaqë \n\tshfletuesi juaj s'mbulon audio HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "Një shfletues web modern që mund të luajë \n\taudion mund ta merrni te <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Kartela origjinale"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "Kartelë WebM (kodek Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "Figurë për %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "Aktivizoni/Çaktivizoni Rrotullimin"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "Perspektivë"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "Ball"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "Krye"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "Anë"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "Shkarkojeni modelin"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Format Kartele"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "Lartësi Objekti"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "Na ndjeni, kjo video nuk do të punojë ngaqë\n shfletuesi juaj web nuk mbulon videot\n HTML5."
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "Mund të merrni një shfletues web modern që \n është në gjendje ta shfaqë këtë video, te <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "Kartelë WebM (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "Shtoni një koleksion"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "Shtoni media tuajën"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (koleksione nga %(username)s)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s nga <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Përpunoni"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Fshije"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Të fshihet vërtet %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Të hiqet vërtet %(media_title)s nga %(collection_title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Hiqe"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "Koleksione të %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "Koleksione të <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "Tungjatjeta %(username)s,\n%(comment_author)s ka komentuar te postimi juaj (%(comment_url)s) në %(instance_name)s\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "Media nga %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "Media të <a href=\"%(user_url)s\">%(username)s</a> me etiketën <a href=\"%(tag_url)s\">%(tag)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "Media nga <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ Po shfletoni media nga <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Shtoni një koment"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Shtoje këtë koment"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "Shtojeni “%(media_title)s” te një koleksion"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "Shtoni një koleksion të ri"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Gjendjen e medias që po përpunohet për galerinë tuaj mund ta ndiqni nga këtu."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "10 ngarkimet tuaja më të suksesshme"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "Profili i %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Na ndjeni, nuk u gjet përdorues i tillë."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "Lypset verifikimi i email-it"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Pothuajse mbaruam! Llogaria juaj ende lyp aktivizimin."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Brenda pak çastesh duhet t'ju mbërrijë një email me udhëzime se si të krhyet kjo."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Në rast se jo:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Ridërgo email-in e verifikimit"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Dikush ka regjistruar një llogari me këtë emër përdoruesi, por ajo duhet aktivizuar."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Nëse jeni ju ai person, por keni humbur email-in tuaj të verifikimit, mund të <a href=\"%(login_url)s\">hyni</a> dhe ta ridërgoni."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Ja një vend t'i tregoni botës mbi veten."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Përpunoni profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Ky përdorues nuk e ka plotësuar (ende) profilin e vet."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "Shfletoni koleksionet"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Shihni krejt mediat nga %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Media juaj do të shfaqet këtu, por nuk duket të keni shtuar gjë ende."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Nuk duket ende të ketë ndonjë media këtu..."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(hiqe)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "Pjesë e koleksionit"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "Shtoje te një koleksion"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "ikonë prurjesh"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Prurje Atom"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Tërë të drejtat të rezervuara"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← Më të reja"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "Më të vjetra →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Shko te faqja:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "më të reja"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "më të vjetra"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "Etiketuar me"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "Nuk lexoi dot kartelën e figurës."
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Oooh!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "Ndodhi një gabim"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "Veprim i palejuar"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Më ndjeni or trim, nuk ju lë dot ta bëni këtë!</p><p>Provuat të kryeni një funksion që nuk lejohet. Keni provuar prapë të fshini krejt llogaritë e përdoruesve?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "Nuk duket se ka ndonjë faqe në këtë adresë. Na ndjeni!</p><p>Nëse jeni i sigurt se kjo adresë është e saktë, ndoshta faqja që po kërkoni është lëvizur ose fshirë."
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "Koment"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "Për formatime mund të përdorni <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>."
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Jam i sigurt që dua të fshihet kjo"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "Jam i sigurt se dua që të hiqet ky objekt prek koleksioni"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "Koleksion"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "-- Përzgjidhni --"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "Përfshini një shënim"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "komentoi te postimi juaj"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Hmmm, komenti juaj qe i zbrazët."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Komenti juaj u postua!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "Ju lutemi, kontrolloni zërat tuaj dhe riprovoni."
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "Duhet të përzgjidhni ose shtoni një koleksion"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "\"%s\" gjendet tashmë te koleksioni \"%s\""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "\"%s\" u shtua te koleksioni \"%s\""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "E fshitë median."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Media nuk u fshi ngaqë nuk i vutë shenjë pohimit se jeni i sigurt."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Ju ndan një hap nga fshirja e medias të një tjetër përdoruesi. Hapni sytë."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "E fshitë objektin prej koleksionit."
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "Objekti nuk u fshi ngaqë, nuk pohuat se jeni të sigurt për këtë."
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "Ju ndan një hap nga fshirja e një objekti prej koleksionit të një përdoruesi tjetër. Hapni sytë."
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "E fshitë koleksionin \"%s\""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "Koleksioni nuk u fshi ngaqë, nuk pohuat se jeni të sigurt për këtë."
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "Ju ndan një hap nga fshirja e koleksionit të një përdoruesi tjetër. Hapni sytë."
diff --git a/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..5564d35d
--- /dev/null
+++ b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..fcf8a666
--- /dev/null
+++ b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1251 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Serbian (http://www.transifex.com/projects/p/mediagoblin/language/sr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sr\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr ""
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr ""
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr ""
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr ""
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr ""
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr ""
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr ""
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr ""
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..3b961e60
--- /dev/null
+++ b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..659de21b
--- /dev/null
+++ b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1253 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# ingenman <simon@ingenmansland.se>, 2011
+# joar <transifex@wandborg.se>, 2011, 2012
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Swedish (http://www.transifex.com/projects/p/mediagoblin/language/sv/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: sv\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Användarnamn"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Lösenord"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "E-postadress"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Vi beklagar, registreringen är avtängd på den här instansen."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "En användare med det användarnamnet finns redan."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Det finns redan en användare med den e-postadressen."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "Din e-postadress är verifierad. Du kan nu logga in, redigera din profil och ladda upp filer!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Verifieringsnyckeln eller användar-IDt är fel."
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "Du måste vara inloggad för att vi ska kunna skicka meddelandet till dig."
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Du har redan verifierat din e-postadress!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Skickade ett nytt verifierings-email."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "Kunde inte skicka e-poståterställning av lösenord eftersom ditt användarnamn är inaktivt eller kontots e-postadress har inte verifierats."
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Titel"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "Beskrivning av verket"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Taggar"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "Sökvägsnamn"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "Sökvägsnamnet kan inte vara tomt"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "Presentation"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Hemsida"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Tidigare lösenord"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "Ett inlägg med det sökvägsnamnet existerar redan."
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Var försiktig, du redigerar någon annans inlägg."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Var försiktig, du redigerar en annan användares profil."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Fel lösenord"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Ogiltig fil för mediatypen."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Fil"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Du måste ange en fil"
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Tjohoo! Upladdat!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "Verifiera din e-postadress"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Logga in"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Mediabehandlingspanel"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Lägg till media"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "Senast medier"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "Media under behandling"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "Ingen media under behandling"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "De här behandlingarna misslyckades:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "Återställ lösenord"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "Skicka instruktioner"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "Hej %(username)s,\n\nför att ändra ditt GNU MediaGoblin-lösenord, öppna följande länk i\ndin webbläsare:\n\n%(verification_url)s\n\nOm du misstänker att du fått detta epostmeddelanade av misstag, ignorera det och fortsätt vara ett glatt troll!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Inloggning misslyckades!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Har du inget konto än?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Skapa ett här!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Glömt ditt lösenord?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Skapa ett konto!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Skapa"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Hej %(username)s,\n\nöppna den följande webbadressen i din webbläsare för att aktivera ditt konto på GNU MediaGoblin:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Utforska"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "Hej, välkommen till den här MediaGoblin-sidan!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "Har du inte ett redan?"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin-logotyp"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Spara ändringar"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "Redigerar %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "Redigerar %(username)ss profil"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "Media taggat med: %(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Original"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Vill du verkligen radera %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "%(username)ss media"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a>s media"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Här kan du se status för mediabehandling av bilder i ditt galleri."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)ss profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Ledsen, hittar ingen sådan användare."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "E-postadressverifiering krävs."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Nästan klar! Ditt konto behöver bara aktiveras."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Ett e-postmeddelande med instruktioner kommer att hamna hos dig inom kort."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "Om det inte skulle göra det:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Skicka ett nytt e-postmeddelande"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "Någon har redan registrerat ett konto med det här användarnamnet men det har inte aktiverats."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Om det är du som är den personen och har förlorat ditt e-postmeddelande med detaljer om hur du verifierar ditt konto så kan du <a href=\"%(login_url)s\">logga in</a> och begära ett nytt."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "Här kan du berätta för andra om dig själv."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Redigera profil"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "Den här användaren har inte fyllt i sin profilsida ännu."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "Se all media från %(username)s"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "Här kommer din media att dyka upp, du verkar inte ha lagt till någonting ännu."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "Det verkar inte finnas någon media här ännu."
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "feed-ikon"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom-feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Ojoj!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Jag är säker på att jag vill radera detta"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Du tänker radera en annan användares media. Var försiktig."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..6e7ebd21
--- /dev/null
+++ b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..b0bf1aa1
--- /dev/null
+++ b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1252 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# వీవెన్ <veeven@gmail.com>, 2011
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Telugu (http://www.transifex.com/projects/p/mediagoblin/language/te/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: te\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "వాడుకరి పేరు"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "సంకేతపదం"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "ఈమెయిలు చిరునామా"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr ""
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr ""
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr ""
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "శీర్షిక"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr ""
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr ""
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr ""
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr ""
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "ప్రవేశం విఫలమయ్యింది!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "మీకు ఇంకా ఖాతా లేదా?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "మీ సంకేతపదాన్ని మర్చిపోయారా?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "రద్దుచేయి"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "మార్పులను భద్రపరచు"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr ""
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..aba1b791
--- /dev/null
+++ b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..c82bd819
--- /dev/null
+++ b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,770 @@
+# Translations template for PROJECT.
+# Copyright (C) 2012 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2012-08-05 10:01-0500\n"
+"PO-Revision-Date: 2011-08-07 03:51+0000\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: tr\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41
+msgid "Username"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+msgid "Password"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:51
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:58
+msgid "Incorrect input"
+msgstr ""
+
+#: mediagoblin/auth/views.py:55
+msgid "Sorry, registration is disabled on this instance."
+msgstr ""
+
+#: mediagoblin/auth/views.py:75
+msgid "Sorry, a user with that name already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:79
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr ""
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr ""
+
+#: mediagoblin/auth/views.py:263
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:273
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:285
+msgid "Couldn't find someone with that username or email."
+msgstr ""
+
+#: mediagoblin/auth/views.py:333
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/submit/forms.py:28
+msgid "Title"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/submit/forms.py:32
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:64
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:67
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:72
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/views.py:64
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:181
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:197
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:226 mediagoblin/edit/views.py:246
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:251
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/gmg_commands/theme.py:58
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/theme.py:71
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/theme.py:74
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:60
+#: mediagoblin/media_types/__init__.py:120
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:35
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:138
+msgid "Invalid file given for media type."
+msgstr ""
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr ""
+
+#: mediagoblin/submit/views.py:56
+msgid "You must provide a file."
+msgstr ""
+
+#: mediagoblin/submit/views.py:163
+msgid "Woohoo! Submitted!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/404.html:22
+msgid "Image of 404 goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/404.html:23
+msgid "Oops!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/404.html:24
+msgid "There doesn't seem to be a page at this address. Sorry!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/404.html:26
+msgid ""
+"If you're sure the address is correct, maybe the page you're looking for has"
+" been moved or deleted."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:50
+msgid "MediaGoblin logo"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:60
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:66
+msgid "+ Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "View your profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:69
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:74
+#: mediagoblin/templates/mediagoblin/auth/login.html:32
+#: mediagoblin/templates/mediagoblin/auth/login.html:50
+msgid "Log in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:88
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a "
+"href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:91
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:24
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:26
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:28
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:29
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:31
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:40
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:22
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:22
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:25
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:28
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:54
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:52
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:55
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:86
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:82
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:88
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:108
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:103
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:32
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:35
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:27
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:30
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:35
+msgid "Logging in failed!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:40
+msgid "Don't have an account yet?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:41
+msgid "Create one here!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:47
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:32
+msgid "Create an account!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:29
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:36
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Cancel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:37
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35
+msgid "Save changes"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:34
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:29
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/image.html:23
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:52
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+#: mediagoblin/templates/mediagoblin/media_displays/image.html:27
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:56
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:40
+msgid ""
+"Sorry, this video will not work because \n"
+"\t your web browser does not support HTML5 \n"
+"\t video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:43
+msgid ""
+"You can get a modern web browser that \n"
+"\t can play this video at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:59
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:26
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:46
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:73
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:87
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:91
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:102
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:109
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:113
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+msgid "at"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#, python-format
+msgid ""
+"<h3>Added on</h3>\n"
+" <p>%(date)s</p>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:167
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:183
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:188
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:25
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:85
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:101
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:118
+msgid "Edit profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:106
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:125
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:138
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:151
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:157
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:163
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:25
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:78
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:30
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:56
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:160
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:166
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:200
+msgid ""
+"Some of the files with this entry seem to be missing. Deleting anyway."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:212
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:220
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..4341870b
--- /dev/null
+++ b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..4155520f
--- /dev/null
+++ b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1252 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# Caner BAŞARAN <basaran.caner@gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-06 15:44+0000\n"
+"Last-Translator: Caner BAŞARAN <basaran.caner@gmail.com>\n"
+"Language-Team: Turkish (Turkey) (http://www.transifex.com/projects/p/mediagoblin/language/tr_TR/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: tr_TR\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "Kullanıcı adı"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "Parola"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "E-posta adresi"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "Kullanıcı adı veya E-posta"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "Kullanıcı adı ya da e-posta"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "Üzgünüz, bu durumda kayıt devre dışıdır."
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "Maalesef, bu isimde bir kullanıcı mevcut."
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "Üzgünüz, bu e-posta adresine sahip bir kullanıcı zaten var."
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "E-posta adresiniz doğrulandı. Şimdi giriş yapabilir, profilinizi düzenleyip ve yeni görüntüleri gönderebilirsiniz!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "Doğrulama anahtarı veya kullanıcı kimliği yanlış"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "Zaten e-posta adresinizi doğruladınız!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "Doğrulama e-postasını tekrar yolla."
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "Parolanızı nasıl değiştireceğinizle ilgili adımları anlatan bir e-posta gönderildi."
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "Şimdi yeni parolanızı giriş için kullanabilirsiniz."
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "Başlık"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "Etiketler"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "Etikerleri virgül ile ayırın."
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "Web sitesi"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "Medyama birisi yorum yazdığında bana e-posta at"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "Eski parola"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "Yeni parola"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "Başka bir kullanıcının medyasını düzenlerken dikkatli davranın."
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "Başka bir kullanıcının profilini düzenlerken dikkatli davranın."
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "Profil değişiklikleri kaydedildi"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "Hesap ayarları kaydedildi"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "Yanlış parola"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "Parolanız başarılı bir şekilde değiştirildi"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "Üzgünüz, bu tip dosyaları desteklemiyoruz :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "Tür"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "Ekle"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "Bu medya türü için geçersiz dosya türü."
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "Dosya"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "Bir dosya sağlamanız gerekir."
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "Hoooop! Gönderildi!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "E-postanızı doğrulayın!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "çıkış"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "Giriş"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "Hesap ayarlarını değiştir"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "Madya işlem paneli"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "Çıkış"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "Medya ekle"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "Giriş başarısız!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "Hala hesabınız yok mu?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "Şimdi oluşturun!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "Parolanı mı unuttun?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "Hesap oluştur!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "Oluştur"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "Merhaba %(username)s,\n\nGNU MediaGoblin hesabınızı etkinleştirmek için, lütfen aşağıdaki\nURL(bağlantı)'yı Web tarayıcınızda açın:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "Keşfet"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin logo"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "İptal"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "Değişiklikleri kaydet"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "Kaydet"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "Gerçekten '%(user_name)s' kullanıcısını ve ilgili tüm medya/yorumları silmek istiyor musun?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "Evet, gerçekten hesabımı silmek istiyorum"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "%(media_title)s düzenleme"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "Parolanızı değiştirin."
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "Hesabımı sil"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "%(username)s profilini düzenleme"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "İndir"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "Özgün"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "Özgün dosya"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF dosya"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "Dosya Biçimi"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "Düzenle"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "Si"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "Gerçekten %(title)s silmek istiyor musun?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "Gerçekten %(collection_title)s %(media_title)s kaldırmak istiyor musun?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "Kaldır"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "%(username)s medyası"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> medyası"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "Bir yorum ekle"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "Bu yorumu ekle"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "%(formatted_time)s önce"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "Eklendi"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "Oluşturuldu"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "Burada galerinizdeki işlenmekte olan medyanın durumunu takip edebilirsiniz."
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)s profili"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "Üzgünüz, böyle bir kullanıcı bulunamadı."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "E-posta doğrulaması gerekli"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "Neredeyse bitti! Hesabınızı etkinleştirmeniz gerekiyor."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "Bunun nasıl yapılacağı ile ilgili talimatlar, birkaç dakika içinde size e-posta ulaşacak."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "Doğrulama e-postası tekrar yolla"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "Doğrulama e-postasını kaybettiyseniz, <a href=\"%(login_url)s\">giriş</a> yapabilir ve yeniden yollayabilirsiniz."
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "Profil düzenle"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "%(username)s tüm medyasını göster"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(kaldır)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "besleme simgesi"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom besleme"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "Tüm hakları saklıdır"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "Sayfaya git:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "Amaninnn boo!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "yıl"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "ay"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "hafta"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "gün"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "saat"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "dakika"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "Bunu silmek için eminim"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "Maalesef, yorum devre dışı."
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "Amaninnn boo, yorumunuz boştu."
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "Yorumunuz gönderildi!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "Medyayı sildiniz."
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "Medya silinmedi çünkü emin olduğunuzu onaylamadınız."
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "Başka bir kullanıcının medyasını silerken dikkatli davranın."
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..1ed5a4f1
--- /dev/null
+++ b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..4bb714fe
--- /dev/null
+++ b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1256 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# <chc@citi.sinica.edu.tw>, 2011
+# cwebber <cwebber@dustycloud.org>, 2013
+# m13253 <m13253@hotmail.com>, 2013
+# medicalwei <medicalwei@gmail.com>, 2012
+# m13253 <m13253@hotmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-16 11:06+0000\n"
+"Last-Translator: m13253 <m13253@hotmail.com>\n"
+"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/mediagoblin/language/zh_CN/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: zh_CN\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "用户名"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "密码"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "电子邮件地址"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "用户名或电子邮件"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "用户名或电子邮件"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "无效用户名或电子邮件地址。"
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "此字段不能填写电子邮件地址。"
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "此字段需填写电子邮件地址。"
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "抱歉,本站已暂停注册。"
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "抱歉,该用户名已存在。"
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "抱歉,已有用户用该电子邮件注册。"
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "您的电子邮件地址已认证。您现在可以登录、修改个人资料并上传图片了!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "验证码错误或用户 ID 错误"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "您必须登录以便让我们知道将电子邮件发给谁"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "您已经认证过电子邮件地址了!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "重发认证邮件。"
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "若该邮件地址(区分大小写)已被注册,则密码修改说明已通过电子邮件送达。"
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "找不到有该用户名的人。"
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "密码修改说明已通过电子邮件送达。"
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "无法发送密码找回邮件,因为您的用户名未激活或者您账户的电子邮件地址未认证。"
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "您现在可以用新的密码来登录了!"
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "标题"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "该作品的描述"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "您可以用 <a href=\"http://wowubuntu.com/markdown/\">Markdown</a> 来排版。"
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "标签"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "用逗号分隔标签。"
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "简称"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "简称不能为空"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "该媒体网址的标题部份。通常不需要修改。"
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "许可证"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "个性签名"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "网站"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "本网址出错了"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "许可证偏好"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "这将是您上传界面的默认许可证。"
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "当有人对我的媒体评论时给我电子邮件"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "标题不能是空的"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "这个合集的描述"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "此合集网址的标题部份,通常不需要修改。"
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "旧的密码"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "输入您的旧密码来证明您拥有这个账户。"
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "新密码"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "这个简称已经被别人用了"
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "您正在修改别人的媒体,请小心操作。"
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "您加上了附件“%s”!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "您只能修改自己的个人资料"
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "您正在修改别人的个人资料,请小心操作。"
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "个人资料已修改"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "账户设置已保存"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "您需要确认删除您的账户。"
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "您已经有一个称做“%s”的合集了!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "该用户已经有使用该简称的合集了。"
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "您正在修改别人的合集,请小心操作。"
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "密码错误"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "您的密码已成功修改"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "无法链接到主题……未设置主题\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "此主题没有素材目录\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "但是旧的目录链接已经找到并移除。\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "无法链接到“%s”:“%s”已存在且不是链接\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "跳过“%s”;已设置过了。\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "“%s”的旧链接已经找到并移除。\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "CSRF cookie 不存在。很可能是由类似 cookie 屏蔽器造成的。<br />请允许本域名的 cookie 设定。"
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "抱歉,我不支持这样的文件格式 :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "无法运行 unoconv,请检查日志"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "视频转码失败"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "位置"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "在 <a href=\"%(osm_url)s\">OpenStreetMap</a> 上观看"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "允许"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "拒绝"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "名称"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "OAuth client 的名称"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "描述"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "本描述将会被进行应用程序认证的用户看到。"
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "类型"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>秘密</strong> — OAuth client 可以对 GNU MediaGoblin 站点发送不被用户代理拦截的请求(例如服务端上的 client)。\n<strong>公开</strong> — OAuth client 无法对 GNU MediaGoblin 站点发送秘密的请求(例如客户端的 JavaScript client)。"
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "重定向 URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "此应用程序的重定向 URI,本字段在公开类型的 OAuth client 为<strong>必填</strong>。"
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "本字段在公开类型的 OAuth client 为必填"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "OAuth client {0} 注册完成!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "OAuth client 连接"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "您的 OAuth client"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "增加"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "提供文件的媒体类型错误。"
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "文件"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "您必须提供一个文件"
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "啊哈!已提交!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "合集“%s”已新增!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "确认您的电子邮件!"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "登出"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "登录"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "<a href=\"%(user_url)s\">%(user_name)s</a> 的账户"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "更改账户设置"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "媒体处理面板"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "登出"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "新增媒体"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "新增合集"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "满脸问号的哥布林"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "最新的媒体"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "此处您可以追踪本站点处理媒体的状态。"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "媒体处理中"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "没有正在处理中的媒体"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "无法处理这些上传内容:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "没有失败的纪录!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "最近 10 次成功上传的纪录"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "现在还没有处理的纪录!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "设置您的新密码"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "设置新密码"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "找回密码"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "发送找回密码说明"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "%(username)s 您好:\n\n要修改 GNU MediaGoblin 的密码,请在您的浏览器中打开下面的网址:\n\n%(verification_url)s\n\n如果您认为这个是个误会,请忽略此封信件,继续当个快乐的哥布林!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "登录失败!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "还没有账户吗?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "在这里建立一个吧!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "忘了密码吗?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "建立一个账户!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "建立"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "%(username)s 您好:\n\n要启动 GNU MediaGoblin 账户,请在您的浏览器中打开下面的网址:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "Powered by <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>,一个 <a href=\"http://gnu.org/\">GNU</a> 项目。"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "以 <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a> 授权发布。备有<a href=\"%(source_link)s\">源代码</a>。"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "探索"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "嘿!欢迎来到 MediaGoblin 站! "
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "本站使用 <a href=\"http://mediagoblin.org\">MediaGoblin</a>——与众不同的媒体分享网站。"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "您可以登录您的 MediaGoblin 账户以上传媒体、张贴评论等等。"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "没有账户吗?开账户很简单!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">在本站创建帐户</a>\n 或者\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">在您自己的服务器上搭建 MediaGoblin</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin 标志"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "编辑 %(media_title)s 的附件"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "附件"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "新增附件"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "取消"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "保存更改"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "修改 %(username)s 的密码"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "保存"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "真的要删除用户 %(user_name)s 及所有相关媒体和评论吗?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "是的,真的删除我的账户"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "永久删除"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "编辑 %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "正在改变 %(username)s 的账户设置"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "修改您的密码。"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "删除我的帐户"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "编辑 %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "编辑 %(username)s 的个人资料"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "此媒体被标记为:%(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "下载"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "源文件"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "抱歉,此声音无法播放,因为您的浏览器不支持 HTML5 音频。"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此声音的浏览器!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "源文件"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM 文件(Vorbis 编码)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr "%(media_title)s 的照片"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF 文件"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "切换旋转"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "透视"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "正面"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "顶面"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "侧面"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "下载模型"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "文件格式"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "对象高度"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "抱歉,此视频无法播放,因为您的浏览器不支持 HTML5 视频。"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此视频的浏览器!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM 文件(640p;VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "新增合集"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "加入您的媒体"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (%(username)s 的合集)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "编辑"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "删除"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "真的要删除 %(title)s 吗?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "确定要从 %(collection_title)s 移除 %(media_title)s 吗?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "移除"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "%(username)s 的合集"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的合集"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "%(username)s 您好:\n%(comment_author)s 在 %(instance_name)s 对您的内容 (%(comment_url)s) 张贴评论\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "%(username)s的媒体"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的有 <a href=\"%(tag_url)s\">%(tag)s</a> 标签的媒体"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的媒体"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ 浏览 <a href=\"%(user_url)s\">%(username)s</a> 的媒体"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "新增评论"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "增加评论"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "%(formatted_time)s前"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "已增加"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "已创建"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "把“%(media_title)s”加入合集"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "新增新的合集"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "您可以在这里追踪您的艺廊中媒体处理的状态。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "您的最近 10 次成功上传的纪录"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)s 的个人资料"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "抱歉,找不到该用户。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "需要认证电子邮件地址"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "快完成了!但您需要激活您的账户。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "账户激活说明将在稍后送达。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "如果仍然无法认证,您可以:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "重发认证邮件"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "有人用注册了该账户,但是该账户需要被启用。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "如果您就是本人但是未收到认证信,您可以<a href=\"%(login_url)s\">登录</a>然后重发一次。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "这个地方能让您向他人介绍自己。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "编辑个人资料"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "这个用户(还)没有填写个人资料。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "浏览合集"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "查看 %(username)s 的全部媒体"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "此处是您的媒体会出现的地方,但是似乎还没有加入任何东西。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "那里好像还没有任何的媒体……"
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr "(移除)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "合集于"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "添加到合集"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "feed 图标"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "版权所有"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← 更新的"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "更旧的 →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "跳到页数:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "更新的"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "更旧的"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "标签"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "无法读取图片文件。"
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "糟糕!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "发生错误"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "操作不允许"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "对不起老兄,我不能让你这样做!</p><p>您正在试着操作不允许您使用的功能。您难道想打算删除所有用户账户吗?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "不好意思,看起来这个网址上没有网页。</p><p>如果您确定这个网址是正确的,您在寻找的页面可能已经移动或是被删除了。"
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "年"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "月"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "周"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "日"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "小时"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "分钟"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "评论"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "您可以用 <a href=\"http://wowubuntu.com/markdown/\">Markdown</a> 来排版。"
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "我确定我要删除这个媒体"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "我确定我要从合集中移除此项目"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "合集"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "— 请选择 —"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "加注"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "在您的内容张贴评论"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "抱歉,不开放评论。"
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "啊,您的评论是空的。"
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "您的评论已经张贴完成!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "请检查项目并重试。"
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "您需要选择或是新增一个合集"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "“%s”已经在“%s”合集"
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "“%s”加入“%s”合集"
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "您已经删除此媒体。"
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "由于您没有勾选确认,该媒体没有被移除。"
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "您正在删除别人的媒体,请小心操作。"
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "您已经从该合集中删除该项目。"
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "由于您没有勾选确认,该项目没有被移除。"
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "您正在从别人的合集中删除项目,请小心操作。"
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "您已经删除“%s”合集。"
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "由于您没有勾选确认,该合集没有被移除。"
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "您正在删除别人的合集,请小心操作。"
diff --git a/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..c234ff00
--- /dev/null
+++ b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..a7ee8db6
--- /dev/null
+++ b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1251 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-05-27 18:54+0000\n"
+"Last-Translator: cwebber <cwebber@dustycloud.org>\n"
+"Language-Team: Chinese (Taiwan) (Big5) (http://www.transifex.com/projects/p/mediagoblin/language/zh_TW.Big5/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: zh_TW.Big5\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr ""
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr ""
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr ""
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr ""
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr ""
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr ""
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr ""
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr ""
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr ""
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr ""
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr ""
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr ""
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr ""
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr ""
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr ""
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr ""
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr ""
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr ""
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr ""
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr ""
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr ""
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr ""
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr ""
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr ""
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr ""
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr ""
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr ""
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr ""
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr ""
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr ""
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr ""
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr ""
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr ""
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr ""
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr ""
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr ""
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr ""
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr ""
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr ""
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr ""
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr ""
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr ""
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr ""
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr ""
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr ""
diff --git a/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mo
new file mode 100644
index 00000000..4b7a2398
--- /dev/null
+++ b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mo
Binary files differ
diff --git a/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po
new file mode 100644
index 00000000..05ecd4b5
--- /dev/null
+++ b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po
@@ -0,0 +1,1256 @@
+# Translations template for PROJECT.
+# Copyright (C) 2013 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+#
+# Translators:
+# <chc@citi.sinica.edu.tw>, 2011
+# Harry Chen <harryhow@gmail.com>, 2011-2012
+# medicalwei <medicalwei@gmail.com>, 2013
+# medicalwei <medicalwei@gmail.com>, 2012
+# m13253 <m13253@hotmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU MediaGoblin\n"
+"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n"
+"POT-Creation-Date: 2013-05-27 13:54-0500\n"
+"PO-Revision-Date: 2013-06-16 01:40+0000\n"
+"Last-Translator: m13253 <m13253@hotmail.com>\n"
+"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/mediagoblin/language/zh_TW/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 0.9.6\n"
+"Language: zh_TW\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: mediagoblin/auth/forms.py:26
+msgid "Username"
+msgstr "使用者名稱"
+
+#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45
+#: mediagoblin/tests/test_util.py:110
+msgid "Password"
+msgstr "密碼"
+
+#: mediagoblin/auth/forms.py:34
+msgid "Email address"
+msgstr "Email 位址"
+
+#: mediagoblin/auth/forms.py:41
+msgid "Username or Email"
+msgstr "使用者名稱或 email"
+
+#: mediagoblin/auth/forms.py:52
+msgid "Username or email"
+msgstr "使用者名稱或 email"
+
+#: mediagoblin/auth/tools.py:31
+msgid "Invalid User name or email address."
+msgstr "無效的使用者名稱或 email 位置。"
+
+#: mediagoblin/auth/tools.py:32
+msgid "This field does not take email addresses."
+msgstr "本欄位不接受 email 位置。"
+
+#: mediagoblin/auth/tools.py:33
+msgid "This field requires an email address."
+msgstr "本欄位需要 email 位置。"
+
+#: mediagoblin/auth/views.py:54
+msgid "Sorry, registration is disabled on this instance."
+msgstr "抱歉,本站已經暫停註冊。"
+
+#: mediagoblin/auth/views.py:68
+msgid "Sorry, a user with that name already exists."
+msgstr "抱歉,這個使用者名稱已經存在。"
+
+#: mediagoblin/auth/views.py:72
+msgid "Sorry, a user with that email address already exists."
+msgstr "抱歉,此 email 位置已經被註冊了。"
+
+#: mediagoblin/auth/views.py:182
+msgid ""
+"Your email address has been verified. You may now login, edit your profile, "
+"and submit images!"
+msgstr "您的 email 位址已被認證。您已經可以登入,編輯您的個人檔案並上傳圖片!"
+
+#: mediagoblin/auth/views.py:188
+msgid "The verification key or user id is incorrect"
+msgstr "認證碼或是使用者 ID 錯誤"
+
+#: mediagoblin/auth/views.py:206
+msgid "You must be logged in so we know who to send the email to!"
+msgstr "您必須登入,我們才知道信要送給誰!"
+
+#: mediagoblin/auth/views.py:214
+msgid "You've already verified your email address!"
+msgstr "您的電子郵件已經確認了!"
+
+#: mediagoblin/auth/views.py:227
+msgid "Resent your verification email."
+msgstr "重送認證信。"
+
+#: mediagoblin/auth/views.py:258
+msgid ""
+"If that email address (case sensitive!) is registered an email has been sent"
+" with instructions on how to change your password."
+msgstr "如果那 email 位置 (請注意大小寫) 已經註冊,寫有修改密碼步驟的 email 已經送出。"
+
+#: mediagoblin/auth/views.py:269
+msgid "Couldn't find someone with that username."
+msgstr "找不到相關的使用者名稱。"
+
+#: mediagoblin/auth/views.py:272
+msgid ""
+"An email has been sent with instructions on how to change your password."
+msgstr "修改密碼的指示已經由電子郵件寄送到您的信箱。"
+
+#: mediagoblin/auth/views.py:279
+msgid ""
+"Could not send password recovery email as your username is inactive or your "
+"account's email address has not been verified."
+msgstr "無法傳送密碼回復信件,因為您的使用者名稱已失效或是帳號尚未認證。"
+
+#: mediagoblin/auth/views.py:336
+msgid "You can now log in using your new password."
+msgstr "您現在可以用新的密碼登入了!"
+
+#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82
+#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47
+#: mediagoblin/user_pages/forms.py:45
+msgid "Title"
+msgstr "標題"
+
+#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31
+msgid "Description of this work"
+msgstr "這個作品的描述"
+
+#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52
+#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32
+#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49
+msgid ""
+"You can use\n"
+" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n"
+" Markdown</a> for formatting."
+msgstr "您可以用 <a href=\"http://markdown.tw\">Markdown</a> 來排版。"
+
+#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36
+msgid "Tags"
+msgstr "標籤"
+
+#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38
+msgid "Separate tags by commas."
+msgstr "用逗號分隔標籤。"
+
+#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90
+msgid "Slug"
+msgstr "簡稱"
+
+#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91
+msgid "The slug can't be empty"
+msgstr "簡稱不能為空白"
+
+#: mediagoblin/edit/forms.py:40
+msgid ""
+"The title part of this media's address. You usually don't need to change "
+"this."
+msgstr "此媒體網址的標題部份。通常不需要修改。"
+
+#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41
+#: mediagoblin/templates/mediagoblin/utils/license.html:20
+msgid "License"
+msgstr "授權"
+
+#: mediagoblin/edit/forms.py:50
+msgid "Bio"
+msgstr "自我介紹"
+
+#: mediagoblin/edit/forms.py:56
+msgid "Website"
+msgstr "網站"
+
+#: mediagoblin/edit/forms.py:58
+msgid "This address contains errors"
+msgstr "本網址出錯了"
+
+#: mediagoblin/edit/forms.py:63
+msgid "License preference"
+msgstr "授權偏好"
+
+#: mediagoblin/edit/forms.py:69
+msgid "This will be your default license on upload forms."
+msgstr "在上傳頁面,這將會是您預設的授權模式。"
+
+#: mediagoblin/edit/forms.py:71
+msgid "Email me when others comment on my media"
+msgstr "當有人對我的媒體留言時寄信給我"
+
+#: mediagoblin/edit/forms.py:83
+msgid "The title can't be empty"
+msgstr "標題不能是空的"
+
+#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50
+#: mediagoblin/user_pages/forms.py:48
+msgid "Description of this collection"
+msgstr "這個蒐藏的描述"
+
+#: mediagoblin/edit/forms.py:92
+msgid ""
+"The title part of this collection's address. You usually don't need to "
+"change this."
+msgstr "此蒐藏網址的標題部份,通常不需要修改。"
+
+#: mediagoblin/edit/forms.py:99
+msgid "Old password"
+msgstr "舊的密碼"
+
+#: mediagoblin/edit/forms.py:101
+msgid "Enter your old password to prove you own this account."
+msgstr "輸入您的舊密碼來證明您擁有這個帳號。"
+
+#: mediagoblin/edit/forms.py:104
+msgid "New password"
+msgstr "新密碼"
+
+#: mediagoblin/edit/views.py:67
+msgid "An entry with that slug already exists for this user."
+msgstr "這個簡稱已經被其他人用了"
+
+#: mediagoblin/edit/views.py:85
+msgid "You are editing another user's media. Proceed with caution."
+msgstr "您正在修改別人的媒體,請小心操作。"
+
+#: mediagoblin/edit/views.py:155
+#, python-format
+msgid "You added the attachment %s!"
+msgstr "您加上了附件「%s」!"
+
+#: mediagoblin/edit/views.py:182
+msgid "You can only edit your own profile."
+msgstr "您只能修改您自己的個人檔案。"
+
+#: mediagoblin/edit/views.py:188
+msgid "You are editing a user's profile. Proceed with caution."
+msgstr "您正在修改別人的個人檔案,請小心操作。"
+
+#: mediagoblin/edit/views.py:204
+msgid "Profile changes saved"
+msgstr "個人檔案修改已儲存"
+
+#: mediagoblin/edit/views.py:240
+msgid "Account settings saved"
+msgstr "帳號設定已儲存"
+
+#: mediagoblin/edit/views.py:274
+msgid "You need to confirm the deletion of your account."
+msgstr "您必須要確認是否刪除您的帳號。"
+
+#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138
+#: mediagoblin/user_pages/views.py:222
+#, python-format
+msgid "You already have a collection called \"%s\"!"
+msgstr "您已經有一個稱做「%s」的蒐藏了!"
+
+#: mediagoblin/edit/views.py:314
+msgid "A collection with that slug already exists for this user."
+msgstr "這個使用者已經有使用該簡稱的蒐藏了。"
+
+#: mediagoblin/edit/views.py:329
+msgid "You are editing another user's collection. Proceed with caution."
+msgstr "您正在修改別人的蒐藏,請小心操作。"
+
+#: mediagoblin/edit/views.py:348
+msgid "Wrong password"
+msgstr "密碼錯誤"
+
+#: mediagoblin/edit/views.py:363
+msgid "Your password was changed successfully"
+msgstr "您的密碼已經成功修改"
+
+#: mediagoblin/gmg_commands/assetlink.py:60
+msgid "Cannot link theme... no theme set\n"
+msgstr "無法連結佈景…沒有此佈景\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:73
+msgid "No asset directory for this theme\n"
+msgstr "此佈景沒有素材目錄\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:76
+msgid "However, old link directory symlink found; removed.\n"
+msgstr "但是舊的目錄連結已經找到並移除。\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:112
+#, python-format
+msgid "Could not link \"%s\": %s exists and is not a symlink\n"
+msgstr "無法連結「%s」:%s 存在,且不是符號連結\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:119
+#, python-format
+msgid "Skipping \"%s\"; already set up.\n"
+msgstr "跳過「%s」,已經建置完成。\n"
+
+#: mediagoblin/gmg_commands/assetlink.py:124
+#, python-format
+msgid "Old link found for \"%s\"; removing.\n"
+msgstr "找到「%s」舊的連結,刪除中。\n"
+
+#: mediagoblin/meddleware/csrf.py:134
+msgid ""
+"CSRF cookie not present. This is most likely the result of a cookie blocker "
+"or somesuch.<br/>Make sure to permit the settings of cookies for this "
+"domain."
+msgstr "跨網站存取 (CSRF) 的 cookie 不存在,有可能是 cookie 阻擋程式之類的程式導致的。<br/>請允許此網域的 cookie 設定。"
+
+#: mediagoblin/media_types/__init__.py:111
+#: mediagoblin/media_types/__init__.py:155
+msgid "Sorry, I don't support that file type :("
+msgstr "抱歉,我不支援這樣的檔案格式 :("
+
+#: mediagoblin/media_types/pdf/processing.py:136
+msgid "unoconv failing to run, check log file"
+msgstr "unoconv 無法執行,請檢查紀錄檔"
+
+#: mediagoblin/media_types/video/processing.py:37
+msgid "Video transcoding failed"
+msgstr "影像轉碼失敗"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24
+msgid "Location"
+msgstr "位置"
+
+#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52
+#, python-format
+msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>"
+msgstr "在 <a href=\"%(osm_url)s\">OpenStreetMap</a> 上觀看"
+
+#: mediagoblin/plugins/oauth/forms.py:29
+msgid "Allow"
+msgstr "允許"
+
+#: mediagoblin/plugins/oauth/forms.py:30
+msgid "Deny"
+msgstr "拒絕"
+
+#: mediagoblin/plugins/oauth/forms.py:34
+msgid "Name"
+msgstr "名稱"
+
+#: mediagoblin/plugins/oauth/forms.py:35
+msgid "The name of the OAuth client"
+msgstr "OAuth 用戶程式的名稱"
+
+#: mediagoblin/plugins/oauth/forms.py:36
+msgid "Description"
+msgstr "描述"
+
+#: mediagoblin/plugins/oauth/forms.py:38
+msgid ""
+"This will be visible to users allowing your\n"
+" application to authenticate as them."
+msgstr "本描述將會被進行應用程式認証的使用者看到。"
+
+#: mediagoblin/plugins/oauth/forms.py:40
+msgid "Type"
+msgstr "類型"
+
+#: mediagoblin/plugins/oauth/forms.py:45
+msgid ""
+"<strong>Confidential</strong> - The client can\n"
+" make requests to the GNU MediaGoblin instance that can not be\n"
+" intercepted by the user agent (e.g. server-side client).<br />\n"
+" <strong>Public</strong> - The client can't make confidential\n"
+" requests to the GNU MediaGoblin instance (e.g. client-side\n"
+" JavaScript client)."
+msgstr "<strong>秘密</strong> — OAuth 用戶程式可以對 GNU MediaGoblin 站台發送不被使用者代理攔截的請求 (例如伺服端的用戶程式)。\n<strong>公開</strong> — OAuth 用戶程式無法對 GNU MediaGoblin 站台發送秘密的請求 (例如客戶端的 JavaScript 用戶程式)。"
+
+#: mediagoblin/plugins/oauth/forms.py:52
+msgid "Redirect URI"
+msgstr "重定向 URI"
+
+#: mediagoblin/plugins/oauth/forms.py:54
+msgid ""
+"The redirect URI for the applications, this field\n"
+" is <strong>required</strong> for public clients."
+msgstr "此應用程式的重定向 URI,本欄位在公開類型的 OAuth 用戶程式為必填。"
+
+#: mediagoblin/plugins/oauth/forms.py:66
+msgid "This field is required for public clients"
+msgstr "本欄位在公開類型的用戶程式為必填"
+
+#: mediagoblin/plugins/oauth/views.py:56
+msgid "The client {0} has been registered!"
+msgstr "OAuth 用戶程式 {0} 註冊完成!"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22
+msgid "OAuth client connections"
+msgstr "OAuth 用戶程式連線"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22
+msgid "Your OAuth clients"
+msgstr "您的 OAuth 用戶程式"
+
+#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29
+#: mediagoblin/templates/mediagoblin/submit/collection.html:30
+#: mediagoblin/templates/mediagoblin/submit/start.html:34
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68
+msgid "Add"
+msgstr "增加"
+
+#: mediagoblin/processing/__init__.py:193
+msgid "Invalid file given for media type."
+msgstr "指定錯誤的媒體類別!"
+
+#: mediagoblin/submit/forms.py:26
+msgid "File"
+msgstr "檔案"
+
+#: mediagoblin/submit/views.py:49
+msgid "You must provide a file."
+msgstr "您必須提供一個檔案"
+
+#: mediagoblin/submit/views.py:93
+msgid "Woohoo! Submitted!"
+msgstr "啊哈!PO 上去啦!"
+
+#: mediagoblin/submit/views.py:144
+#, python-format
+msgid "Collection \"%s\" added!"
+msgstr "蒐藏「%s」新增完成!"
+
+#: mediagoblin/templates/mediagoblin/base.html:67
+msgid "Verify your email!"
+msgstr "確認您的電子郵件"
+
+#: mediagoblin/templates/mediagoblin/base.html:68
+msgid "log out"
+msgstr "登出"
+
+#: mediagoblin/templates/mediagoblin/base.html:73
+#: mediagoblin/templates/mediagoblin/auth/login.html:28
+#: mediagoblin/templates/mediagoblin/auth/login.html:36
+#: mediagoblin/templates/mediagoblin/auth/login.html:54
+msgid "Log in"
+msgstr "登入"
+
+#: mediagoblin/templates/mediagoblin/base.html:82
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account"
+msgstr "<a href=\"%(user_url)s\">%(user_name)s</a> 的帳號"
+
+#: mediagoblin/templates/mediagoblin/base.html:89
+msgid "Change account settings"
+msgstr "更改帳號設定"
+
+#: mediagoblin/templates/mediagoblin/base.html:93
+#: mediagoblin/templates/mediagoblin/base.html:108
+#: mediagoblin/templates/mediagoblin/admin/panel.html:21
+#: mediagoblin/templates/mediagoblin/admin/panel.html:26
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26
+msgid "Media processing panel"
+msgstr "媒體處理面板"
+
+#: mediagoblin/templates/mediagoblin/base.html:96
+msgid "Log out"
+msgstr "登出"
+
+#: mediagoblin/templates/mediagoblin/base.html:99
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:156
+msgid "Add media"
+msgstr "新增媒體"
+
+#: mediagoblin/templates/mediagoblin/base.html:102
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41
+msgid "Create new collection"
+msgstr "新增新的蒐藏"
+
+#: mediagoblin/templates/mediagoblin/error.html:24
+msgid "Image of goblin stressing out"
+msgstr "滿臉問號的哥布林"
+
+#: mediagoblin/templates/mediagoblin/root.html:32
+msgid "Most recent media"
+msgstr "最新的媒體"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:29
+msgid ""
+"Here you can track the state of media being processed on this instance."
+msgstr "此處您可以追蹤本站台處理媒體的狀態。"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32
+msgid "Media in-processing"
+msgstr "媒體處理中"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:58
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56
+msgid "No media in-processing"
+msgstr "沒有正在處理中的媒體"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:61
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59
+msgid "These uploads failed to process:"
+msgstr "無法處理這些上傳內容:"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:90
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86
+msgid "No failed entries!"
+msgstr "沒有失敗的紀錄!"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:92
+msgid "Last 10 successful uploads"
+msgstr "最近 10 次成功上傳的紀錄"
+
+#: mediagoblin/templates/mediagoblin/admin/panel.html:112
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107
+msgid "No processed entries, yet!"
+msgstr "現在還沒有處理的紀錄!"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36
+msgid "Set your new password"
+msgstr "設定您的新密碼"
+
+#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39
+msgid "Set password"
+msgstr "設定新密碼"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31
+msgid "Recover password"
+msgstr "找回密碼"
+
+#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34
+msgid "Send instructions"
+msgstr "送出指示"
+
+#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to change your GNU MediaGoblin password, open the following URL in \n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s\n"
+"\n"
+"If you think this is an error, just ignore this email and continue being\n"
+"a happy goblin!"
+msgstr "%(username)s 您好:\n\n要修改 GNU MediaGoblin 的密碼,請在您的瀏覽器中打開下面的網址:\n\n%(verification_url)s\n\n如果您認為這個是個誤會,請忽略此封信件,繼續當個快樂的哥布林!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:39
+msgid "Logging in failed!"
+msgstr "登入失敗!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:44
+msgid "Don't have an account yet?"
+msgstr "還沒有帳號嗎?"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:45
+msgid "Create one here!"
+msgstr "在這裡建立一個吧!"
+
+#: mediagoblin/templates/mediagoblin/auth/login.html:51
+msgid "Forgot your password?"
+msgstr "忘了密碼嗎?"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:28
+#: mediagoblin/templates/mediagoblin/auth/register.html:36
+msgid "Create an account!"
+msgstr "建立一個帳號!"
+
+#: mediagoblin/templates/mediagoblin/auth/register.html:40
+msgid "Create"
+msgstr "建立"
+
+#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"\n"
+"to activate your GNU MediaGoblin account, open the following URL in\n"
+"your web browser:\n"
+"\n"
+"%(verification_url)s"
+msgstr "%(username)s 您好:\n\n要啟動 GNU MediaGoblin 帳號,請在您的瀏覽器中打開下面的網址:\n\n%(verification_url)s"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21
+#, python-format
+msgid ""
+"Powered by <a href=\"http://mediagoblin.org/\" title='Version "
+"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project."
+msgstr "本站使用 <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>,這是一個 <a href=\"http://gnu.org/\">GNU</a> 專案。"
+
+#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24
+#, python-format
+msgid ""
+"Released under the <a "
+"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a "
+"href=\"%(source_link)s\">Source code</a> available."
+msgstr "以 <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a> 授權釋出。備有<a href=\"%(source_link)s\">原始碼</a>。"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20
+msgid "Explore"
+msgstr "探索"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22
+msgid "Hi there, welcome to this MediaGoblin site!"
+msgstr "嘿!歡迎來到 MediaGoblin 站台! "
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24
+msgid ""
+"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an "
+"extraordinarily great piece of media hosting software."
+msgstr "本站使用 <a href=\"http://mediagoblin.org\">MediaGoblin</a> — 與眾不同的媒體分享網站。"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25
+msgid ""
+"To add your own media, place comments, and more, you can log in with your "
+"MediaGoblin account."
+msgstr "您可以登入您的 MediaGoblin 帳號以進行上傳媒體、張貼評論等等。"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27
+msgid "Don't have one yet? It's easy!"
+msgstr "沒有帳號嗎?開帳號很簡單!"
+
+#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28
+#, python-format
+msgid ""
+"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n"
+" or\n"
+" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>"
+msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">在本站建立您的帳號</a>\n 或是\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">在您自己的伺服器上安裝 MediaGoblin</a>"
+
+#: mediagoblin/templates/mediagoblin/bits/logo.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23
+msgid "MediaGoblin logo"
+msgstr "MediaGoblin 標誌"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:23
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:35
+#, python-format
+msgid "Editing attachments for %(media_title)s"
+msgstr "編輯 %(media_title)s 的附件"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:182
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:198
+msgid "Attachments"
+msgstr "附件"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:57
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:204
+msgid "Add attachment"
+msgstr "新增附件"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:61
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit.html:41
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48
+msgid "Cancel"
+msgstr "取消"
+
+#: mediagoblin/templates/mediagoblin/edit/attachments.html:63
+#: mediagoblin/templates/mediagoblin/edit/edit.html:42
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40
+msgid "Save changes"
+msgstr "儲存變更"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38
+#, python-format
+msgid "Changing %(username)s's password"
+msgstr "更改 %(username)s 的密碼"
+
+#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45
+msgid "Save"
+msgstr "儲存"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28
+#, python-format
+msgid "Really delete user '%(user_name)s' and all related media/comments?"
+msgstr "真的要刪除使用者「%(user_name)s」以及相關的媒體與留言?"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35
+msgid "Yes, really delete my account"
+msgstr "是的,我真的要把我的帳號刪除"
+
+#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49
+msgid "Delete permanently"
+msgstr "永久刪除"
+
+#: mediagoblin/templates/mediagoblin/edit/edit.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit.html:35
+#, python-format
+msgid "Editing %(media_title)s"
+msgstr "編輯 %(media_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40
+#, python-format
+msgid "Changing %(username)s's account settings"
+msgstr "正在改變 %(username)s 的帳號設定"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46
+msgid "Change your password."
+msgstr "更改您的密碼。"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62
+msgid "Delete my account"
+msgstr "刪除我的帳號"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29
+#, python-format
+msgid "Editing %(collection_title)s"
+msgstr "編輯 %(collection_title)s"
+
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23
+#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34
+#, python-format
+msgid "Editing %(username)s's profile"
+msgstr "編輯 %(username)s 的個人檔案"
+
+#: mediagoblin/templates/mediagoblin/listings/collection.html:30
+#: mediagoblin/templates/mediagoblin/listings/collection.html:35
+#: mediagoblin/templates/mediagoblin/listings/tag.html:30
+#: mediagoblin/templates/mediagoblin/listings/tag.html:35
+#, python-format
+msgid "Media tagged with: %(tag_name)s"
+msgstr "這個媒體具有以下標籤:%(tag_name)s"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:55
+msgid "Download"
+msgstr "下載"
+
+#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38
+msgid "Original"
+msgstr "原始檔"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44
+msgid ""
+"Sorry, this audio will not work because \n"
+"\tyour web browser does not support HTML5 \n"
+"\taudio."
+msgstr "抱歉,此聲音無法播放,因為您的瀏覽器不支援 HTML5 音訊。"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47
+msgid ""
+"You can get a modern web browser that \n"
+"\tcan play the audio at <a href=\"http://getfirefox.com\">\n"
+"\t http://getfirefox.com</a>!"
+msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此聲音的瀏覽器!"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:61
+msgid "Original file"
+msgstr "原始檔案"
+
+#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63
+msgid "WebM file (Vorbis codec)"
+msgstr "WebM 檔案 (Vorbis 編碼)"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:59
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:65
+#, python-format
+msgid "Image for %(media_title)s"
+msgstr " %(media_title)s 的照片"
+
+#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79
+msgid "PDF file"
+msgstr "PDF 檔"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112
+msgid "Toggle Rotate"
+msgstr "切換旋轉"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113
+msgid "Perspective"
+msgstr "透視"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117
+msgid "Front"
+msgstr "正面"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121
+msgid "Top"
+msgstr "頂面"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125
+msgid "Side"
+msgstr "側面"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131
+msgid "WebGL"
+msgstr "WebGL"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138
+msgid "Download model"
+msgstr "下載模型"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146
+msgid "File Format"
+msgstr "檔案格式"
+
+#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148
+msgid "Object Height"
+msgstr "物件高度"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:44
+msgid ""
+"Sorry, this video will not work because\n"
+" your web browser does not support HTML5 \n"
+" video."
+msgstr "抱歉,由於您的瀏覽器不支援 HTML5 影片,本影片無法播放"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:47
+msgid ""
+"You can get a modern web browser that \n"
+" can play this video at <a href=\"http://getfirefox.com\">\n"
+" http://getfirefox.com</a>!"
+msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此影片的先進瀏覽器。"
+
+#: mediagoblin/templates/mediagoblin/media_displays/video.html:69
+msgid "WebM file (640p; VP8/Vorbis)"
+msgstr "WebM 檔案 (640p; VP8/Vorbis)"
+
+#: mediagoblin/templates/mediagoblin/submit/collection.html:26
+msgid "Add a collection"
+msgstr "新增蒐藏"
+
+#: mediagoblin/templates/mediagoblin/submit/start.html:23
+#: mediagoblin/templates/mediagoblin/submit/start.html:30
+msgid "Add your media"
+msgstr "加入您的媒體"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30
+#, python-format
+msgid "%(collection_title)s (%(username)s's collection)"
+msgstr "%(collection_title)s (%(username)s 的蒐藏)"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39
+#, python-format
+msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:79
+msgid "Edit"
+msgstr "編輯"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:83
+msgid "Delete"
+msgstr "刪除"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30
+#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30
+#, python-format
+msgid "Really delete %(title)s?"
+msgstr "真的要刪除 %(title)s?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31
+#, python-format
+msgid "Really remove %(media_title)s from %(collection_title)s?"
+msgstr "確定要從 %(collection_title)s 移除 %(media_title)s 嗎?"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54
+msgid "Remove"
+msgstr "移除"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21
+#, python-format
+msgid "%(username)s's collections"
+msgstr "%(username)s 的蒐藏"
+
+#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的蒐藏"
+
+#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19
+#, python-format
+msgid ""
+"Hi %(username)s,\n"
+"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n"
+msgstr "%(username)s 您好:\n%(comment_author)s 在 %(instance_name)s 對您的內容 (%(comment_url)s) 張貼留言\n"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30
+#, python-format
+msgid "%(username)s's media"
+msgstr "%(username)s的媒體"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38
+#, python-format
+msgid ""
+"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a "
+"href=\"%(tag_url)s\">%(tag)s</a>"
+msgstr "標籤為 <a href=\"%(tag_url)s\">%(tag)s</a> 的 <a href=\"%(user_url)s\">%(username)s</a> 的媒體"
+
+#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48
+#, python-format
+msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media"
+msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的媒體"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:38
+#, python-format
+msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>"
+msgstr "❖ 瀏覽 <a href=\"%(user_url)s\">%(username)s</a> 的媒體"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:95
+msgid "Add a comment"
+msgstr "新增留言"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:104
+msgid "Add this comment"
+msgstr "增加留言"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:132
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:152
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:164
+#, python-format
+msgid "%(formatted_time)s ago"
+msgstr "%(formatted_time)s 前"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:150
+msgid "Added"
+msgstr "新增於"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media.html:161
+msgid "Created"
+msgstr "建立於"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40
+#, python-format
+msgid "Add “%(media_title)s” to a collection"
+msgstr "加入 “%(media_title)s” 至蒐藏"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54
+msgid "+"
+msgstr "+"
+
+#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58
+msgid "Add a new collection"
+msgstr "新增新的蒐藏"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29
+msgid ""
+"You can track the state of media being processed for your gallery here."
+msgstr "您可以在這裡追蹤您的藝廊中媒體處理的狀態。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89
+msgid "Your last 10 successful uploads"
+msgstr "您的最近 10 次成功上傳的紀錄"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:31
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:89
+#, python-format
+msgid "%(username)s's profile"
+msgstr "%(username)s 的個人檔案"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:43
+msgid "Sorry, no such user found."
+msgstr "抱歉,找不到這個使用者。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:50
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:70
+msgid "Email verification needed"
+msgstr "需要認證電子郵件"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:53
+msgid "Almost done! Your account still needs to be activated."
+msgstr "快完成了!但您需要啟用您的帳號。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:58
+msgid ""
+"An email should arrive in a few moments with instructions on how to do so."
+msgstr "啟用步驟的 email 將會寄到您的信箱。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:62
+msgid "In case it doesn't:"
+msgstr "如果仍然無法認證,您可以:"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:65
+msgid "Resend verification email"
+msgstr "重送認證信"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:73
+msgid ""
+"Someone has registered an account with this username, but it still has to be"
+" activated."
+msgstr "有人用了這個帳號登錄了,但是這個帳號需要被啟用。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:79
+#, python-format
+msgid ""
+"If you are that person but you've lost your verification email, you can <a "
+"href=\"%(login_url)s\">log in</a> and resend it."
+msgstr "如果您就是本人但是掉了認證信,您可以 <a href=\"%(login_url)s\">登入</a> 然後重送一次。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:96
+msgid "Here's a spot to tell others about yourself."
+msgstr "這個地方能讓您向他人介紹自己。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:100
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:117
+msgid "Edit profile"
+msgstr "編輯個人檔案"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:105
+msgid "This user hasn't filled in their profile (yet)."
+msgstr "這個使用者(還)沒有填寫個人檔案。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:124
+msgid "Browse collections"
+msgstr "瀏覽蒐藏"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:137
+#, python-format
+msgid "View all of %(username)s's media"
+msgstr "查看 %(username)s 的全部媒體"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:150
+msgid ""
+"This is where your media will appear, but you don't seem to have added "
+"anything yet."
+msgstr "此處是您的媒體會出現的地方,但是似乎還沒有加入任何東西。"
+
+#: mediagoblin/templates/mediagoblin/user_pages/user.html:162
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84
+#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70
+msgid "There doesn't seem to be any media here yet..."
+msgstr "那裡好像還沒有任何的媒體…"
+
+#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49
+msgid "(remove)"
+msgstr " (移除)"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:21
+msgid "Collected in"
+msgstr "蒐集了"
+
+#: mediagoblin/templates/mediagoblin/utils/collections.html:40
+msgid "Add to a collection"
+msgstr "加入至蒐藏"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21
+msgid "feed icon"
+msgstr "feed 圖示"
+
+#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23
+#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23
+msgid "Atom feed"
+msgstr "Atom feed"
+
+#: mediagoblin/templates/mediagoblin/utils/license.html:25
+msgid "All rights reserved"
+msgstr "版權所有"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:39
+msgid "← Newer"
+msgstr "← 更新的"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:45
+msgid "Older →"
+msgstr "更舊的 →"
+
+#: mediagoblin/templates/mediagoblin/utils/pagination.html:48
+msgid "Go to page:"
+msgstr "跳到頁數:"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33
+msgid "newer"
+msgstr "更新的"
+
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39
+#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44
+msgid "older"
+msgstr "更舊的"
+
+#: mediagoblin/templates/mediagoblin/utils/tags.html:20
+msgid "Tagged with"
+msgstr "標籤"
+
+#: mediagoblin/tools/exif.py:83
+msgid "Could not read the image file."
+msgstr "無法讀取圖片檔案。"
+
+#: mediagoblin/tools/response.py:35
+msgid "Oops!"
+msgstr "糟糕!"
+
+#: mediagoblin/tools/response.py:36
+msgid "An error occured"
+msgstr "發生錯誤"
+
+#: mediagoblin/tools/response.py:51
+msgid "Operation not allowed"
+msgstr "操作不允許"
+
+#: mediagoblin/tools/response.py:52
+msgid ""
+"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a "
+"function that you are not allowed to. Have you been trying to delete all "
+"user accounts again?"
+msgstr "Dave 對不起,我不能讓你這樣做!</p><p>您正在試著操作不允許您使用的功能。您打算刪除所有使用者的帳號嗎?"
+
+#: mediagoblin/tools/response.py:60
+msgid ""
+"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure"
+" the address is correct, maybe the page you're looking for has been moved or"
+" deleted."
+msgstr "不好意思,看起來這個網址上沒有網頁。</p><p>如果您確定這個網址是正確的,您在尋找的頁面可能已經移動或是被刪除了。"
+
+#: mediagoblin/tools/timesince.py:62
+msgid "year"
+msgstr "年"
+
+#: mediagoblin/tools/timesince.py:63
+msgid "month"
+msgstr "月"
+
+#: mediagoblin/tools/timesince.py:64
+msgid "week"
+msgstr "週"
+
+#: mediagoblin/tools/timesince.py:65
+msgid "day"
+msgstr "日"
+
+#: mediagoblin/tools/timesince.py:66
+msgid "hour"
+msgstr "小時"
+
+#: mediagoblin/tools/timesince.py:67
+msgid "minute"
+msgstr "分"
+
+#: mediagoblin/user_pages/forms.py:23
+msgid "Comment"
+msgstr "留言"
+
+#: mediagoblin/user_pages/forms.py:25
+msgid ""
+"You can use <a "
+"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for"
+" formatting."
+msgstr "您可以用 <a href=\"http://markdown.tw\">Markdown</a> 來排版。"
+
+#: mediagoblin/user_pages/forms.py:31
+msgid "I am sure I want to delete this"
+msgstr "我確定我要刪除這個媒體"
+
+#: mediagoblin/user_pages/forms.py:35
+msgid "I am sure I want to remove this item from the collection"
+msgstr "我確定我要從蒐藏中移除此項目"
+
+#: mediagoblin/user_pages/forms.py:39
+msgid "Collection"
+msgstr "蒐藏"
+
+#: mediagoblin/user_pages/forms.py:40
+msgid "-- Select --"
+msgstr "— 請選擇 —"
+
+#: mediagoblin/user_pages/forms.py:42
+msgid "Include a note"
+msgstr "加註"
+
+#: mediagoblin/user_pages/lib.py:58
+msgid "commented on your post"
+msgstr "在您的內容張貼留言"
+
+#: mediagoblin/user_pages/views.py:169
+msgid "Sorry, comments are disabled."
+msgstr "抱歉,留言被關閉。"
+
+#: mediagoblin/user_pages/views.py:174
+msgid "Oops, your comment was empty."
+msgstr "啊,您的留言是空的。"
+
+#: mediagoblin/user_pages/views.py:180
+msgid "Your comment has been posted!"
+msgstr "您的留言已經張貼完成!"
+
+#: mediagoblin/user_pages/views.py:205
+msgid "Please check your entries and try again."
+msgstr "請檢查項目並重試。"
+
+#: mediagoblin/user_pages/views.py:245
+msgid "You have to select or add a collection"
+msgstr "您需要選擇或是新增一個蒐藏"
+
+#: mediagoblin/user_pages/views.py:256
+#, python-format
+msgid "\"%s\" already in collection \"%s\""
+msgstr "「%s」已經在「%s」蒐藏"
+
+#: mediagoblin/user_pages/views.py:262
+#, python-format
+msgid "\"%s\" added to collection \"%s\""
+msgstr "「%s」加入「%s」蒐藏"
+
+#: mediagoblin/user_pages/views.py:282
+msgid "You deleted the media."
+msgstr "您已經刪除此媒體。"
+
+#: mediagoblin/user_pages/views.py:289
+msgid "The media was not deleted because you didn't check that you were sure."
+msgstr "由於您沒有勾選確認,該媒體沒有被移除。"
+
+#: mediagoblin/user_pages/views.py:296
+msgid "You are about to delete another user's media. Proceed with caution."
+msgstr "您正在刪除別人的媒體,請小心操作。"
+
+#: mediagoblin/user_pages/views.py:370
+msgid "You deleted the item from the collection."
+msgstr "您已經從該蒐藏中刪除該項目。"
+
+#: mediagoblin/user_pages/views.py:374
+msgid "The item was not removed because you didn't check that you were sure."
+msgstr "由於您沒有勾選確認,該項目沒有被移除。"
+
+#: mediagoblin/user_pages/views.py:382
+msgid ""
+"You are about to delete an item from another user's collection. Proceed with"
+" caution."
+msgstr "您正在從別人的蒐藏中刪除項目,請小心操作。"
+
+#: mediagoblin/user_pages/views.py:415
+#, python-format
+msgid "You deleted the collection \"%s\""
+msgstr "您已經刪除「%s」蒐藏。"
+
+#: mediagoblin/user_pages/views.py:422
+msgid ""
+"The collection was not deleted because you didn't check that you were sure."
+msgstr "由於您沒有勾選確認,該蒐藏沒有被移除。"
+
+#: mediagoblin/user_pages/views.py:430
+msgid ""
+"You are about to delete another user's collection. Proceed with caution."
+msgstr "您正在刪除別人的蒐藏,請小心操作。"
diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py
new file mode 100644
index 00000000..444c624f
--- /dev/null
+++ b/mediagoblin/init/__init__.py
@@ -0,0 +1,153 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import jinja2
+
+from mediagoblin.tools import staticdirect
+from mediagoblin.tools.translate import set_available_locales
+from mediagoblin.init.config import (
+ read_mediagoblin_config, generate_validation_report)
+from mediagoblin import mg_globals
+from mediagoblin.mg_globals import setup_globals
+from mediagoblin.db.open import setup_connection_and_db_from_config, \
+ check_db_migrations_current, load_models
+from mediagoblin.tools.pluginapi import hook_runall
+from mediagoblin.tools.workbench import WorkbenchManager
+from mediagoblin.storage import storage_system_from_config
+
+
+class Error(Exception):
+ pass
+
+
+class ImproperlyConfigured(Error):
+ pass
+
+
+def setup_locales():
+ """Checks which language translations are available and sets them"""
+ set_available_locales()
+
+
+def setup_global_and_app_config(config_path):
+ global_config, validation_result = read_mediagoblin_config(config_path)
+ app_config = global_config['mediagoblin']
+ # report errors if necessary
+ validation_report = generate_validation_report(
+ global_config, validation_result)
+ if validation_report:
+ raise ImproperlyConfigured(validation_report)
+
+ setup_globals(
+ app_config=app_config,
+ global_config=global_config)
+
+ return global_config, app_config
+
+
+def setup_database():
+ app_config = mg_globals.app_config
+
+ # Load all models for media types (plugins, ...)
+ load_models(app_config)
+
+ # Set up the database
+ db = setup_connection_and_db_from_config(app_config)
+
+ check_db_migrations_current(db)
+
+ setup_globals(database=db)
+
+ return db
+
+
+def get_jinja_loader(user_template_path=None, current_theme=None,
+ plugin_template_paths=None):
+ """
+ Set up the Jinja template loaders, possibly allowing for user
+ overridden templates.
+
+ (In the future we may have another system for providing theming;
+ for now this is good enough.)
+ """
+ path_list = []
+
+ # Add user path first--this takes precedence over everything.
+ if user_template_path is not None:
+ path_list.append(jinja2.FileSystemLoader(user_template_path))
+
+ # Any theme directories in the registry
+ if current_theme and current_theme.get('templates_dir'):
+ path_list.append(
+ jinja2.FileSystemLoader(
+ current_theme['templates_dir']))
+
+ # Add plugin template paths next--takes precedence over
+ # core templates.
+ if plugin_template_paths is not None:
+ path_list.extend((jinja2.FileSystemLoader(path)
+ for path in plugin_template_paths))
+
+ # Add core templates last.
+ path_list.append(jinja2.PackageLoader('mediagoblin', 'templates'))
+
+ return jinja2.ChoiceLoader(path_list)
+
+
+def get_staticdirector(app_config):
+ # At minimum, we need the direct_remote_path
+ if not 'direct_remote_path' in app_config \
+ or not 'theme_web_path' in app_config:
+ raise ImproperlyConfigured(
+ "direct_remote_path and theme_web_path must be provided")
+
+ direct_domains = {None: app_config['direct_remote_path'].strip()}
+ direct_domains['theme'] = app_config['theme_web_path'].strip()
+
+ # Let plugins load additional paths
+ for plugin_static in hook_runall("static_setup"):
+ direct_domains[plugin_static.name] = "%s/%s" % (
+ app_config['plugin_web_path'].rstrip('/'),
+ plugin_static.name)
+
+ return staticdirect.StaticDirect(
+ direct_domains)
+
+
+def setup_storage():
+ global_config = mg_globals.global_config
+
+ key_short = 'publicstore'
+ key_long = "storage:" + key_short
+ public_store = storage_system_from_config(global_config[key_long])
+
+ key_short = 'queuestore'
+ key_long = "storage:" + key_short
+ queue_store = storage_system_from_config(global_config[key_long])
+
+ setup_globals(
+ public_store=public_store,
+ queue_store=queue_store)
+
+ return public_store, queue_store
+
+
+def setup_workbench():
+ app_config = mg_globals.app_config
+
+ workbench_manager = WorkbenchManager(app_config['workbench_path'])
+
+ setup_globals(workbench_manager=workbench_manager)
diff --git a/mediagoblin/init/celery/__init__.py b/mediagoblin/init/celery/__init__.py
new file mode 100644
index 00000000..169cc935
--- /dev/null
+++ b/mediagoblin/init/celery/__init__.py
@@ -0,0 +1,99 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+
+from celery import Celery
+from mediagoblin.tools.pluginapi import hook_runall
+
+
+MANDATORY_CELERY_IMPORTS = ['mediagoblin.processing.task']
+
+DEFAULT_SETTINGS_MODULE = 'mediagoblin.init.celery.dummy_settings_module'
+
+
+def get_celery_settings_dict(app_config, global_config,
+ force_celery_always_eager=False):
+ """
+ Get a celery settings dictionary from reading the config
+ """
+ if 'celery' in global_config:
+ celery_conf = global_config['celery']
+ else:
+ celery_conf = {}
+
+ celery_settings = {}
+
+ # Add all celery settings from config
+ for key, value in celery_conf.iteritems():
+ celery_settings[key] = value
+
+ # TODO: use default result stuff here if it exists
+
+ # add mandatory celery imports
+ celery_imports = celery_settings.setdefault('CELERY_IMPORTS', [])
+ celery_imports.extend(MANDATORY_CELERY_IMPORTS)
+
+ if force_celery_always_eager:
+ celery_settings['CELERY_ALWAYS_EAGER'] = True
+ celery_settings['CELERY_EAGER_PROPAGATES_EXCEPTIONS'] = True
+
+ return celery_settings
+
+
+def setup_celery_app(app_config, global_config,
+ settings_module=DEFAULT_SETTINGS_MODULE,
+ force_celery_always_eager=False):
+ """
+ Setup celery without using terrible setup-celery-module hacks.
+ """
+ celery_settings = get_celery_settings_dict(
+ app_config, global_config, force_celery_always_eager)
+ celery_app = Celery()
+ celery_app.config_from_object(celery_settings)
+
+ hook_runall('celery_setup', celery_app)
+
+
+def setup_celery_from_config(app_config, global_config,
+ settings_module=DEFAULT_SETTINGS_MODULE,
+ force_celery_always_eager=False,
+ set_environ=True):
+ """
+ Take a mediagoblin app config and try to set up a celery settings
+ module from this.
+
+ Args:
+ - app_config: the application config section
+ - global_config: the entire ConfigObj loaded config, all sections
+ - settings_module: the module to populate, as a string
+ - force_celery_always_eager: whether or not to force celery into
+ always eager mode; good for development and small installs
+ - set_environ: if set, this will CELERY_CONFIG_MODULE to the
+ settings_module
+ """
+ celery_settings = get_celery_settings_dict(
+ app_config, global_config, force_celery_always_eager)
+
+ __import__(settings_module)
+ this_module = sys.modules[settings_module]
+
+ for key, value in celery_settings.iteritems():
+ setattr(this_module, key, value)
+
+ if set_environ:
+ os.environ['CELERY_CONFIG_MODULE'] = settings_module
diff --git a/mediagoblin/init/celery/dummy_settings_module.py b/mediagoblin/init/celery/dummy_settings_module.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediagoblin/init/celery/dummy_settings_module.py
diff --git a/mediagoblin/init/celery/from_celery.py b/mediagoblin/init/celery/from_celery.py
new file mode 100644
index 00000000..b395a826
--- /dev/null
+++ b/mediagoblin/init/celery/from_celery.py
@@ -0,0 +1,96 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+import logging.config
+
+from celery.signals import setup_logging
+
+from mediagoblin import app, mg_globals
+from mediagoblin.init.celery import setup_celery_from_config
+from mediagoblin.tools.pluginapi import hook_runall
+
+
+OUR_MODULENAME = __name__
+
+_log = logging.getLogger(__name__)
+
+
+def setup_logging_from_paste_ini(loglevel, **kw):
+ if os.path.exists(os.path.abspath('paste_local.ini')):
+ logging_conf_file = 'paste_local.ini'
+ else:
+ logging_conf_file = 'paste.ini'
+
+ # allow users to set up explicitly which paste file to check via the
+ # PASTE_CONFIG environment variable
+ logging_conf_file = os.environ.get(
+ 'PASTE_CONFIG', logging_conf_file)
+
+ if not os.path.exists(logging_conf_file):
+ raise IOError('{0} does not exist. Logging can not be set up.'.format(
+ logging_conf_file))
+
+ logging.config.fileConfig(logging_conf_file)
+
+ hook_runall('celery_logging_setup')
+
+
+setup_logging.connect(setup_logging_from_paste_ini)
+
+
+def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME,
+ default_conf_file=None):
+ """
+ Transform this module into a celery config module by reading the
+ mediagoblin config file. Set the environment variable
+ MEDIAGOBLIN_CONFIG to specify where this config file is.
+
+ By default it defaults to 'mediagoblin.ini'.
+
+ Note that if celery_setup_elsewhere is set in your config file,
+ this simply won't work.
+ """
+ if not default_conf_file:
+ if os.path.exists(os.path.abspath('mediagoblin_local.ini')):
+ default_conf_file = 'mediagoblin_local.ini'
+ else:
+ default_conf_file = 'mediagoblin.ini'
+
+ if check_environ_for_conf:
+ mgoblin_conf_file = os.path.abspath(
+ os.environ.get('MEDIAGOBLIN_CONFIG', default_conf_file))
+ else:
+ mgoblin_conf_file = default_conf_file
+
+ if not os.path.exists(mgoblin_conf_file):
+ raise IOError(
+ "MEDIAGOBLIN_CONFIG not set or file does not exist")
+
+ # By setting the environment variable here we should ensure that
+ # this is the module that gets set up.
+ os.environ['CELERY_CONFIG_MODULE'] = module_name
+ app.MediaGoblinApp(mgoblin_conf_file, setup_celery=False)
+
+ setup_celery_from_config(
+ mg_globals.app_config, mg_globals.global_config,
+ settings_module=module_name,
+ set_environ=False)
+
+
+if os.environ['CELERY_CONFIG_MODULE'] == OUR_MODULENAME:
+ setup_self()
diff --git a/mediagoblin/init/config.py b/mediagoblin/init/config.py
new file mode 100644
index 00000000..11a91cff
--- /dev/null
+++ b/mediagoblin/init/config.py
@@ -0,0 +1,164 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import os
+import pkg_resources
+
+from configobj import ConfigObj, flatten_errors
+from validate import Validator
+
+
+_log = logging.getLogger(__name__)
+
+
+CONFIG_SPEC_PATH = pkg_resources.resource_filename(
+ 'mediagoblin', 'config_spec.ini')
+
+
+def _setup_defaults(config, config_path):
+ """
+ Setup DEFAULTS in a config object from an (absolute) config_path.
+ """
+ config.setdefault('DEFAULT', {})
+ config['DEFAULT']['here'] = os.path.dirname(config_path)
+ config['DEFAULT']['__file__'] = config_path
+
+
+def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH):
+ """
+ Read a config object from config_path.
+
+ Does automatic value transformation based on the config_spec.
+ Also provides %(__file__)s and %(here)s values of this file and
+ its directory respectively similar to paste deploy.
+
+ Also reads for [plugins] section, appends all config_spec.ini
+ files from said plugins into the general config_spec specification.
+
+ This function doesn't itself raise any exceptions if validation
+ fails, you'll have to do something
+
+ Args:
+ - config_path: path to the config file
+ - config_spec: config file that provides defaults and value types
+ for validation / conversion. Defaults to mediagoblin/config_spec.ini
+
+ Returns:
+ A tuple like: (config, validation_result)
+ ... where 'conf' is the parsed config object and 'validation_result'
+ is the information from the validation process.
+ """
+ config_path = os.path.abspath(config_path)
+
+ # PRE-READ of config file. This allows us to fetch the plugins so
+ # we can add their plugin specs to the general config_spec.
+ config = ConfigObj(
+ config_path,
+ interpolation='ConfigParser')
+
+ plugins = config.get("plugins", {}).keys()
+ plugin_configs = {}
+
+ for plugin in plugins:
+ try:
+ plugin_config_spec_path = pkg_resources.resource_filename(
+ plugin, "config_spec.ini")
+ if not os.path.exists(plugin_config_spec_path):
+ continue
+
+ plugin_config_spec = ConfigObj(
+ plugin_config_spec_path,
+ encoding='UTF8', list_values=False, _inspec=True)
+ _setup_defaults(plugin_config_spec, config_path)
+
+ if not "plugin_spec" in plugin_config_spec:
+ continue
+
+ plugin_configs[plugin] = plugin_config_spec["plugin_spec"]
+
+ except ImportError:
+ _log.warning(
+ "When setting up config section, could not import '%s'" %
+ plugin)
+
+ # Now load the main config spec
+ config_spec = ConfigObj(
+ config_spec,
+ encoding='UTF8', list_values=False, _inspec=True)
+
+ # append the plugin specific sections of the config spec
+ config_spec['plugins'] = plugin_configs
+
+ _setup_defaults(config_spec, config_path)
+
+ config = ConfigObj(
+ config_path,
+ configspec=config_spec,
+ interpolation='ConfigParser')
+
+ _setup_defaults(config, config_path)
+
+ # For now the validator just works with the default functions,
+ # but in the future if we want to add additional validation/configuration
+ # functions we'd add them to validator.functions here.
+ #
+ # See also:
+ # http://www.voidspace.org.uk/python/validate.html#adding-functions
+ validator = Validator()
+ validation_result = config.validate(validator, preserve_errors=True)
+
+ return config, validation_result
+
+
+REPORT_HEADER = u"""\
+There were validation problems loading this config file:
+--------------------------------------------------------
+"""
+
+
+def generate_validation_report(config, validation_result):
+ """
+ Generate a report if necessary of problems while validating.
+
+ Returns:
+ Either a string describing for a user the problems validating
+ this config or None if there are no problems.
+ """
+ report = []
+
+ # Organize the report
+ for entry in flatten_errors(config, validation_result):
+ # each entry is a tuple
+ section_list, key, error = entry
+
+ if key is not None:
+ section_list.append(key)
+ else:
+ section_list.append(u'[missing section]')
+
+ section_string = u':'.join(section_list)
+
+ if error == False:
+ # We don't care about missing values for now.
+ continue
+
+ report.append(u"%s = %s" % (section_string, error))
+
+ if report:
+ return REPORT_HEADER + u"\n".join(report)
+ else:
+ return None
diff --git a/mediagoblin/init/plugins/__init__.py b/mediagoblin/init/plugins/__init__.py
new file mode 100644
index 00000000..0df4f381
--- /dev/null
+++ b/mediagoblin/init/plugins/__init__.py
@@ -0,0 +1,62 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import sys
+
+from mediagoblin import mg_globals
+from mediagoblin.tools import pluginapi
+
+
+_log = logging.getLogger(__name__)
+
+
+def setup_plugins():
+ """This loads, configures and registers plugins
+
+ See plugin documentation for more details.
+ """
+
+ global_config = mg_globals.global_config
+ plugin_section = global_config.get('plugins', {})
+
+ if not plugin_section:
+ _log.info("No plugins to load")
+ return
+
+ pman = pluginapi.PluginManager()
+
+ # Go through and import all the modules that are subsections of
+ # the [plugins] section and read in the hooks.
+ for plugin_module, config in plugin_section.items():
+ # Skip any modules that start with -. This makes it easier for
+ # someone to tweak their configuration so as to not load a
+ # plugin without having to remove swaths of plugin
+ # configuration.
+ if plugin_module.startswith('-'):
+ continue
+
+ _log.info("Importing plugin module: %s" % plugin_module)
+ pman.register_plugin(plugin_module)
+ # If this throws errors, that's ok--it'll halt mediagoblin
+ # startup.
+ __import__(plugin_module)
+ plugin = sys.modules[plugin_module]
+ if hasattr(plugin, 'hooks'):
+ pman.register_hooks(plugin.hooks)
+
+ # Execute anything registered to the setup hook.
+ pluginapi.hook_runall('setup')
diff --git a/mediagoblin/listings/__init__.py b/mediagoblin/listings/__init__.py
new file mode 100644
index 00000000..3f873e0c
--- /dev/null
+++ b/mediagoblin/listings/__init__.py
@@ -0,0 +1,19 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Non-user listing views and routing should go in this submodule.
+"""
diff --git a/mediagoblin/listings/routing.py b/mediagoblin/listings/routing.py
new file mode 100644
index 00000000..ee8f5020
--- /dev/null
+++ b/mediagoblin/listings/routing.py
@@ -0,0 +1,29 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.routing import add_route
+
+add_route('mediagoblin.listings.tags_listing',
+ "/tag/<string:tag>/",
+ "mediagoblin.listings.views:tag_listing")
+
+# Atom feeds:
+add_route('mediagoblin.listings.tag_atom_feed', "/tag/<string:tag>/atom/",
+ "mediagoblin.listings.views:atom_feed")
+
+# The all new entries feed
+add_route('mediagoblin.listings.atom_feed', '/atom/',
+ "mediagoblin.listings.views:atom_feed")
diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py
new file mode 100644
index 00000000..35af7148
--- /dev/null
+++ b/mediagoblin/listings/views.py
@@ -0,0 +1,110 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.db.models import MediaEntry
+from mediagoblin.db.util import media_entries_for_tag_slug
+from mediagoblin.tools.pagination import Pagination
+from mediagoblin.tools.response import render_to_response
+from mediagoblin.decorators import uses_pagination
+
+from werkzeug.contrib.atom import AtomFeed
+
+
+def _get_tag_name_from_entries(media_entries, tag_slug):
+ """
+ Get a tag name from the first entry by looking it up via its slug.
+ """
+ # ... this is slightly hacky looking :\
+ tag_name = tag_slug
+
+ for entry in media_entries:
+ for tag in entry.tags:
+ if tag['slug'] == tag_slug:
+ tag_name = tag['name']
+ break
+ break
+ return tag_name
+
+
+@uses_pagination
+def tag_listing(request, page):
+ """'Gallery'/listing for this tag slug"""
+ tag_slug = request.matchdict[u'tag']
+
+ cursor = media_entries_for_tag_slug(request.db, tag_slug)
+ cursor = cursor.order_by(MediaEntry.created.desc())
+
+ pagination = Pagination(page, cursor)
+ media_entries = pagination()
+
+ tag_name = _get_tag_name_from_entries(media_entries, tag_slug)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/listings/tag.html',
+ {'tag_slug': tag_slug,
+ 'tag_name': tag_name,
+ 'media_entries': media_entries,
+ 'pagination': pagination})
+
+
+ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15
+
+
+def atom_feed(request):
+ """
+ generates the atom feed with the tag images
+ """
+ tag_slug = request.matchdict.get(u'tag')
+ feed_title = "MediaGoblin Feed"
+ if tag_slug:
+ cursor = media_entries_for_tag_slug(request.db, tag_slug)
+ link = request.urlgen('mediagoblin.listings.tags_listing',
+ qualified=True, tag=tag_slug )
+ feed_title += "for tag '%s'" % tag_slug
+ else: # all recent item feed
+ cursor = MediaEntry.query.filter_by(state=u'processed')
+ link = request.urlgen('index', qualified=True)
+ feed_title += "for all recent items"
+
+ cursor = cursor.order_by(MediaEntry.created.desc())
+ cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
+
+ feed = AtomFeed(
+ feed_title,
+ feed_url=request.url,
+ id=link,
+ links=[{'href': link,
+ 'rel': 'alternate',
+ 'type': 'text/html'}])
+ for entry in cursor:
+ feed.add(entry.get('title'),
+ entry.description_html,
+ id=entry.url_for_self(request.urlgen,qualified=True),
+ content_type='html',
+ author={'name': entry.get_uploader.username,
+ 'uri': request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ qualified=True, user=entry.get_uploader.username)},
+ updated=entry.get('created'),
+ links=[{
+ 'href':entry.url_for_self(
+ request.urlgen,
+ qualified=True),
+ 'rel': 'alternate',
+ 'type': 'text/html'}])
+
+ return feed.get_response()
diff --git a/mediagoblin/meddleware/__init__.py b/mediagoblin/meddleware/__init__.py
new file mode 100644
index 00000000..886c9ad9
--- /dev/null
+++ b/mediagoblin/meddleware/__init__.py
@@ -0,0 +1,31 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ENABLED_MEDDLEWARE = [
+ 'mediagoblin.meddleware.csrf:CsrfMeddleware',
+ ]
+
+
+class BaseMeddleware(object):
+
+ def __init__(self, mg_app):
+ self.app = mg_app
+
+ def process_request(self, request, controller):
+ pass
+
+ def process_response(self, request, response):
+ pass
diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py
new file mode 100644
index 00000000..661f0ba2
--- /dev/null
+++ b/mediagoblin/meddleware/csrf.py
@@ -0,0 +1,152 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import random
+import logging
+
+from werkzeug.exceptions import Forbidden
+from wtforms import Form, HiddenField, validators
+
+from mediagoblin import mg_globals
+from mediagoblin.meddleware import BaseMeddleware
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+_log = logging.getLogger(__name__)
+
+# Use the system (hardware-based) random number generator if it exists.
+# -- this optimization is lifted from Django
+if hasattr(random, 'SystemRandom'):
+ getrandbits = random.SystemRandom().getrandbits
+else:
+ getrandbits = random.getrandbits
+
+
+def csrf_exempt(func):
+ """Decorate a Controller to exempt it from CSRF protection."""
+
+ func.csrf_enabled = False
+ return func
+
+
+class CsrfForm(Form):
+ """Simple form to handle rendering a CSRF token and confirming it
+ is included in the POST."""
+
+ csrf_token = HiddenField("",
+ [validators.Required()])
+
+
+def render_csrf_form_token(request):
+ """Render the CSRF token in a format suitable for inclusion in a
+ form."""
+
+ if 'CSRF_TOKEN' not in request.environ:
+ return None
+
+ form = CsrfForm(csrf_token=request.environ['CSRF_TOKEN'])
+
+ return form.csrf_token
+
+
+class CsrfMeddleware(BaseMeddleware):
+ """CSRF Protection Meddleware
+
+ Adds a CSRF Cookie to responses and verifies that it is present
+ and matches the form token for non-safe requests.
+ """
+
+ CSRF_KEYLEN = 64
+ SAFE_HTTP_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE")
+
+ def process_request(self, request, controller):
+ """For non-safe requests, confirm that the tokens are present
+ and match.
+ """
+
+ # get the token from the cookie
+ try:
+ request.environ['CSRF_TOKEN'] = \
+ request.cookies[mg_globals.app_config['csrf_cookie_name']]
+
+ except KeyError:
+ # if it doesn't exist, make a new one
+ request.environ['CSRF_TOKEN'] = self._make_token(request)
+
+ # if this is a non-"safe" request (ie, one that could have
+ # side effects), confirm that the CSRF tokens are present and
+ # valid
+ if (getattr(controller, 'csrf_enabled', True) and
+ request.method not in self.SAFE_HTTP_METHODS and
+ ('gmg.verify_csrf' in request.environ or
+ 'paste.testing' not in request.environ)
+ ):
+
+ return self.verify_tokens(request)
+
+ def process_response(self, request, response):
+ """Add the CSRF cookie to the response if needed and set Vary
+ headers.
+ """
+
+ # set the CSRF cookie
+ response.set_cookie(
+ mg_globals.app_config['csrf_cookie_name'],
+ request.environ['CSRF_TOKEN'],
+ path=request.environ['SCRIPT_NAME'],
+ domain=mg_globals.app_config.get('csrf_cookie_domain'),
+ secure=(request.scheme.lower() == 'https'),
+ httponly=True)
+
+ # update the Vary header
+ response.vary = (getattr(response, 'vary', None) or []) + ['Cookie']
+
+ def _make_token(self, request):
+ """Generate a new token to use for CSRF protection."""
+
+ return "%s" % (getrandbits(self.CSRF_KEYLEN),)
+
+ def verify_tokens(self, request):
+ """Verify that the CSRF Cookie exists and that it matches the
+ form value."""
+
+ # confirm the cookie token was presented
+ cookie_token = request.cookies.get(
+ mg_globals.app_config['csrf_cookie_name'],
+ None)
+
+ if cookie_token is None:
+ # the CSRF cookie must be present in the request, if not a
+ # cookie blocker might be in action (in the best case)
+ _log.error('CSRF cookie not present')
+ raise Forbidden(_('CSRF cookie not present. This is most likely '
+ 'the result of a cookie blocker or somesuch.<br/>'
+ 'Make sure to permit the settings of cookies for '
+ 'this domain.'))
+
+ # get the form token and confirm it matches
+ form = CsrfForm(request.form)
+ if form.validate():
+ form_token = form.csrf_token.data
+
+ if form_token == cookie_token:
+ # all's well that ends well
+ return
+
+ # either the tokens didn't match or the form token wasn't
+ # present; either way, the request is denied
+ errstr = 'CSRF validation failed'
+ _log.error(errstr)
+ raise Forbidden(errstr)
diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py
new file mode 100644
index 00000000..20e1918e
--- /dev/null
+++ b/mediagoblin/media_types/__init__.py
@@ -0,0 +1,155 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import logging
+import tempfile
+
+from mediagoblin import mg_globals
+from mediagoblin.tools.common import import_component
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+_log = logging.getLogger(__name__)
+
+class FileTypeNotSupported(Exception):
+ pass
+
+class InvalidFileType(Exception):
+ pass
+
+
+class MediaManagerBase(object):
+ "Base class for all media managers"
+
+ # Please override in actual media managers
+ media_fetch_order = None
+
+ @staticmethod
+ def sniff_handler(*args, **kwargs):
+ return False
+
+ def __init__(self, entry):
+ self.entry = entry
+
+ def __getitem__(self, i):
+ return getattr(self, i)
+
+ def __contains__(self, i):
+ return hasattr(self, i)
+
+
+class CompatMediaManager(object):
+ def __init__(self, mm_dict, entry=None):
+ self.mm_dict = mm_dict
+ self.entry = entry
+
+ def __call__(self, entry):
+ "So this object can look like a class too, somehow"
+ assert self.entry is None
+ return self.__class__(self.mm_dict, entry)
+
+ def __getitem__(self, i):
+ return self.mm_dict[i]
+
+ def __contains__(self, i):
+ return (i in self.mm_dict)
+
+ @property
+ def media_fetch_order(self):
+ return self.mm_dict.get('media_fetch_order')
+
+ def sniff_handler(self, *args, **kwargs):
+ func = self.mm_dict.get("sniff_handler", None)
+ if func is not None:
+ return func(*args, **kwargs)
+ return False
+
+ def __getattr__(self, i):
+ return self.mm_dict[i]
+
+
+def sniff_media(media):
+ '''
+ Iterate through the enabled media types and find those suited
+ for a certain file.
+ '''
+
+ try:
+ return get_media_type_and_manager(media.filename)
+ except FileTypeNotSupported:
+ _log.info('No media handler found by file extension. Doing it the expensive way...')
+ # Create a temporary file for sniffers suchs as GStreamer-based
+ # Audio video
+ media_file = tempfile.NamedTemporaryFile()
+ media_file.write(media.stream.read())
+ media.stream.seek(0)
+
+ for media_type, manager in get_media_managers():
+ _log.info('Sniffing {0}'.format(media_type))
+ if manager.sniff_handler(media_file, media=media):
+ _log.info('{0} accepts the file'.format(media_type))
+ return media_type, manager
+ else:
+ _log.debug('{0} did not accept the file'.format(media_type))
+
+ raise FileTypeNotSupported(
+ # TODO: Provide information on which file types are supported
+ _(u'Sorry, I don\'t support that file type :('))
+
+
+def get_media_types():
+ """
+ Generator, yields the available media types
+ """
+ for media_type in mg_globals.app_config['media_types']:
+ yield media_type
+
+
+def get_media_managers():
+ '''
+ Generator, yields all enabled media managers
+ '''
+ for media_type in get_media_types():
+ mm = import_component(media_type + ":MEDIA_MANAGER")
+
+ if isinstance(mm, dict):
+ mm = CompatMediaManager(mm)
+
+ yield media_type, mm
+
+
+def get_media_type_and_manager(filename):
+ '''
+ Try to find the media type based on the file name, extension
+ specifically. This is used as a speedup, the sniffing functionality
+ then falls back on more in-depth bitsniffing of the source file.
+ '''
+ if filename.find('.') > 0:
+ # Get the file extension
+ ext = os.path.splitext(filename)[1].lower()
+
+ for media_type, manager in get_media_managers():
+ # Omit the dot from the extension and match it against
+ # the media manager
+ if ext[1:] in manager.accepted_extensions:
+ return media_type, manager
+ else:
+ _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
+ filename))
+
+ raise FileTypeNotSupported(
+ _(u'Sorry, I don\'t support that file type :('))
diff --git a/mediagoblin/media_types/ascii/__init__.py b/mediagoblin/media_types/ascii/__init__.py
new file mode 100644
index 00000000..0931e83a
--- /dev/null
+++ b/mediagoblin/media_types/ascii/__init__.py
@@ -0,0 +1,31 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.media_types import MediaManagerBase
+from mediagoblin.media_types.ascii.processing import process_ascii, \
+ sniff_handler
+
+
+class ASCIIMediaManager(MediaManagerBase):
+ human_readable = "ASCII"
+ processor = staticmethod(process_ascii)
+ sniff_handler = staticmethod(sniff_handler)
+ display_template = "mediagoblin/media_displays/ascii.html"
+ default_thumb = "images/media_thumbs/ascii.jpg"
+ accepted_extensions = ["txt", "asc", "nfo"]
+
+
+MEDIA_MANAGER = ASCIIMediaManager
diff --git a/mediagoblin/media_types/ascii/asciitoimage.py b/mediagoblin/media_types/ascii/asciitoimage.py
new file mode 100644
index 00000000..786941f6
--- /dev/null
+++ b/mediagoblin/media_types/ascii/asciitoimage.py
@@ -0,0 +1,146 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+try:
+ from PIL import Image
+ from PIL import ImageFont
+ from PIL import ImageDraw
+except ImportError:
+ import Image
+ import ImageFont
+ import ImageDraw
+import logging
+import pkg_resources
+import os
+
+_log = logging.getLogger(__name__)
+
+
+class AsciiToImage(object):
+ '''
+ Converter of ASCII art into image files, preserving whitespace
+
+ kwargs:
+ - font: Path to font file
+ default: fonts/Inconsolata.otf
+ - font_size: Font size, ``int``
+ default: 11
+ '''
+ def __init__(self, **kw):
+ self._font = kw.get('font', pkg_resources.resource_filename(
+ 'mediagoblin.media_types.ascii',
+ os.path.join('fonts', 'Inconsolata.otf')))
+
+ self._font_size = kw.get('font_size', 11)
+
+ self._if = ImageFont.truetype(
+ self._font,
+ self._font_size,
+ encoding='unic')
+
+ _log.info('Font set to {0}, size {1}'.format(
+ self._font,
+ self._font_size))
+
+ # ,-,-^-'-^'^-^'^-'^-.
+ # ( I am a wall socket )Oo, ___
+ # `-.,.-.,.-.-.,.-.--' ' `
+ # Get the size, in pixels of the '.' character
+ self._if_dims = self._if.getsize('.')
+ # `---'
+
+ def convert(self, text, destination):
+ # TODO: Detect if text is a file-like, if so, act accordingly
+ im = self._create_image(text)
+
+ # PIL's Image.save will handle both file-likes and paths
+ if im.save(destination):
+ _log.info('Saved image in {0}'.format(
+ destination))
+
+ def _create_image(self, text):
+ '''
+ Write characters to a PIL image canvas.
+
+ TODO:
+ - Character set detection and decoding,
+ http://pypi.python.org/pypi/chardet
+ '''
+ _log.debug('Drawing image')
+ # Convert the input from str to unicode
+ text = text.decode('utf-8')
+
+ # TODO: Account for alternative line endings
+ lines = text.split('\n')
+
+ line_lengths = [len(i) for i in lines]
+
+ # Calculate destination size based on text input and character size
+ im_dims = (
+ max(line_lengths) * self._if_dims[0],
+ len(line_lengths) * self._if_dims[1])
+
+ _log.info('Destination image dimensions will be {0}'.format(
+ im_dims))
+
+ im = Image.new(
+ 'RGBA',
+ im_dims,
+ (255, 255, 255, 0))
+
+ draw = ImageDraw.Draw(im)
+
+ char_pos = [0, 0]
+
+ for line in lines:
+ line_length = len(line)
+
+ _log.debug('Writing line at {0}'.format(char_pos))
+
+ for _pos in range(0, line_length):
+ char = line[_pos]
+
+ px_pos = self._px_pos(char_pos)
+
+ _log.debug('Writing character "{0}" at {1} (px pos {2})'.format(
+ char.encode('ascii', 'replace'),
+ char_pos,
+ px_pos))
+
+ draw.text(
+ px_pos,
+ char,
+ font=self._if,
+ fill=(0, 0, 0, 255))
+
+ char_pos[0] += 1
+
+ # Reset X position, increment Y position
+ char_pos[0] = 0
+ char_pos[1] += 1
+
+ return im
+
+ def _px_pos(self, char_pos):
+ '''
+ Helper function to calculate the pixel position based on
+ character position and character dimensions
+ '''
+ px_pos = [0, 0]
+ for index, val in zip(range(0, len(char_pos)), char_pos):
+ px_pos[index] = char_pos[index] * self._if_dims[index]
+
+ return px_pos
diff --git a/mediagoblin/media_types/ascii/fonts/Inconsolata.otf b/mediagoblin/media_types/ascii/fonts/Inconsolata.otf
new file mode 120000
index 00000000..4e742b5e
--- /dev/null
+++ b/mediagoblin/media_types/ascii/fonts/Inconsolata.otf
@@ -0,0 +1 @@
+../../../../extlib/inconsolata/Inconsolata.otf \ No newline at end of file
diff --git a/mediagoblin/media_types/ascii/migrations.py b/mediagoblin/media_types/ascii/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/ascii/migrations.py
@@ -0,0 +1,17 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/ascii/models.py b/mediagoblin/media_types/ascii/models.py
new file mode 100644
index 00000000..c7505292
--- /dev/null
+++ b/mediagoblin/media_types/ascii/models.py
@@ -0,0 +1,40 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.base import Base
+
+from sqlalchemy import (
+ Column, Integer, ForeignKey)
+from sqlalchemy.orm import relationship, backref
+
+
+BACKREF_NAME = "ascii__media_data"
+
+
+class AsciiData(Base):
+ __tablename__ = "ascii__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref(BACKREF_NAME, uselist=False,
+ cascade="all, delete-orphan"))
+
+
+DATA_MODEL = AsciiData
+MODELS = [AsciiData]
diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py
new file mode 100644
index 00000000..2f6079be
--- /dev/null
+++ b/mediagoblin/media_types/ascii/processing.py
@@ -0,0 +1,146 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import chardet
+import os
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+import logging
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.processing import create_pub_filepath
+from mediagoblin.media_types.ascii import asciitoimage
+
+_log = logging.getLogger(__name__)
+
+SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo']
+
+
+def sniff_handler(media_file, **kw):
+ if kw.get('media') is not None:
+ name, ext = os.path.splitext(kw['media'].filename)
+ clean_ext = ext[1:].lower()
+
+ if clean_ext in SUPPORTED_EXTENSIONS:
+ return True
+
+ return False
+
+
+def process_ascii(proc_state):
+ """Code to process a txt file. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
+ entry = proc_state.entry
+ workbench = proc_state.workbench
+ ascii_config = mgg.global_config['media_type:mediagoblin.media_types.ascii']
+ # Conversions subdirectory to avoid collisions
+ conversions_subdir = os.path.join(
+ workbench.dir, 'conversions')
+ os.mkdir(conversions_subdir)
+
+ queued_filepath = entry.queued_media_file
+ queued_filename = workbench.localized_file(
+ mgg.queue_store, queued_filepath,
+ 'source')
+
+ queued_file = file(queued_filename, 'rb')
+
+ with queued_file:
+ queued_file_charset = chardet.detect(queued_file.read())
+
+ # Only select a non-utf-8 charset if chardet is *really* sure
+ # Tested with "Feli\x0109an superjaron", which was detecte
+ if queued_file_charset['confidence'] < 0.9:
+ interpreted_charset = 'utf-8'
+ else:
+ interpreted_charset = queued_file_charset['encoding']
+
+ _log.info('Charset detected: {0}\nWill interpret as: {1}'.format(
+ queued_file_charset,
+ interpreted_charset))
+
+ queued_file.seek(0) # Rewind the queued file
+
+ thumb_filepath = create_pub_filepath(
+ entry, 'thumbnail.png')
+
+ tmp_thumb_filename = os.path.join(
+ conversions_subdir, thumb_filepath[-1])
+
+ ascii_converter_args = {}
+
+ if ascii_config['thumbnail_font']:
+ ascii_converter_args.update(
+ {'font': ascii_config['thumbnail_font']})
+
+ converter = asciitoimage.AsciiToImage(
+ **ascii_converter_args)
+
+ thumb = converter._create_image(
+ queued_file.read())
+
+ with file(tmp_thumb_filename, 'w') as thumb_file:
+ thumb.thumbnail(
+ (mgg.global_config['media:thumb']['max_width'],
+ mgg.global_config['media:thumb']['max_height']),
+ Image.ANTIALIAS)
+ thumb.save(thumb_file)
+
+ _log.debug('Copying local file to public storage')
+ mgg.public_store.copy_local_to_storage(
+ tmp_thumb_filename, thumb_filepath)
+
+ queued_file.seek(0)
+
+ original_filepath = create_pub_filepath(entry, queued_filepath[-1])
+
+ with mgg.public_store.get_file(original_filepath, 'wb') \
+ as original_file:
+ original_file.write(queued_file.read())
+
+ queued_file.seek(0) # Rewind *again*
+
+ unicode_filepath = create_pub_filepath(entry, 'ascii-portable.txt')
+
+ with mgg.public_store.get_file(unicode_filepath, 'wb') \
+ as unicode_file:
+ # Decode the original file from its detected charset (or UTF8)
+ # Encode the unicode instance to ASCII and replace any non-ASCII
+ # with an HTML entity (&#
+ unicode_file.write(
+ unicode(queued_file.read().decode(
+ interpreted_charset)).encode(
+ 'ascii',
+ 'xmlcharrefreplace'))
+
+ # Remove queued media file from storage and database.
+ # queued_filepath is in the task_id directory which should
+ # be removed too, but fail if the directory is not empty to be on
+ # the super-safe side.
+ mgg.queue_store.delete_file(queued_filepath) # rm file
+ mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir
+ entry.queued_media_file = []
+
+ media_files_dict = entry.setdefault('media_files', {})
+ media_files_dict['thumb'] = thumb_filepath
+ media_files_dict['unicode'] = unicode_filepath
+ media_files_dict['original'] = original_filepath
+
+ entry.save()
diff --git a/mediagoblin/media_types/audio/__init__.py b/mediagoblin/media_types/audio/__init__.py
new file mode 100644
index 00000000..2eb7300e
--- /dev/null
+++ b/mediagoblin/media_types/audio/__init__.py
@@ -0,0 +1,30 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.media_types import MediaManagerBase
+from mediagoblin.media_types.audio.processing import process_audio, \
+ sniff_handler
+
+
+class AudioMediaManager(MediaManagerBase):
+ human_readable = "Audio"
+ processor = staticmethod(process_audio)
+ sniff_handler = staticmethod(sniff_handler)
+ display_template = "mediagoblin/media_displays/audio.html"
+ accepted_extensions = ["mp3", "flac", "wav", "m4a"]
+
+
+MEDIA_MANAGER = AudioMediaManager
diff --git a/mediagoblin/media_types/audio/audioprocessing.py b/mediagoblin/media_types/audio/audioprocessing.py
new file mode 120000
index 00000000..c5e3c52c
--- /dev/null
+++ b/mediagoblin/media_types/audio/audioprocessing.py
@@ -0,0 +1 @@
+../../../extlib/freesound/audioprocessing.py \ No newline at end of file
diff --git a/mediagoblin/media_types/audio/migrations.py b/mediagoblin/media_types/audio/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/audio/migrations.py
@@ -0,0 +1,17 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/audio/models.py b/mediagoblin/media_types/audio/models.py
new file mode 100644
index 00000000..d01367d5
--- /dev/null
+++ b/mediagoblin/media_types/audio/models.py
@@ -0,0 +1,40 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.base import Base
+
+from sqlalchemy import (
+ Column, Integer, ForeignKey)
+from sqlalchemy.orm import relationship, backref
+
+
+BACKREF_NAME = "audio__media_data"
+
+
+class AudioData(Base):
+ __tablename__ = "audio__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref(BACKREF_NAME, uselist=False,
+ cascade="all, delete-orphan"))
+
+
+DATA_MODEL = AudioData
+MODELS = [AudioData]
diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py
new file mode 100644
index 00000000..101b83e5
--- /dev/null
+++ b/mediagoblin/media_types/audio/processing.py
@@ -0,0 +1,156 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+from tempfile import NamedTemporaryFile
+import os
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.processing import (create_pub_filepath, BadMediaFail,
+ FilenameBuilder, ProgressCallback)
+
+from mediagoblin.media_types.audio.transcoders import (AudioTranscoder,
+ AudioThumbnailer)
+
+_log = logging.getLogger(__name__)
+
+
+def sniff_handler(media_file, **kw):
+ try:
+ transcoder = AudioTranscoder()
+ data = transcoder.discover(media_file.name)
+ except BadMediaFail:
+ _log.debug('Audio discovery raised BadMediaFail')
+ return False
+
+ if data.is_audio == True and data.is_video == False:
+ return True
+
+ return False
+
+
+def process_audio(proc_state):
+ """Code to process uploaded audio. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
+ entry = proc_state.entry
+ workbench = proc_state.workbench
+ audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio']
+
+ queued_filepath = entry.queued_media_file
+ queued_filename = workbench.localized_file(
+ mgg.queue_store, queued_filepath,
+ 'source')
+ name_builder = FilenameBuilder(queued_filename)
+
+ webm_audio_filepath = create_pub_filepath(
+ entry,
+ '{original}.webm'.format(
+ original=os.path.splitext(
+ queued_filepath[-1])[0]))
+
+ if audio_config['keep_original']:
+ with open(queued_filename, 'rb') as queued_file:
+ original_filepath = create_pub_filepath(
+ entry, name_builder.fill('{basename}{ext}'))
+
+ with mgg.public_store.get_file(original_filepath, 'wb') as \
+ original_file:
+ _log.debug('Saving original...')
+ original_file.write(queued_file.read())
+
+ entry.media_files['original'] = original_filepath
+
+ transcoder = AudioTranscoder()
+
+ with NamedTemporaryFile(dir=workbench.dir) as webm_audio_tmp:
+ progress_callback = ProgressCallback(entry)
+
+ transcoder.transcode(
+ queued_filename,
+ webm_audio_tmp.name,
+ quality=audio_config['quality'],
+ progress_callback=progress_callback)
+
+ transcoder.discover(webm_audio_tmp.name)
+
+ _log.debug('Saving medium...')
+ mgg.public_store.get_file(webm_audio_filepath, 'wb').write(
+ webm_audio_tmp.read())
+
+ entry.media_files['webm_audio'] = webm_audio_filepath
+
+ # entry.media_data_init(length=int(data.audiolength))
+
+ if audio_config['create_spectrogram']:
+ spectrogram_filepath = create_pub_filepath(
+ entry,
+ '{original}-spectrogram.jpg'.format(
+ original=os.path.splitext(
+ queued_filepath[-1])[0]))
+
+ with NamedTemporaryFile(dir=workbench.dir, suffix='.ogg') as wav_tmp:
+ _log.info('Creating OGG source for spectrogram')
+ transcoder.transcode(
+ queued_filename,
+ wav_tmp.name,
+ mux_string='vorbisenc quality={0} ! oggmux'.format(
+ audio_config['quality']))
+
+ thumbnailer = AudioThumbnailer()
+
+ with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as spectrogram_tmp:
+ thumbnailer.spectrogram(
+ wav_tmp.name,
+ spectrogram_tmp.name,
+ width=mgg.global_config['media:medium']['max_width'],
+ fft_size=audio_config['spectrogram_fft_size'])
+
+ _log.debug('Saving spectrogram...')
+ mgg.public_store.get_file(spectrogram_filepath, 'wb').write(
+ spectrogram_tmp.read())
+
+ entry.media_files['spectrogram'] = spectrogram_filepath
+
+ with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as thumb_tmp:
+ thumbnailer.thumbnail_spectrogram(
+ spectrogram_tmp.name,
+ thumb_tmp.name,
+ (mgg.global_config['media:thumb']['max_width'],
+ mgg.global_config['media:thumb']['max_height']))
+
+ thumb_filepath = create_pub_filepath(
+ entry,
+ '{original}-thumbnail.jpg'.format(
+ original=os.path.splitext(
+ queued_filepath[-1])[0]))
+
+ mgg.public_store.get_file(thumb_filepath, 'wb').write(
+ thumb_tmp.read())
+
+ entry.media_files['thumb'] = thumb_filepath
+ else:
+ entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg']
+
+ # Remove queued media file from storage and database.
+ # queued_filepath is in the task_id directory which should
+ # be removed too, but fail if the directory is not empty to be on
+ # the super-safe side.
+ mgg.queue_store.delete_file(queued_filepath) # rm file
+ mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir
+ entry.queued_media_file = []
diff --git a/mediagoblin/media_types/audio/spectrogram.py b/mediagoblin/media_types/audio/spectrogram.py
new file mode 100644
index 00000000..dd4d0299
--- /dev/null
+++ b/mediagoblin/media_types/audio/spectrogram.py
@@ -0,0 +1,360 @@
+# processing.py -- various audio processing functions
+# Copyright (C) 2008 MUSIC TECHNOLOGY GROUP (MTG)
+# UNIVERSITAT POMPEU FABRA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+# Bram de Jong <bram.dejong at domain.com where domain in gmail>
+# 2012, Joar Wandborg <first name at last name dot se>
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+import math
+import numpy
+
+try:
+ import scikits.audiolab as audiolab
+except ImportError:
+ print "WARNING: audiolab is not installed so wav2png will not work"
+
+
+class AudioProcessingException(Exception):
+ pass
+
+
+class SpectrogramImage(object):
+ def __init__(self, image_size, fft_size):
+ self.image_width, self.image_height = image_size
+ self.fft_size = fft_size
+
+ colors = [
+ (0, 0, 0, 0),
+ (58 / 4, 68 / 4, 65 / 4, 255),
+ (80 / 2, 100 / 2, 153 / 2, 255),
+ (90, 180, 100, 255),
+ (224, 224, 44, 255),
+ (255, 60, 30, 255),
+ (255, 255, 255, 255)
+ ]
+
+ self.palette = interpolate_colors(colors)
+
+ # Generate lookup table for y-coordinate from fft-bin
+ self.y_to_bin = []
+
+ fft_min = 100.0
+ fft_max = 22050.0 # kHz?
+
+ y_min = math.log10(fft_min)
+ y_max = math.log10(fft_max)
+
+ for y in range(self.image_height):
+ freq = math.pow(
+ 10.0,
+ y_min + y / (self.image_height - 1.0)
+ * (y_max - y_min))
+
+ fft_bin = freq / fft_max * (self.fft_size / 2 + 1)
+
+ if fft_bin < self.fft_size / 2:
+ alpha = fft_bin - int(fft_bin)
+
+ self.y_to_bin.append((int(fft_bin), alpha * 255))
+
+ # this is a bit strange, but using image.load()[x,y] = ... is
+ # a lot slower than using image.putadata and then rotating the image
+ # so we store all the pixels in an array and then create the image when saving
+ self.pixels = []
+
+ def draw_spectrum(self, x, spectrum):
+ # for all frequencies, draw the pixels
+ for index, alpha in self.y_to_bin:
+ self.pixels.append(
+ self.palette[int((255.0 - alpha) * spectrum[index]
+ + alpha * spectrum[index + 1])])
+
+ # if the FFT is too small to fill up the image, fill with black to the top
+ for y in range(len(self.y_to_bin), self.image_height):
+ self.pixels.append(self.palette[0])
+
+ def save(self, filename, quality=90):
+ self.image = Image.new(
+ 'RGBA',
+ (self.image_height, self.image_width))
+
+ self.image.putdata(self.pixels)
+ self.image.transpose(Image.ROTATE_90).save(
+ filename,
+ quality=quality)
+
+
+class AudioProcessor(object):
+ """
+ The audio processor processes chunks of audio an calculates the spectrac centroid and the peak
+ samples in that chunk of audio.
+ """
+ def __init__(self, input_filename, fft_size, window_function=numpy.hanning):
+ max_level = get_max_level(input_filename)
+
+ self.audio_file = audiolab.Sndfile(input_filename, 'r')
+ self.fft_size = fft_size
+ self.window = window_function(self.fft_size)
+ self.spectrum_range = None
+ self.lower = 100
+ self.higher = 22050
+ self.lower_log = math.log10(self.lower)
+ self.higher_log = math.log10(self.higher)
+ self.clip = lambda val, low, high: min(high, max(low, val))
+
+ # figure out what the maximum value is for an FFT doing the FFT of a DC signal
+ fft = numpy.fft.rfft(numpy.ones(fft_size) * self.window)
+ max_fft = (numpy.abs(fft)).max()
+
+ # set the scale to normalized audio and normalized FFT
+ self.scale = 1.0 / max_level / max_fft if max_level > 0 else 1
+
+ def read(self, start, size, resize_if_less=False):
+ """ read size samples starting at start, if resize_if_less is True and less than size
+ samples are read, resize the array to size and fill with zeros """
+
+ # number of zeros to add to start and end of the buffer
+ add_to_start = 0
+ add_to_end = 0
+
+ if start < 0:
+ # the first FFT window starts centered around zero
+ if size + start <= 0:
+ return numpy.zeros(size) if resize_if_less else numpy.array([])
+ else:
+ self.audio_file.seek(0)
+
+ add_to_start = - start # remember: start is negative!
+ to_read = size + start
+
+ if to_read > self.audio_file.nframes:
+ add_to_end = to_read - self.audio_file.nframes
+ to_read = self.audio_file.nframes
+ else:
+ self.audio_file.seek(start)
+
+ to_read = size
+ if start + to_read >= self.audio_file.nframes:
+ to_read = self.audio_file.nframes - start
+ add_to_end = size - to_read
+
+ try:
+ samples = self.audio_file.read_frames(to_read)
+ except RuntimeError:
+ # this can happen for wave files with broken headers...
+ return numpy.zeros(size) if resize_if_less else numpy.zeros(2)
+
+ # convert to mono by selecting left channel only
+ if self.audio_file.channels > 1:
+ samples = samples[:,0]
+
+ if resize_if_less and (add_to_start > 0 or add_to_end > 0):
+ if add_to_start > 0:
+ samples = numpy.concatenate((numpy.zeros(add_to_start), samples), axis=1)
+
+ if add_to_end > 0:
+ samples = numpy.resize(samples, size)
+ samples[size - add_to_end:] = 0
+
+ return samples
+
+ def spectral_centroid(self, seek_point, spec_range=110.0):
+ """ starting at seek_point read fft_size samples, and calculate the spectral centroid """
+
+ samples = self.read(seek_point - self.fft_size/2, self.fft_size, True)
+
+ samples *= self.window
+ fft = numpy.fft.rfft(samples)
+ spectrum = self.scale * numpy.abs(fft) # normalized abs(FFT) between 0 and 1
+
+ length = numpy.float64(spectrum.shape[0])
+
+ # scale the db spectrum from [- spec_range db ... 0 db] > [0..1]
+ db_spectrum = ((20*(numpy.log10(spectrum + 1e-60))).clip(-spec_range, 0.0) + spec_range)/spec_range
+
+ energy = spectrum.sum()
+ spectral_centroid = 0
+
+ if energy > 1e-60:
+ # calculate the spectral centroid
+
+ if self.spectrum_range == None:
+ self.spectrum_range = numpy.arange(length)
+
+ spectral_centroid = (spectrum * self.spectrum_range).sum() / (energy * (length - 1)) * self.audio_file.samplerate * 0.5
+
+ # clip > log10 > scale between 0 and 1
+ spectral_centroid = (math.log10(self.clip(spectral_centroid, self.lower, self.higher)) - self.lower_log) / (self.higher_log - self.lower_log)
+
+ return (spectral_centroid, db_spectrum)
+
+
+ def peaks(self, start_seek, end_seek):
+ """ read all samples between start_seek and end_seek, then find the minimum and maximum peak
+ in that range. Returns that pair in the order they were found. So if min was found first,
+ it returns (min, max) else the other way around. """
+
+ # larger blocksizes are faster but take more mem...
+ # Aha, Watson, a clue, a tradeof!
+ block_size = 4096
+
+ max_index = -1
+ max_value = -1
+ min_index = -1
+ min_value = 1
+
+ if start_seek < 0:
+ start_seek = 0
+
+ if end_seek > self.audio_file.nframes:
+ end_seek = self.audio_file.nframes
+
+ if end_seek <= start_seek:
+ samples = self.read(start_seek, 1)
+ return (samples[0], samples[0])
+
+ if block_size > end_seek - start_seek:
+ block_size = end_seek - start_seek
+
+ for i in range(start_seek, end_seek, block_size):
+ samples = self.read(i, block_size)
+
+ local_max_index = numpy.argmax(samples)
+ local_max_value = samples[local_max_index]
+
+ if local_max_value > max_value:
+ max_value = local_max_value
+ max_index = local_max_index
+
+ local_min_index = numpy.argmin(samples)
+ local_min_value = samples[local_min_index]
+
+ if local_min_value < min_value:
+ min_value = local_min_value
+ min_index = local_min_index
+
+ return (min_value, max_value) if min_index < max_index else (max_value, min_value)
+
+
+def create_spectrogram_image(source_filename, output_filename,
+ image_size, fft_size, progress_callback=None):
+
+ processor = AudioProcessor(source_filename, fft_size, numpy.hamming)
+ samples_per_pixel = processor.audio_file.nframes / float(image_size[0])
+
+ spectrogram = SpectrogramImage(image_size, fft_size)
+
+ for x in range(image_size[0]):
+ if progress_callback and x % (image_size[0] / 10) == 0:
+ progress_callback((x * 100) / image_size[0])
+
+ seek_point = int(x * samples_per_pixel)
+ next_seek_point = int((x + 1) * samples_per_pixel)
+
+ (spectral_centroid, db_spectrum) = processor.spectral_centroid(seek_point)
+
+ spectrogram.draw_spectrum(x, db_spectrum)
+
+ if progress_callback:
+ progress_callback(100)
+
+ spectrogram.save(output_filename)
+
+
+def interpolate_colors(colors, flat=False, num_colors=256):
+
+ palette = []
+
+ for i in range(num_colors):
+ # TODO: What does this do?
+ index = (
+ (i *
+ (len(colors) - 1) # 7
+ ) # 0..7..14..21..28...
+ /
+ (num_colors - 1.0) # 255.0
+ )
+
+ # TODO: What is the meaning of 'alpha' in this context?
+ alpha = index - round(index)
+
+ channels = list('rgb')
+ values = dict()
+
+ for k, v in zip(range(len(channels)), channels):
+ if alpha > 0:
+ values[v] = (
+ (1.0 - alpha)
+ *
+ colors[int(index)][k]
+ +
+ alpha * colors[int(index) + 1][k]
+ )
+ else:
+ values[v] = (
+ (1.0 - alpha)
+ *
+ colors[int(index)][k]
+ )
+
+ if flat:
+ palette.extend(
+ tuple(int(values[i]) for i in channels))
+ else:
+ palette.append(
+ tuple(int(values[i]) for i in channels))
+
+ return palette
+
+
+def get_max_level(filename):
+ max_value = 0
+ buffer_size = 4096
+ audio_file = audiolab.Sndfile(filename, 'r')
+ n_samples_left = audio_file.nframes
+
+ while n_samples_left:
+ to_read = min(buffer_size, n_samples_left)
+
+ try:
+ samples = audio_file.read_frames(to_read)
+ except RuntimeError:
+ # this can happen with a broken header
+ break
+
+ # convert to mono by selecting left channel only
+ if audio_file.channels > 1:
+ samples = samples[:,0]
+
+ max_value = max(max_value, numpy.abs(samples).max())
+
+ n_samples_left -= to_read
+
+ audio_file.close()
+
+ return max_value
+
+if __name__ == '__main__':
+ import sys
+ sys.argv[4] = int(sys.argv[4])
+ sys.argv[3] = tuple([int(i) for i in sys.argv[3].split('x')])
+
+ create_spectrogram_image(*sys.argv[1:])
diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py
new file mode 100644
index 00000000..84e6af7e
--- /dev/null
+++ b/mediagoblin/media_types/audio/transcoders.py
@@ -0,0 +1,237 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from mediagoblin.processing import BadMediaFail
+from mediagoblin.media_types.audio import audioprocessing
+
+
+_log = logging.getLogger(__name__)
+
+CPU_COUNT = 2 # Just assuming for now
+
+# IMPORT MULTIPROCESSING
+try:
+ import multiprocessing
+ try:
+ CPU_COUNT = multiprocessing.cpu_count()
+ except NotImplementedError:
+ _log.warning('multiprocessing.cpu_count not implemented!\n'
+ 'Assuming 2 CPU cores')
+except ImportError:
+ _log.warning('Could not import multiprocessing, assuming 2 CPU cores')
+
+# IMPORT GOBJECT
+try:
+ import gobject
+ gobject.threads_init()
+except ImportError:
+ raise Exception('gobject could not be found')
+
+# IMPORT PYGST
+try:
+ import pygst
+
+ # We won't settle for less. For now, this is an arbitrary limit
+ # as we have not tested with > 0.10
+ pygst.require('0.10')
+
+ import gst
+
+ import gst.extend.discoverer
+except ImportError:
+ raise Exception('gst/pygst >= 0.10 could not be imported')
+
+import numpy
+
+
+class AudioThumbnailer(object):
+ def __init__(self):
+ _log.info('Initializing {0}'.format(self.__class__.__name__))
+
+ def spectrogram(self, src, dst, **kw):
+ width = kw['width']
+ height = int(kw.get('height', float(width) * 0.3))
+ fft_size = kw.get('fft_size', 2048)
+ callback = kw.get('progress_callback')
+
+ processor = audioprocessing.AudioProcessor(
+ src,
+ fft_size,
+ numpy.hanning)
+
+ samples_per_pixel = processor.audio_file.nframes / float(width)
+
+ spectrogram = audioprocessing.SpectrogramImage(width, height, fft_size)
+
+ for x in range(width):
+ if callback and x % (width / 10) == 0:
+ callback((x * 100) / width)
+
+ seek_point = int(x * samples_per_pixel)
+
+ (spectral_centroid, db_spectrum) = processor.spectral_centroid(
+ seek_point)
+
+ spectrogram.draw_spectrum(x, db_spectrum)
+
+ if callback:
+ callback(100)
+
+ spectrogram.save(dst)
+
+ def thumbnail_spectrogram(self, src, dst, thumb_size):
+ '''
+ Takes a spectrogram and creates a thumbnail from it
+ '''
+ if not (type(thumb_size) == tuple and len(thumb_size) == 2):
+ raise Exception('thumb_size argument should be a tuple(width, height)')
+
+ im = Image.open(src)
+
+ im_w, im_h = [float(i) for i in im.size]
+ th_w, th_h = [float(i) for i in thumb_size]
+
+ wadsworth_position = im_w * 0.3
+
+ start_x = max((
+ wadsworth_position - ((im_h * (th_w / th_h)) / 2.0),
+ 0.0))
+
+ stop_x = start_x + (im_h * (th_w / th_h))
+
+ th = im.crop((
+ int(start_x), 0,
+ int(stop_x), int(im_h)))
+
+ if th.size[0] > th_w or th.size[1] > th_h:
+ th.thumbnail(thumb_size, Image.ANTIALIAS)
+
+ th.save(dst)
+
+
+class AudioTranscoder(object):
+ def __init__(self):
+ _log.info('Initializing {0}'.format(self.__class__.__name__))
+
+ # Instantiate MainLoop
+ self._loop = gobject.MainLoop()
+ self._failed = None
+
+ def discover(self, src):
+ self._src_path = src
+ _log.info('Discovering {0}'.format(src))
+ self._discovery_path = src
+
+ self._discoverer = gst.extend.discoverer.Discoverer(
+ self._discovery_path)
+ self._discoverer.connect('discovered', self.__on_discovered)
+ self._discoverer.discover()
+
+ self._loop.run() # Run MainLoop
+
+ if self._failed:
+ raise self._failed
+
+ # Once MainLoop has returned, return discovery data
+ return getattr(self, '_discovery_data', False)
+
+ def __on_discovered(self, data, is_media):
+ if not is_media:
+ self._failed = BadMediaFail()
+ _log.error('Could not discover {0}'.format(self._src_path))
+ self.halt()
+
+ _log.debug('Discovered: {0}'.format(data.__dict__))
+
+ self._discovery_data = data
+
+ # Gracefully shut down MainLoop
+ self.halt()
+
+ def transcode(self, src, dst, **kw):
+ _log.info('Transcoding {0} into {1}'.format(src, dst))
+ self._discovery_data = kw.get('data', self.discover(src))
+
+ self.__on_progress = kw.get('progress_callback')
+
+ quality = kw.get('quality', 0.3)
+
+ mux_string = kw.get(
+ 'mux_string',
+ 'vorbisenc quality={0} ! webmmux'.format(quality))
+
+ # Set up pipeline
+ self.pipeline = gst.parse_launch(
+ 'filesrc location="{src}" ! '
+ 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! '
+ 'audioconvert ! audio/x-raw-float,channels=2 ! '
+ '{mux_string} ! '
+ 'progressreport silent=true ! '
+ 'filesink location="{dst}"'.format(
+ src=src,
+ tolerance=80000000,
+ mux_string=mux_string,
+ dst=dst))
+
+ self.bus = self.pipeline.get_bus()
+ self.bus.add_signal_watch()
+ self.bus.connect('message', self.__on_bus_message)
+
+ self.pipeline.set_state(gst.STATE_PLAYING)
+
+ self._loop.run()
+
+ def __on_bus_message(self, bus, message):
+ _log.debug(message)
+
+ if (message.type == gst.MESSAGE_ELEMENT
+ and message.structure.get_name() == 'progress'):
+ data = dict(message.structure)
+
+ if self.__on_progress:
+ self.__on_progress(data.get('percent'))
+
+ _log.info('{0}% done...'.format(
+ data.get('percent')))
+ elif message.type == gst.MESSAGE_EOS:
+ _log.info('Done')
+ self.halt()
+
+ def halt(self):
+ if getattr(self, 'pipeline', False):
+ self.pipeline.set_state(gst.STATE_NULL)
+ del self.pipeline
+ _log.info('Quitting MainLoop gracefully...')
+ gobject.idle_add(self._loop.quit)
+
+if __name__ == '__main__':
+ import sys
+ logging.basicConfig()
+ _log.setLevel(logging.INFO)
+
+ #transcoder = AudioTranscoder()
+ #data = transcoder.discover(sys.argv[1])
+ #res = transcoder.transcode(*sys.argv[1:3])
+
+ thumbnailer = AudioThumbnailer()
+
+ thumbnailer.spectrogram(*sys.argv[1:], width=640)
diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py
new file mode 100644
index 00000000..5130ef48
--- /dev/null
+++ b/mediagoblin/media_types/image/__init__.py
@@ -0,0 +1,55 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+
+from mediagoblin.media_types import MediaManagerBase
+from mediagoblin.media_types.image.processing import process_image, \
+ sniff_handler
+
+
+class ImageMediaManager(MediaManagerBase):
+ human_readable = "Image"
+ processor = staticmethod(process_image)
+ sniff_handler = staticmethod(sniff_handler)
+ display_template = "mediagoblin/media_displays/image.html"
+ default_thumb = "images/media_thumbs/image.png"
+ accepted_extensions = ["jpg", "jpeg", "png", "gif", "tiff"]
+ media_fetch_order = [u'medium', u'original', u'thumb']
+
+ def get_original_date(self):
+ """
+ Get the original date and time from the EXIF information. Returns
+ either a datetime object or None (if anything goes wrong)
+ """
+ if not self.entry.media_data or not self.entry.media_data.exif_all:
+ return None
+
+ try:
+ # Try wrapped around all since exif_all might be none,
+ # EXIF DateTimeOriginal or printable might not exist
+ # or strptime might not be able to parse date correctly
+ exif_date = self.entry.media_data.exif_all[
+ 'EXIF DateTimeOriginal']['printable']
+ original_date = datetime.datetime.strptime(
+ exif_date,
+ '%Y:%m:%d %H:%M:%S')
+ return original_date
+ except (KeyError, ValueError):
+ return None
+
+
+MEDIA_MANAGER = ImageMediaManager
diff --git a/mediagoblin/media_types/image/migrations.py b/mediagoblin/media_types/image/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/image/migrations.py
@@ -0,0 +1,17 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/image/models.py b/mediagoblin/media_types/image/models.py
new file mode 100644
index 00000000..b2ea3960
--- /dev/null
+++ b/mediagoblin/media_types/image/models.py
@@ -0,0 +1,49 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.base import Base
+
+from sqlalchemy import (
+ Column, Integer, Float, ForeignKey)
+from sqlalchemy.orm import relationship, backref
+from mediagoblin.db.extratypes import JSONEncoded
+
+
+BACKREF_NAME = "image__media_data"
+
+
+class ImageData(Base):
+ __tablename__ = "image__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref(BACKREF_NAME, uselist=False,
+ cascade="all, delete-orphan"))
+
+ width = Column(Integer)
+ height = Column(Integer)
+ exif_all = Column(JSONEncoded)
+ gps_longitude = Column(Float)
+ gps_latitude = Column(Float)
+ gps_altitude = Column(Float)
+ gps_direction = Column(Float)
+
+
+DATA_MODEL = ImageData
+MODELS = [ImageData]
diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py
new file mode 100644
index 00000000..bc0ce3f8
--- /dev/null
+++ b/mediagoblin/media_types/image/processing.py
@@ -0,0 +1,180 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+import os
+import logging
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.processing import BadMediaFail, FilenameBuilder
+from mediagoblin.tools.exif import exif_fix_image_orientation, \
+ extract_exif, clean_exif, get_gps_data, get_useful, \
+ exif_image_needs_rotation
+
+_log = logging.getLogger(__name__)
+
+PIL_FILTERS = {
+ 'NEAREST': Image.NEAREST,
+ 'BILINEAR': Image.BILINEAR,
+ 'BICUBIC': Image.BICUBIC,
+ 'ANTIALIAS': Image.ANTIALIAS}
+
+
+def resize_image(proc_state, resized, keyname, target_name, new_size,
+ exif_tags, workdir):
+ """
+ Store a resized version of an image and return its pathname.
+
+ Arguments:
+ proc_state -- the processing state for the image to resize
+ resized -- an image from Image.open() of the original image being resized
+ keyname -- Under what key to save in the db.
+ target_name -- public file path for the new resized image
+ exif_tags -- EXIF data for the original image
+ workdir -- directory path for storing converted image files
+ new_size -- 2-tuple size for the resized image
+ """
+ config = mgg.global_config['media_type:mediagoblin.media_types.image']
+
+ resized = exif_fix_image_orientation(resized, exif_tags) # Fix orientation
+
+ filter_config = config['resize_filter']
+ try:
+ resize_filter = PIL_FILTERS[filter_config.upper()]
+ except KeyError:
+ raise Exception('Filter "{0}" not found, choose one of {1}'.format(
+ unicode(filter_config),
+ u', '.join(PIL_FILTERS.keys())))
+
+ resized.thumbnail(new_size, resize_filter)
+
+ # Copy the new file to the conversion subdir, then remotely.
+ tmp_resized_filename = os.path.join(workdir, target_name)
+ with file(tmp_resized_filename, 'w') as resized_file:
+ resized.save(resized_file, quality=config['quality'])
+ proc_state.store_public(keyname, tmp_resized_filename, target_name)
+
+
+def resize_tool(proc_state, force, keyname, target_name,
+ conversions_subdir, exif_tags):
+ # filename -- the filename of the original image being resized
+ filename = proc_state.get_queued_filename()
+ max_width = mgg.global_config['media:' + keyname]['max_width']
+ max_height = mgg.global_config['media:' + keyname]['max_height']
+ # If the size of the original file exceeds the specified size for the desized
+ # file, a target_name file is created and later associated with the media
+ # entry.
+ # Also created if the file needs rotation, or if forced.
+ try:
+ im = Image.open(filename)
+ except IOError:
+ raise BadMediaFail()
+ if force \
+ or im.size[0] > max_width \
+ or im.size[1] > max_height \
+ or exif_image_needs_rotation(exif_tags):
+ resize_image(
+ proc_state, im, unicode(keyname), target_name,
+ (max_width, max_height),
+ exif_tags, conversions_subdir)
+
+
+SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg']
+
+
+def sniff_handler(media_file, **kw):
+ if kw.get('media') is not None: # That's a double negative!
+ name, ext = os.path.splitext(kw['media'].filename)
+ clean_ext = ext[1:].lower() # Strip the . from ext and make lowercase
+
+ if clean_ext in SUPPORTED_FILETYPES:
+ _log.info('Found file extension in supported filetypes')
+ return True
+ else:
+ _log.debug('Media present, extension not found in {0}'.format(
+ SUPPORTED_FILETYPES))
+ else:
+ _log.warning('Need additional information (keyword argument \'media\')'
+ ' to be able to handle sniffing')
+
+ return False
+
+
+def process_image(proc_state):
+ """Code to process an image. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
+ entry = proc_state.entry
+ workbench = proc_state.workbench
+
+ # Conversions subdirectory to avoid collisions
+ conversions_subdir = os.path.join(
+ workbench.dir, 'conversions')
+ os.mkdir(conversions_subdir)
+
+ queued_filename = proc_state.get_queued_filename()
+ name_builder = FilenameBuilder(queued_filename)
+
+ # EXIF extraction
+ exif_tags = extract_exif(queued_filename)
+ gps_data = get_gps_data(exif_tags)
+
+ # Always create a small thumbnail
+ resize_tool(proc_state, True, 'thumb',
+ name_builder.fill('{basename}.thumbnail{ext}'),
+ conversions_subdir, exif_tags)
+
+ # Possibly create a medium
+ resize_tool(proc_state, False, 'medium',
+ name_builder.fill('{basename}.medium{ext}'),
+ conversions_subdir, exif_tags)
+
+ # Copy our queued local workbench to its final destination
+ proc_state.copy_original(name_builder.fill('{basename}{ext}'))
+
+ # Remove queued media file from storage and database
+ proc_state.delete_queue_file()
+
+ # Insert exif data into database
+ exif_all = clean_exif(exif_tags)
+
+ if len(exif_all):
+ entry.media_data_init(exif_all=exif_all)
+
+ if len(gps_data):
+ for key in list(gps_data.keys()):
+ gps_data['gps_' + key] = gps_data.pop(key)
+ entry.media_data_init(**gps_data)
+
+
+if __name__ == '__main__':
+ import sys
+ import pprint
+
+ pp = pprint.PrettyPrinter()
+
+ result = extract_exif(sys.argv[1])
+ gps = get_gps_data(result)
+ clean = clean_exif(result)
+ useful = get_useful(clean)
+
+ print pp.pprint(
+ clean)
diff --git a/mediagoblin/media_types/pdf/__init__.py b/mediagoblin/media_types/pdf/__init__.py
new file mode 100644
index 00000000..f0ba7867
--- /dev/null
+++ b/mediagoblin/media_types/pdf/__init__.py
@@ -0,0 +1,31 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.media_types import MediaManagerBase
+from mediagoblin.media_types.pdf.processing import process_pdf, \
+ sniff_handler
+
+
+class PDFMediaManager(MediaManagerBase):
+ human_readable = "PDF"
+ processor = staticmethod(process_pdf)
+ sniff_handler = staticmethod(sniff_handler)
+ display_template = "mediagoblin/media_displays/pdf.html"
+ default_thumb = "images/media_thumbs/pdf.jpg"
+ accepted_extensions = ["pdf"]
+
+
+MEDIA_MANAGER = PDFMediaManager
diff --git a/mediagoblin/media_types/pdf/migrations.py b/mediagoblin/media_types/pdf/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/pdf/migrations.py
@@ -0,0 +1,17 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/pdf/models.py b/mediagoblin/media_types/pdf/models.py
new file mode 100644
index 00000000..c39262d1
--- /dev/null
+++ b/mediagoblin/media_types/pdf/models.py
@@ -0,0 +1,58 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.base import Base
+
+from sqlalchemy import (
+ Column, Float, Integer, String, DateTime, ForeignKey)
+from sqlalchemy.orm import relationship, backref
+
+
+BACKREF_NAME = "pdf__media_data"
+
+
+class PdfData(Base):
+ __tablename__ = "pdf__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref(BACKREF_NAME, uselist=False,
+ cascade="all, delete-orphan"))
+ pages = Column(Integer)
+
+ # These are taken from what pdfinfo can do, perhaps others make sense too
+ pdf_author = Column(String)
+ pdf_title = Column(String)
+ # note on keywords: this is the pdf parsed string, it should be considered a cached
+ # value like the rest of these values, since they can be deduced at query time / client
+ # side too.
+ pdf_keywords = Column(String)
+ pdf_creator = Column(String)
+ pdf_producer = Column(String)
+ pdf_creation_date = Column(DateTime)
+ pdf_modified_date = Column(DateTime)
+ pdf_version_major = Column(Integer)
+ pdf_version_minor = Column(Integer)
+ pdf_page_size_width = Column(Float) # unit: pts
+ pdf_page_size_height = Column(Float)
+ pdf_pages = Column(Integer)
+
+
+DATA_MODEL = PdfData
+MODELS = [PdfData]
diff --git a/mediagoblin/media_types/pdf/processing.py b/mediagoblin/media_types/pdf/processing.py
new file mode 100644
index 00000000..49742fd7
--- /dev/null
+++ b/mediagoblin/media_types/pdf/processing.py
@@ -0,0 +1,277 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+import logging
+import dateutil.parser
+from subprocess import PIPE, Popen
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.processing import (create_pub_filepath,
+ FilenameBuilder, BadMediaFail)
+from mediagoblin.tools.translate import fake_ugettext_passthrough as _
+
+_log = logging.getLogger(__name__)
+
+# TODO - cache (memoize) util
+
+# This is a list created via uniconv --show and hand removing some types that
+# we already support via other media types better.
+unoconv_supported = [
+ 'bib', # - BibTeX [.bib]
+ #bmp - Windows Bitmap [.bmp]
+ 'csv', # - Text CSV [.csv]
+ 'dbf', # - dBASE [.dbf]
+ 'dif', # - Data Interchange Format [.dif]
+ 'doc6', # - Microsoft Word 6.0 [.doc]
+ 'doc95', # - Microsoft Word 95 [.doc]
+ 'docbook', # - DocBook [.xml]
+ 'doc', # - Microsoft Word 97/2000/XP [.doc]
+ 'docx7', # - Microsoft Office Open XML [.docx]
+ 'docx', # - Microsoft Office Open XML [.docx]
+ #emf - Enhanced Metafile [.emf]
+ 'eps', # - Encapsulated PostScript [.eps]
+ 'fodp', # - OpenDocument Presentation (Flat XML) [.fodp]
+ 'fods', # - OpenDocument Spreadsheet (Flat XML) [.fods]
+ 'fodt', # - OpenDocument Text (Flat XML) [.fodt]
+ #gif - Graphics Interchange Format [.gif]
+ 'html', # - HTML Document (OpenOffice.org Writer) [.html]
+ #jpg - Joint Photographic Experts Group [.jpg]
+ 'latex', # - LaTeX 2e [.ltx]
+ 'mediawiki', # - MediaWiki [.txt]
+ 'met', # - OS/2 Metafile [.met]
+ 'odd', # - OpenDocument Drawing [.odd]
+ 'odg', # - ODF Drawing (Impress) [.odg]
+ 'odp', # - ODF Presentation [.odp]
+ 'ods', # - ODF Spreadsheet [.ods]
+ 'odt', # - ODF Text Document [.odt]
+ 'ooxml', # - Microsoft Office Open XML [.xml]
+ 'otg', # - OpenDocument Drawing Template [.otg]
+ 'otp', # - ODF Presentation Template [.otp]
+ 'ots', # - ODF Spreadsheet Template [.ots]
+ 'ott', # - Open Document Text [.ott]
+ #pbm - Portable Bitmap [.pbm]
+ #pct - Mac Pict [.pct]
+ 'pdb', # - AportisDoc (Palm) [.pdb]
+ #pdf - Portable Document Format [.pdf]
+ #pgm - Portable Graymap [.pgm]
+ #png - Portable Network Graphic [.png]
+ 'pot', # - Microsoft PowerPoint 97/2000/XP Template [.pot]
+ 'potm', # - Microsoft PowerPoint 2007/2010 XML Template [.potm]
+ #ppm - Portable Pixelmap [.ppm]
+ 'pps', # - Microsoft PowerPoint 97/2000/XP (Autoplay) [.pps]
+ 'ppt', # - Microsoft PowerPoint 97/2000/XP [.ppt]
+ 'pptx', # - Microsoft PowerPoint 2007/2010 XML [.pptx]
+ 'psw', # - Pocket Word [.psw]
+ 'pwp', # - PlaceWare [.pwp]
+ 'pxl', # - Pocket Excel [.pxl]
+ #ras - Sun Raster Image [.ras]
+ 'rtf', # - Rich Text Format [.rtf]
+ 'sda', # - StarDraw 5.0 (OpenOffice.org Impress) [.sda]
+ 'sdc3', # - StarCalc 3.0 [.sdc]
+ 'sdc4', # - StarCalc 4.0 [.sdc]
+ 'sdc', # - StarCalc 5.0 [.sdc]
+ 'sdd3', # - StarDraw 3.0 (OpenOffice.org Impress) [.sdd]
+ 'sdd4', # - StarImpress 4.0 [.sdd]
+ 'sdd', # - StarImpress 5.0 [.sdd]
+ 'sdw3', # - StarWriter 3.0 [.sdw]
+ 'sdw4', # - StarWriter 4.0 [.sdw]
+ 'sdw', # - StarWriter 5.0 [.sdw]
+ 'slk', # - SYLK [.slk]
+ 'stc', # - OpenOffice.org 1.0 Spreadsheet Template [.stc]
+ 'std', # - OpenOffice.org 1.0 Drawing Template [.std]
+ 'sti', # - OpenOffice.org 1.0 Presentation Template [.sti]
+ 'stw', # - Open Office.org 1.0 Text Document Template [.stw]
+ #svg - Scalable Vector Graphics [.svg]
+ 'svm', # - StarView Metafile [.svm]
+ 'swf', # - Macromedia Flash (SWF) [.swf]
+ 'sxc', # - OpenOffice.org 1.0 Spreadsheet [.sxc]
+ 'sxd3', # - StarDraw 3.0 [.sxd]
+ 'sxd5', # - StarDraw 5.0 [.sxd]
+ 'sxd', # - OpenOffice.org 1.0 Drawing (OpenOffice.org Impress) [.sxd]
+ 'sxi', # - OpenOffice.org 1.0 Presentation [.sxi]
+ 'sxw', # - Open Office.org 1.0 Text Document [.sxw]
+ #text - Text Encoded [.txt]
+ #tiff - Tagged Image File Format [.tiff]
+ #txt - Text [.txt]
+ 'uop', # - Unified Office Format presentation [.uop]
+ 'uos', # - Unified Office Format spreadsheet [.uos]
+ 'uot', # - Unified Office Format text [.uot]
+ 'vor3', # - StarDraw 3.0 Template (OpenOffice.org Impress) [.vor]
+ 'vor4', # - StarWriter 4.0 Template [.vor]
+ 'vor5', # - StarDraw 5.0 Template (OpenOffice.org Impress) [.vor]
+ 'vor', # - StarCalc 5.0 Template [.vor]
+ #wmf - Windows Metafile [.wmf]
+ 'xhtml', # - XHTML Document [.html]
+ 'xls5', # - Microsoft Excel 5.0 [.xls]
+ 'xls95', # - Microsoft Excel 95 [.xls]
+ 'xls', # - Microsoft Excel 97/2000/XP [.xls]
+ 'xlt5', # - Microsoft Excel 5.0 Template [.xlt]
+ 'xlt95', # - Microsoft Excel 95 Template [.xlt]
+ 'xlt', # - Microsoft Excel 97/2000/XP Template [.xlt]
+ #xpm - X PixMap [.xpm]
+]
+
+def is_unoconv_working():
+ # TODO: must have libreoffice-headless installed too, need to check for it
+ unoconv = where('unoconv')
+ if not unoconv:
+ return False
+ try:
+ proc = Popen([unoconv, '--show'], stderr=PIPE)
+ output = proc.stderr.read()
+ except OSError, e:
+ _log.warn(_('unoconv failing to run, check log file'))
+ return False
+ if 'ERROR' in output:
+ return False
+ return True
+
+def supported_extensions(cache=[None]):
+ if cache[0] == None:
+ cache[0] = 'pdf'
+ if is_unoconv_working():
+ cache.extend(unoconv_supported)
+ return cache
+
+def where(name):
+ for p in os.environ['PATH'].split(os.pathsep):
+ fullpath = os.path.join(p, name)
+ if os.path.exists(fullpath):
+ return fullpath
+ return None
+
+def check_prerequisites():
+ if not where('pdfinfo'):
+ _log.warn('missing pdfinfo')
+ return False
+ if not where('pdftocairo'):
+ _log.warn('missing pdfcairo')
+ return False
+ return True
+
+def sniff_handler(media_file, **kw):
+ if not check_prerequisites():
+ return False
+ if kw.get('media') is not None:
+ name, ext = os.path.splitext(kw['media'].filename)
+ clean_ext = ext[1:].lower()
+
+ if clean_ext in supported_extensions():
+ return True
+
+ return False
+
+def create_pdf_thumb(original, thumb_filename, width, height):
+ # Note: pdftocairo adds '.png', remove it
+ thumb_filename = thumb_filename[:-4]
+ executable = where('pdftocairo')
+ args = [executable, '-scale-to', str(min(width, height)),
+ '-singlefile', '-png', original, thumb_filename]
+ _log.debug('calling {0}'.format(repr(' '.join(args))))
+ Popen(executable=executable, args=args).wait()
+
+def pdf_info(original):
+ """
+ Extract dictionary of pdf information. This could use a library instead
+ of a process.
+
+ Note: I'm assuming pdfinfo output is sanitized (integers where integers are
+ expected, etc.) - if this is wrong then an exception will be raised and caught
+ leading to the dreaded error page. It seems a safe assumption.
+ """
+ ret_dict = {}
+ pdfinfo = where('pdfinfo')
+ try:
+ proc = Popen(executable=pdfinfo,
+ args=[pdfinfo, original], stdout=PIPE)
+ lines = proc.stdout.readlines()
+ except OSError:
+ _log.debug('pdfinfo could not read the pdf file.')
+ raise BadMediaFail()
+
+ info_dict = dict([[part.strip() for part in l.strip().split(':', 1)]
+ for l in lines if ':' in l])
+
+ for date_key in [('pdf_mod_date', 'ModDate'),
+ ('pdf_creation_date', 'CreationDate')]:
+ if date_key in info_dict:
+ ret_dict[date_key] = dateutil.parser.parse(info_dict[date_key])
+ for db_key, int_key in [('pdf_pages', 'Pages')]:
+ if int_key in info_dict:
+ ret_dict[db_key] = int(info_dict[int_key])
+
+ # parse 'PageSize' field: 595 x 842 pts (A4)
+ page_size_parts = info_dict['Page size'].split()
+ ret_dict['pdf_page_size_width'] = float(page_size_parts[0])
+ ret_dict['pdf_page_size_height'] = float(page_size_parts[2])
+
+ for db_key, str_key in [('pdf_keywords', 'Keywords'),
+ ('pdf_creator', 'Creator'), ('pdf_producer', 'Producer'),
+ ('pdf_author', 'Author'), ('pdf_title', 'Title')]:
+ ret_dict[db_key] = info_dict.get(str_key, None)
+ ret_dict['pdf_version_major'], ret_dict['pdf_version_minor'] = \
+ map(int, info_dict['PDF version'].split('.'))
+
+ return ret_dict
+
+def process_pdf(proc_state):
+ """Code to process a pdf file. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
+ entry = proc_state.entry
+ workbench = proc_state.workbench
+
+ queued_filename = proc_state.get_queued_filename()
+ name_builder = FilenameBuilder(queued_filename)
+
+ # Copy our queued local workbench to its final destination
+ original_dest = name_builder.fill('{basename}{ext}')
+ proc_state.copy_original(original_dest)
+
+ # Create a pdf if this is a different doc, store pdf for viewer
+ ext = queued_filename.rsplit('.', 1)[-1].lower()
+ if ext == 'pdf':
+ pdf_filename = queued_filename
+ else:
+ pdf_filename = queued_filename.rsplit('.', 1)[0] + '.pdf'
+ unoconv = where('unoconv')
+ call(executable=unoconv,
+ args=[unoconv, '-v', '-f', 'pdf', queued_filename])
+ if not os.path.exists(pdf_filename):
+ _log.debug('unoconv failed to convert file to pdf')
+ raise BadMediaFail()
+ proc_state.store_public(keyname=u'pdf', local_file=pdf_filename)
+
+ pdf_info_dict = pdf_info(pdf_filename)
+
+ for name, width, height in [
+ (u'thumb', mgg.global_config['media:thumb']['max_width'],
+ mgg.global_config['media:thumb']['max_height']),
+ (u'medium', mgg.global_config['media:medium']['max_width'],
+ mgg.global_config['media:medium']['max_height']),
+ ]:
+ filename = name_builder.fill('{basename}.%s.png' % name)
+ path = workbench.joinpath(filename)
+ create_pdf_thumb(pdf_filename, path, width, height)
+ assert(os.path.exists(path))
+ proc_state.store_public(keyname=name, local_file=path)
+
+ proc_state.delete_queue_file()
+
+ entry.media_data_init(**pdf_info_dict)
+ entry.save()
diff --git a/mediagoblin/media_types/stl/__init__.py b/mediagoblin/media_types/stl/__init__.py
new file mode 100644
index 00000000..6ae8a8b9
--- /dev/null
+++ b/mediagoblin/media_types/stl/__init__.py
@@ -0,0 +1,31 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.media_types import MediaManagerBase
+from mediagoblin.media_types.stl.processing import process_stl, \
+ sniff_handler
+
+
+class STLMediaManager(MediaManagerBase):
+ human_readable = "stereo lithographics"
+ processor = staticmethod(process_stl)
+ sniff_handler = staticmethod(sniff_handler)
+ display_template = "mediagoblin/media_displays/stl.html"
+ default_thumb = "images/media_thumbs/video.jpg"
+ accepted_extensions = ["obj", "stl"]
+
+
+MEDIA_MANAGER = STLMediaManager
diff --git a/mediagoblin/media_types/stl/assets/blender_render.blend b/mediagoblin/media_types/stl/assets/blender_render.blend
new file mode 100644
index 00000000..dd356a06
--- /dev/null
+++ b/mediagoblin/media_types/stl/assets/blender_render.blend
Binary files differ
diff --git a/mediagoblin/media_types/stl/assets/blender_render.py b/mediagoblin/media_types/stl/assets/blender_render.py
new file mode 100644
index 00000000..99d5fa31
--- /dev/null
+++ b/mediagoblin/media_types/stl/assets/blender_render.py
@@ -0,0 +1,84 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import bpy, json, os
+
+
+try:
+ CONFIG = json.loads(os.environ["RENDER_SETUP"])
+ MODEL_EXT = CONFIG["model_ext"]
+ MODEL_PATH = CONFIG["model_path"]
+ CAMERA_COORD = CONFIG["camera_coord"]
+ CAMERA_FOCUS = CONFIG["camera_focus"]
+ CAMERA_CLIP = CONFIG["camera_clip"]
+ CAMERA_TYPE = CONFIG["projection"]
+ CAMERA_ORTHO = CONFIG["greatest"] * 1.5
+ RENDER_WIDTH = CONFIG["width"]
+ RENDER_HEIGHT = CONFIG["height"]
+ RENDER_FILE = CONFIG["out_file"]
+except KeyError:
+ print("Failed to load RENDER_SETUP environment variable.")
+ exit(1)
+
+
+# add and setup camera
+bpy.ops.object.camera_add(view_align=False, enter_editmode=False,
+ location = CAMERA_COORD)
+camera_ob = bpy.data.objects[0]
+camera = bpy.data.cameras[0]
+camera.clip_end = CAMERA_CLIP
+camera.ortho_scale = CAMERA_ORTHO
+camera.type = CAMERA_TYPE
+
+
+
+# add an empty for focusing the camera
+bpy.ops.object.add(location=CAMERA_FOCUS)
+target = bpy.data.objects[1]
+bpy.ops.object.select_all(action="SELECT")
+bpy.ops.object.track_set(type="TRACKTO")
+bpy.ops.object.select_all(action="DESELECT")
+
+
+if MODEL_EXT == 'stl':
+ # import an stl model
+ bpy.ops.import_mesh.stl(filepath=MODEL_PATH)
+
+elif MODEL_EXT == 'obj':
+ # import an obj model
+ bpy.ops.import_scene.obj(
+ filepath=MODEL_PATH,
+ use_smooth_groups=False,
+ use_image_search=False,
+ axis_forward="Y",
+ axis_up="Z")
+
+
+# rotate the imported objects with meshes in the scene
+if CAMERA_TYPE == "PERSP":
+ for obj in bpy.data.objects[2:]:
+ obj.rotation_euler[2]=-.3
+
+
+# attempt to render
+scene = bpy.data.scenes.values()[0]
+scene.camera = camera_ob
+scene.render.filepath = RENDER_FILE
+scene.render.resolution_x = RENDER_WIDTH
+scene.render.resolution_y = RENDER_HEIGHT
+scene.render.resolution_percentage = 100
+bpy.ops.render.render(write_still=True)
diff --git a/mediagoblin/media_types/stl/migrations.py b/mediagoblin/media_types/stl/migrations.py
new file mode 100644
index 00000000..f54c23ea
--- /dev/null
+++ b/mediagoblin/media_types/stl/migrations.py
@@ -0,0 +1,17 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+MIGRATIONS = {}
diff --git a/mediagoblin/media_types/stl/model_loader.py b/mediagoblin/media_types/stl/model_loader.py
new file mode 100644
index 00000000..88f19314
--- /dev/null
+++ b/mediagoblin/media_types/stl/model_loader.py
@@ -0,0 +1,137 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import struct
+
+
+class ThreeDeeParseError(Exception):
+ pass
+
+
+class ThreeDee():
+ """
+ 3D model parser base class. Derrived classes are used for basic
+ analysis of 3D models, and are not intended to be used for 3D
+ rendering.
+ """
+
+ def __init__(self, fileob):
+ self.verts = []
+ self.average = [0, 0, 0]
+ self.min = [None, None, None]
+ self.max = [None, None, None]
+ self.width = 0 # x axis
+ self.depth = 0 # y axis
+ self.height = 0 # z axis
+
+ self.load(fileob)
+ if not len(self.verts):
+ raise ThreeDeeParseError("Empty model.")
+
+ for vector in self.verts:
+ for i in range(3):
+ num = vector[i]
+ self.average[i] += num
+ if not self.min[i]:
+ self.min[i] = num
+ self.max[i] = num
+ else:
+ if self.min[i] > num:
+ self.min[i] = num
+ if self.max[i] < num:
+ self.max[i] = num
+
+ for i in range(3):
+ self.average[i]/=len(self.verts)
+
+ self.width = abs(self.min[0] - self.max[0])
+ self.depth = abs(self.min[1] - self.max[1])
+ self.height = abs(self.min[2] - self.max[2])
+
+
+ def load(self, fileob):
+ """Override this method in your subclass."""
+ pass
+
+
+class ObjModel(ThreeDee):
+ """
+ Parser for textureless wavefront obj files. File format
+ reference: http://en.wikipedia.org/wiki/Wavefront_.obj_file
+ """
+
+ def __vector(self, line, expected=3):
+ nums = map(float, line.strip().split(" ")[1:])
+ return tuple(nums[:expected])
+
+ def load(self, fileob):
+ for line in fileob:
+ line = line.strip()
+ if line[0] == "v":
+ self.verts.append(self.__vector(line))
+
+
+class BinaryStlModel(ThreeDee):
+ """
+ Parser for ascii-encoded stl files. File format reference:
+ http://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL
+ """
+
+ def load(self, fileob):
+ fileob.seek(80) # skip the header
+ count = struct.unpack("<I", fileob.read(4))[0]
+ for i in range(count):
+ fileob.read(12) # skip the normal vector
+ for v in range(3):
+ self.verts.append(struct.unpack("<3f", fileob.read(12)))
+ fileob.read(2) # skip the attribute bytes
+
+
+def auto_detect(fileob, hint):
+ """
+ Attempt to divine which parser to use to divine information about
+ the model / verify the file."""
+
+ if hint == "obj" or not hint:
+ try:
+ return ObjModel(fileob)
+ except ThreeDeeParseError:
+ pass
+
+ if hint == "stl" or not hint:
+ try:
+ # HACK Ascii formatted stls are similar enough to obj
+ # files that we can just use the same parser for both.
+ # Isn't that something?
+ return ObjModel(fileob)
+ except ThreeDeeParseError:
+ pass
+ except ValueError:
+ pass
+ except IndexError:
+ pass
+ try:
+ # It is pretty important that the binary stl model loader
+ # is tried second, because its possible for it to parse
+ # total garbage from plaintext =)
+ return BinaryStlModel(fileob)
+ except ThreeDeeParseError:
+ pass
+ except MemoryError:
+ pass
+
+ raise ThreeDeeParseError("Could not successfully parse the model :(")
diff --git a/mediagoblin/media_types/stl/models.py b/mediagoblin/media_types/stl/models.py
new file mode 100644
index 00000000..ff50e9c0
--- /dev/null
+++ b/mediagoblin/media_types/stl/models.py
@@ -0,0 +1,50 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.base import Base
+
+from sqlalchemy import (
+ Column, Integer, Float, String, ForeignKey)
+from sqlalchemy.orm import relationship, backref
+
+
+BACKREF_NAME = "stl__media_data"
+
+
+class StlData(Base):
+ __tablename__ = "stl__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref(BACKREF_NAME, uselist=False,
+ cascade="all, delete-orphan"))
+
+ center_x = Column(Float)
+ center_y = Column(Float)
+ center_z = Column(Float)
+
+ width = Column(Float)
+ height = Column(Float)
+ depth = Column(Float)
+
+ file_type = Column(String)
+
+
+DATA_MODEL = StlData
+MODELS = [StlData]
diff --git a/mediagoblin/media_types/stl/processing.py b/mediagoblin/media_types/stl/processing.py
new file mode 100644
index 00000000..49382495
--- /dev/null
+++ b/mediagoblin/media_types/stl/processing.py
@@ -0,0 +1,193 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import json
+import logging
+import subprocess
+import pkg_resources
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.processing import create_pub_filepath, \
+ FilenameBuilder
+
+from mediagoblin.media_types.stl import model_loader
+
+
+_log = logging.getLogger(__name__)
+SUPPORTED_FILETYPES = ['stl', 'obj']
+
+BLEND_FILE = pkg_resources.resource_filename(
+ 'mediagoblin.media_types.stl',
+ os.path.join(
+ 'assets',
+ 'blender_render.blend'))
+BLEND_SCRIPT = pkg_resources.resource_filename(
+ 'mediagoblin.media_types.stl',
+ os.path.join(
+ 'assets',
+ 'blender_render.py'))
+
+
+def sniff_handler(media_file, **kw):
+ if kw.get('media') is not None:
+ name, ext = os.path.splitext(kw['media'].filename)
+ clean_ext = ext[1:].lower()
+
+ if clean_ext in SUPPORTED_FILETYPES:
+ _log.info('Found file extension in supported filetypes')
+ return True
+ else:
+ _log.debug('Media present, extension not found in {0}'.format(
+ SUPPORTED_FILETYPES))
+ else:
+ _log.warning('Need additional information (keyword argument \'media\')'
+ ' to be able to handle sniffing')
+
+ return False
+
+
+def blender_render(config):
+ """
+ Called to prerender a model.
+ """
+ env = {"RENDER_SETUP" : json.dumps(config), "DISPLAY":":0"}
+ subprocess.call(
+ ["blender",
+ "-b", BLEND_FILE,
+ "-F", "JPEG",
+ "-P", BLEND_SCRIPT],
+ env=env)
+
+
+def process_stl(proc_state):
+ """Code to process an stl or obj model. Will be run by celery.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
+ entry = proc_state.entry
+ workbench = proc_state.workbench
+
+ queued_filepath = entry.queued_media_file
+ queued_filename = workbench.localized_file(
+ mgg.queue_store, queued_filepath, 'source')
+ name_builder = FilenameBuilder(queued_filename)
+
+ ext = queued_filename.lower().strip()[-4:]
+ if ext.startswith("."):
+ ext = ext[1:]
+ else:
+ ext = None
+
+ # Attempt to parse the model file and divine some useful
+ # information about it.
+ with open(queued_filename, 'rb') as model_file:
+ model = model_loader.auto_detect(model_file, ext)
+
+ # generate preview images
+ greatest = [model.width, model.height, model.depth]
+ greatest.sort()
+ greatest = greatest[-1]
+
+ def snap(name, camera, width=640, height=640, project="ORTHO"):
+ filename = name_builder.fill(name)
+ workbench_path = workbench.joinpath(filename)
+ shot = {
+ "model_path": queued_filename,
+ "model_ext": ext,
+ "camera_coord": camera,
+ "camera_focus": model.average,
+ "camera_clip": greatest*10,
+ "greatest": greatest,
+ "projection": project,
+ "width": width,
+ "height": height,
+ "out_file": workbench_path,
+ }
+ blender_render(shot)
+
+ # make sure the image rendered to the workbench path
+ assert os.path.exists(workbench_path)
+
+ # copy it up!
+ with open(workbench_path, 'rb') as rendered_file:
+ public_path = create_pub_filepath(entry, filename)
+
+ with mgg.public_store.get_file(public_path, "wb") as public_file:
+ public_file.write(rendered_file.read())
+
+ return public_path
+
+ thumb_path = snap(
+ "{basename}.thumb.jpg",
+ [0, greatest*-1.5, greatest],
+ mgg.global_config['media:thumb']['max_width'],
+ mgg.global_config['media:thumb']['max_height'],
+ project="PERSP")
+
+ perspective_path = snap(
+ "{basename}.perspective.jpg",
+ [0, greatest*-1.5, greatest], project="PERSP")
+
+ topview_path = snap(
+ "{basename}.top.jpg",
+ [model.average[0], model.average[1], greatest*2])
+
+ frontview_path = snap(
+ "{basename}.front.jpg",
+ [model.average[0], greatest*-2, model.average[2]])
+
+ sideview_path = snap(
+ "{basename}.side.jpg",
+ [greatest*-2, model.average[1], model.average[2]])
+
+ ## Save the public file stuffs
+ model_filepath = create_pub_filepath(
+ entry, name_builder.fill('{basename}{ext}'))
+
+ with mgg.public_store.get_file(model_filepath, 'wb') as model_file:
+ with open(queued_filename, 'rb') as queued_file:
+ model_file.write(queued_file.read())
+
+ # Remove queued media file from storage and database.
+ # queued_filepath is in the task_id directory which should
+ # be removed too, but fail if the directory is not empty to be on
+ # the super-safe side.
+ mgg.queue_store.delete_file(queued_filepath) # rm file
+ mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir
+ entry.queued_media_file = []
+
+ # Insert media file information into database
+ media_files_dict = entry.setdefault('media_files', {})
+ media_files_dict[u'original'] = model_filepath
+ media_files_dict[u'thumb'] = thumb_path
+ media_files_dict[u'perspective'] = perspective_path
+ media_files_dict[u'top'] = topview_path
+ media_files_dict[u'side'] = sideview_path
+ media_files_dict[u'front'] = frontview_path
+
+ # Put model dimensions into the database
+ dimensions = {
+ "center_x" : model.average[0],
+ "center_y" : model.average[1],
+ "center_z" : model.average[2],
+ "width" : model.width,
+ "height" : model.height,
+ "depth" : model.depth,
+ "file_type" : ext,
+ }
+ entry.media_data_init(**dimensions)
diff --git a/mediagoblin/media_types/video/__init__.py b/mediagoblin/media_types/video/__init__.py
new file mode 100644
index 00000000..569cf11a
--- /dev/null
+++ b/mediagoblin/media_types/video/__init__.py
@@ -0,0 +1,36 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.media_types import MediaManagerBase
+from mediagoblin.media_types.video.processing import process_video, \
+ sniff_handler
+
+
+class VideoMediaManager(MediaManagerBase):
+ human_readable = "Video"
+ processor = staticmethod(process_video)
+ sniff_handler = staticmethod(sniff_handler)
+ display_template = "mediagoblin/media_displays/video.html"
+ default_thumb = "images/media_thumbs/video.jpg"
+ accepted_extensions = [
+ "mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "m4v"]
+
+ # Used by the media_entry.get_display_media method
+ media_fetch_order = [u'webm_640', u'original']
+ default_webm_type = 'video/webm; codecs="vp8, vorbis"'
+
+
+MEDIA_MANAGER = VideoMediaManager
diff --git a/mediagoblin/media_types/video/devices/web-advanced.json b/mediagoblin/media_types/video/devices/web-advanced.json
new file mode 100644
index 00000000..ce1d22ff
--- /dev/null
+++ b/mediagoblin/media_types/video/devices/web-advanced.json
@@ -0,0 +1,505 @@
+{
+ "make": "Generic",
+ "model": "Web Browser (Advanced)",
+ "description": "Media for World Wide Web",
+ "version": "0.1",
+ "author": {
+ "name": "Dionisio E Alonso",
+ "email": "dealonso@gmail.com"
+ },
+ "icon": "file://web.svg",
+ "default": "WebM 480p",
+ "presets": [
+ {
+ "name": "H.264 720p",
+ "extension": "mp4",
+ "container": "qtmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "qtmux",
+ "width": [
+ 960, 1280
+ ],
+ "height": [
+ 720, 720
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "qtmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ },
+ {
+ "name": "WebM 720p",
+ "extension": "webm",
+ "container": "webmmux",
+ "icon": "file://web-webm.svg",
+ "vcodec": {
+ "name": "vp8enc",
+ "container": "webmmux",
+ "width": [
+ 960, 1280
+ ],
+ "height": [
+ 720, 720
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "quality=5.75 threads=%(threads)s speed=2"
+ ]
+ },
+ "acodec": {
+ "name": "vorbisenc",
+ "container": "webmmux",
+ "width": [
+ 8, 32
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "quality=0.3"
+ ]
+ }
+ },
+ {
+ "name": "Flash Video 720p",
+ "extension": "flv",
+ "icon": "file://web-flv.png",
+ "container": "flvmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "flvmux",
+ "width": [
+ 960, 1280
+ ],
+ "height": [
+ 720, 720
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "flvmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ },
+
+ {
+ "name": "H.264 576p",
+ "extension": "mp4",
+ "container": "qtmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "qtmux",
+ "width": [
+ 768, 1024
+ ],
+ "height": [
+ 576, 576
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "qtmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ },
+ {
+ "name": "WebM 576p",
+ "extension": "webm",
+ "container": "webmmux",
+ "icon": "file://web-webm.svg",
+ "vcodec": {
+ "name": "vp8enc",
+ "container": "webmmux",
+ "width": [
+ 768, 1024
+ ],
+ "height": [
+ 576, 576
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "quality=5.75 threads=%(threads)s speed=2"
+ ]
+ },
+ "acodec": {
+ "name": "vorbisenc",
+ "container": "webmmux",
+ "width": [
+ 8, 32
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "quality=0.3"
+ ]
+ }
+ },
+ {
+ "name": "Flash Video 576p",
+ "extension": "flv",
+ "icon": "file://web-flv.png",
+ "container": "flvmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "flvmux",
+ "width": [
+ 768, 1024
+ ],
+ "height": [
+ 576, 576
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "flvmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ },
+
+ {
+ "name": "H.264 480p",
+ "extension": "mp4",
+ "container": "qtmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "qtmux",
+ "width": [
+ 640, 854
+ ],
+ "height": [
+ 480, 480
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "qtmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ },
+ {
+ "name": "WebM 480p",
+ "extension": "webm",
+ "container": "webmmux",
+ "icon": "file://web-webm.svg",
+ "vcodec": {
+ "name": "vp8enc",
+ "container": "webmmux",
+ "width": [
+ 640, 854
+ ],
+ "height": [
+ 480, 480
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "quality=5.75 threads=%(threads)s speed=2"
+ ]
+ },
+ "acodec": {
+ "name": "vorbisenc",
+ "container": "webmmux",
+ "width": [
+ 8, 32
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "quality=0.3"
+ ]
+ }
+ },
+ {
+ "name": "Flash Video 480p",
+ "extension": "flv",
+ "icon": "file://web-flv.png",
+ "container": "flvmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "flvmux",
+ "width": [
+ 640, 854
+ ],
+ "height": [
+ 480, 480
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "flvmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ },
+
+ {
+ "name": "H.264 360p",
+ "extension": "mp4",
+ "container": "qtmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "qtmux",
+ "width": [
+ 480, 640
+ ],
+ "height": [
+ 360, 360
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "qtmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ },
+ {
+ "name": "WebM 360p",
+ "extension": "webm",
+ "container": "webmmux",
+ "icon": "file://web-webm.svg",
+ "vcodec": {
+ "name": "vp8enc",
+ "container": "webmmux",
+ "width": [
+ 480, 640
+ ],
+ "height": [
+ 360, 360
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "quality=5.75 threads=%(threads)s speed=2"
+ ]
+ },
+ "acodec": {
+ "name": "vorbisenc",
+ "container": "webmmux",
+ "width": [
+ 8, 32
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "quality=0.3"
+ ]
+ }
+ },
+ {
+ "name": "Flash Video 360p",
+ "extension": "flv",
+ "icon": "file://web-flv.png",
+ "container": "flvmux",
+ "vcodec": {
+ "name": "x264enc",
+ "container": "flvmux",
+ "width": [
+ 480, 640
+ ],
+ "height": [
+ 360, 360
+ ],
+ "rate": [
+ 1, 30
+ ],
+ "passes": [
+ "pass=qual quantizer=23 subme=6 cabac=0 threads=0"
+ ]
+ },
+ "acodec": {
+ "name": "faac",
+ "container": "flvmux",
+ "width": [
+ 8, 24
+ ],
+ "depth": [
+ 8, 24
+ ],
+ "rate": [
+ 8000, 96000
+ ],
+ "channels": [
+ 1, 2
+ ],
+ "passes": [
+ "bitrate=131072 profile=LC"
+ ]
+ }
+ }
+ ]
+}
diff --git a/mediagoblin/media_types/video/devices/web-flv.png b/mediagoblin/media_types/video/devices/web-flv.png
new file mode 100644
index 00000000..b75699f4
--- /dev/null
+++ b/mediagoblin/media_types/video/devices/web-flv.png
Binary files differ
diff --git a/mediagoblin/media_types/video/devices/web-webm.svg b/mediagoblin/media_types/video/devices/web-webm.svg
new file mode 100644
index 00000000..4e5b3e97
--- /dev/null
+++ b/mediagoblin/media_types/video/devices/web-webm.svg
@@ -0,0 +1,259 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2816"
+ version="1.1"
+ inkscape:version="0.47 r22583"
+ sodipodi:docname="web-webm.svg">
+ <defs
+ id="defs2818">
+ <linearGradient
+ id="linearGradient3656">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3658" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3660" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3632">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.54901963;"
+ offset="0"
+ id="stop3634" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3636" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3622">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3624" />
+ <stop
+ style="stop-color:#d3d7cf;stop-opacity:1;"
+ offset="1"
+ id="stop3626" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3600">
+ <stop
+ style="stop-color:#8ae234;stop-opacity:1;"
+ offset="0"
+ id="stop3602" />
+ <stop
+ style="stop-color:#4e9a06;stop-opacity:1;"
+ offset="1"
+ id="stop3604" />
+ </linearGradient>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective2824" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3600"
+ id="linearGradient3606"
+ x1="20.256382"
+ y1="2.546674"
+ x2="20.256382"
+ y2="46.881901"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3622"
+ id="linearGradient3628"
+ x1="21.2349"
+ y1="7.948472"
+ x2="21.2349"
+ y2="40.191879"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3632"
+ id="linearGradient3638"
+ x1="6.4826794"
+ y1="4.543263"
+ x2="25.363527"
+ y2="35.227882"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(0,-0.35355339)" />
+ <inkscape:perspective
+ id="perspective3693"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3600-5"
+ id="linearGradient3606-9"
+ x1="20.256382"
+ y1="2.546674"
+ x2="20.256382"
+ y2="46.881901"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3600-5">
+ <stop
+ style="stop-color:#8ae234;stop-opacity:1;"
+ offset="0"
+ id="stop3602-7" />
+ <stop
+ style="stop-color:#4e9a06;stop-opacity:1;"
+ offset="1"
+ id="stop3604-2" />
+ </linearGradient>
+ <filter
+ inkscape:collect="always"
+ id="filter3731">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.82730657"
+ id="feGaussianBlur3733" />
+ </filter>
+ <inkscape:perspective
+ id="perspective3749"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <inkscape:perspective
+ id="perspective3782"
+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+ inkscape:vp_z="1 : 0.5 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_x="0 : 0.5 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="8"
+ inkscape:cx="20.51741"
+ inkscape:cy="22.534228"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1099"
+ inkscape:window-height="834"
+ inkscape:window-x="801"
+ inkscape:window-y="106"
+ inkscape:window-maximized="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3608" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata2821">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="star"
+ style="fill:#000000;fill-opacity:0.2869955;stroke:none;filter:url(#filter3731)"
+ id="path3598-4"
+ sodipodi:sides="3"
+ sodipodi:cx="13.857143"
+ sodipodi:cy="24.714287"
+ sodipodi:r1="25.596954"
+ sodipodi:r2="12.798477"
+ sodipodi:arg1="0"
+ sodipodi:arg2="1.0471976"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 39.454098,24.714287 20.256381,35.798093 1.0586662,46.8819 l 0,-22.167614 0,-22.1676119 19.1977168,11.0838069 19.197715,11.083806 z"
+ transform="matrix(1.0537808,0,0,1.0537808,3.6163385,-1.9600717)" />
+ <path
+ sodipodi:type="star"
+ style="fill:url(#linearGradient3606);fill-opacity:1;stroke:#366a04;stroke-width:1.05497880999999993;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:round"
+ id="path3598"
+ sodipodi:sides="3"
+ sodipodi:cx="13.857143"
+ sodipodi:cy="24.714287"
+ sodipodi:r1="25.596954"
+ sodipodi:r2="12.798477"
+ sodipodi:arg1="0"
+ sodipodi:arg2="1.0471976"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="M 39.454098,24.714287 20.256381,35.798093 1.0586662,46.8819 l 0,-22.167614 0,-22.1676119 19.1977168,11.0838069 19.197715,11.083806 z"
+ transform="matrix(0.94788634,0,0,0.94788634,5.0257749,0.56128794)" />
+ <path
+ style="fill:url(#linearGradient3628);stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
+ d="m 6.5304575,9.646791 8.7347075,20.091724 4.674611,-18.160553 4.525987,2.612472 3.885316,12.559503 4.403755,-7.765833 1.744319,1.009296 -2.127799,9.211229 -6.155446,3.554753 -4.028978,-9.439016 -2.255629,13.086534 -5.852703,3.373025 -7.5584205,-9.989634 0.01028,-20.1435 z"
+ id="path3620"
+ sodipodi:nodetypes="cccccccccccccc" />
+ <path
+ style="fill:none;stroke:url(#linearGradient3638);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 6.9826793,42.785087 0,-38.0953773 32.9068657,18.9987873"
+ id="path3630"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
+ d="M 6.6184028,8.6135689 15.026019,28.134068 19.45616,10.995613"
+ id="path3739"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
+ d="m 25.081121,14.552251 3.345117,11.020499 3.93014,-6.825955"
+ id="path3739-5"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
+ d="m 6.6291261,30.85266 7.0710679,9.280777"
+ id="path3772"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
+ d="m 34.736621,20.290253 -2.032932,8.794642"
+ id="path3772-6"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275"
+ d="m 20.594485,35.934991 1.811961,-10.650796 3.270369,7.778174"
+ id="path3796"
+ sodipodi:nodetypes="ccc" />
+ </g>
+</svg>
diff --git a/mediagoblin/media_types/video/devices/web.svg b/mediagoblin/media_types/video/devices/web.svg
new file mode 100644
index 00000000..c0c68244
--- /dev/null
+++ b/mediagoblin/media_types/video/devices/web.svg
@@ -0,0 +1,982 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/apps"
+ sodipodi:docname="internet-web-browser.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs3">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective156" />
+ <linearGradient
+ id="linearGradient4750">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4752" />
+ <stop
+ style="stop-color:#fefefe;stop-opacity:1.0000000;"
+ offset="0.37931034"
+ id="stop4758" />
+ <stop
+ style="stop-color:#1d1d1d;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop4754" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4350">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4352" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4354" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3962"
+ id="radialGradient3968"
+ gradientTransform="scale(0.999989,1.000011)"
+ cx="18.247644"
+ cy="15.716079"
+ fx="18.247644"
+ fy="15.716079"
+ r="29.993349"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4114"
+ id="radialGradient4120"
+ gradientTransform="scale(1.643990,0.608276)"
+ cx="15.115514"
+ cy="63.965388"
+ fx="15.115514"
+ fy="63.965388"
+ r="12.289036"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4126"
+ id="radialGradient4132"
+ gradientTransform="scale(0.999989,1.000011)"
+ cx="15.601279"
+ cy="12.142302"
+ fx="15.601279"
+ fy="12.142302"
+ r="43.526714"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4350"
+ id="radialGradient4356"
+ gradientTransform="scale(1.179536,0.847791)"
+ cx="11.826907"
+ cy="10.476453"
+ fx="11.826907"
+ fy="10.476453"
+ r="32.664848"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4750"
+ id="radialGradient4756"
+ gradientTransform="scale(1.036822,0.964486)"
+ cx="18.633780"
+ cy="17.486208"
+ fx="18.934305"
+ fy="17.810213"
+ r="40.692665"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1460"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1462"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1466"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1468"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1470"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1474"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1476"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1478"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1482"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1484"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1486"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1490"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1492"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1494"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1498"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1500"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1502"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1506"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1508"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1510"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1514"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1516"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1518"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1522"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1524"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1526"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1528"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1530"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1532"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1534"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1536"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1538"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1540"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1542"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1544"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1546"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1550"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1552"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1554"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1558"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="9.8994949"
+ inkscape:cx="25.799661"
+ inkscape:cy="24.622653"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1440"
+ inkscape:window-height="823"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:showpageshadow="false" />
+ <metadata
+ id="metadata4">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Globe</dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>globe</rdf:li>
+ <rdf:li>international</rdf:li>
+ <rdf:li>web</rdf:li>
+ <rdf:li>www</rdf:li>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>network</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient4120);fill-opacity:1.0000000;stroke:none;stroke-opacity:1.0000000"
+ id="path4112"
+ sodipodi:cx="24.849752"
+ sodipodi:cy="38.908627"
+ sodipodi:rx="20.203051"
+ sodipodi:ry="7.4751287"
+ d="M 45.052803 38.908627 A 20.203051 7.4751287 0 1 1 4.6467018,38.908627 A 20.203051 7.4751287 0 1 1 45.052803 38.908627 z"
+ transform="matrix(1.000000,0.000000,0.000000,1.243244,0.000000,-10.27241)" />
+ <path
+ style="fill:url(#radialGradient3968);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#39396c;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 43.959853,23.485499 C 43.959853,34.195217 35.277750,42.877222 24.569505,42.877222 C 13.860279,42.877222 5.1786663,34.195119 5.1786663,23.485499 C 5.1786663,12.776272 13.860279,4.0951517 24.569505,4.0951517 C 35.277750,4.0951517 43.959853,12.776272 43.959853,23.485499 L 43.959853,23.485499 z "
+ id="path3214" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.42159382;fill:url(#radialGradient4356);fill-opacity:1.0000000;stroke:none;stroke-opacity:1.0000000"
+ id="path4348"
+ sodipodi:cx="17.778685"
+ sodipodi:cy="15.271057"
+ sodipodi:rx="12.929953"
+ sodipodi:ry="9.2934036"
+ d="M 30.708637 15.271057 A 12.929953 9.2934036 0 1 1 4.8487320,15.271057 A 12.929953 9.2934036 0 1 1 30.708637 15.271057 z"
+ transform="matrix(0.835938,0.000000,0.000000,1.000000,9.886868,0.000000)" />
+ <g
+ id="g4136"
+ style="fill:#000000;fill-opacity:0.71345031;fill-rule:nonzero;stroke:none;stroke-miterlimit:4.0000000"
+ transform="matrix(0.982371,0.000000,0.000000,0.982371,0.121079,0.232914)">
+ <g
+ id="g4138">
+ <g
+ id="g4142">
+ <path
+ d="M 44.071300,20.714400 C 44.071300,20.977100 44.071300,20.714400 44.071300,20.714400 L 43.526400,21.331600 C 43.192400,20.938000 42.817400,20.607000 42.436600,20.261300 L 41.600700,20.384300 L 40.837000,19.521000 L 40.837000,20.589400 L 41.491300,21.084500 L 41.926800,21.577700 L 42.508800,20.919500 C 42.655300,21.193900 42.799800,21.468300 42.945300,21.742700 L 42.945300,22.565000 L 42.290000,23.305200 L 41.090800,24.128400 L 40.182600,25.034700 L 39.600600,24.374500 L 39.891600,23.634300 L 39.310500,22.976100 L 38.329100,20.878400 L 37.493200,19.933100 L 37.274400,20.179200 L 37.602500,21.372600 L 38.219700,22.071800 C 38.572200,23.089400 38.920900,24.062000 39.383800,25.034700 C 40.101600,25.034700 40.778300,24.958500 41.491200,24.868700 L 41.491200,25.444900 L 40.619100,27.584100 L 39.819300,28.488400 L 39.165000,29.888800 C 39.165000,30.656400 39.165000,31.424000 39.165000,32.191500 L 39.383800,33.097800 L 39.020500,33.508000 L 38.219700,34.002100 L 37.383800,34.701300 L 38.075200,35.482600 L 37.129900,36.306800 L 37.311500,36.840000 L 35.893500,38.445500 L 34.949200,38.445500 L 34.149400,38.939600 L 33.639600,38.939600 L 33.639600,38.281400 L 33.422800,36.963000 C 33.141500,36.136800 32.848600,35.316500 32.550700,34.496200 C 32.550700,33.890700 32.586800,33.291100 32.623000,32.685700 L 32.987300,31.863400 L 32.477500,30.875100 L 32.514600,29.517700 L 31.823200,28.736400 L 32.168900,27.605500 L 31.606400,26.967300 L 30.624000,26.967300 L 30.296900,26.597200 L 29.315500,27.214900 L 28.916100,26.761300 L 28.006900,27.543000 C 27.389700,26.843300 26.771500,26.144100 26.153400,25.444900 L 25.426800,23.716400 L 26.081100,22.730100 L 25.717800,22.319000 L 26.516600,20.425400 C 27.172900,19.609000 27.858400,18.825800 28.551800,18.039700 L 29.788100,17.710600 L 31.169000,17.546500 L 32.114300,17.793600 L 33.459000,19.150000 L 33.931700,18.615800 L 34.585000,18.533800 L 35.821300,18.944900 L 36.766600,18.944900 L 37.420900,18.368700 L 37.711900,17.957600 L 37.056600,17.546500 L 35.965800,17.464500 C 35.663100,17.044600 35.381800,16.603200 35.022400,16.230100 L 34.658100,16.394200 L 34.512600,17.464500 L 33.858300,16.724300 L 33.713800,15.900100 L 32.987200,15.325900 L 32.695200,15.325900 L 33.422700,16.148200 L 33.131700,16.888400 L 32.550600,17.052500 L 32.913900,16.312300 L 32.258600,15.984200 L 31.678500,15.326000 L 30.586700,15.572100 L 30.442200,15.900200 L 29.787900,16.312300 L 29.424600,17.217600 L 28.516400,17.669700 L 28.116000,17.217600 L 27.680500,17.217600 L 27.680500,15.736200 L 28.625800,15.242100 L 29.352400,15.242100 L 29.205900,14.666900 L 28.625800,14.090700 L 29.606300,13.884600 L 30.151200,13.268400 L 30.586700,12.527200 L 31.387500,12.527200 L 31.168700,11.952000 L 31.678500,11.622900 L 31.678500,12.281100 L 32.768300,12.527200 L 33.858100,11.622900 L 33.931300,11.210800 L 34.875600,10.553100 C 34.533800,10.595600 34.192000,10.626800 33.858000,10.717700 L 33.858000,9.9766000 L 34.221300,9.1538000 L 33.858000,9.1538000 L 33.059600,9.8940000 L 32.840800,10.305600 L 33.059600,10.882300 L 32.695300,11.868600 L 32.114200,11.539500 L 31.606400,10.964300 L 30.805600,11.539500 L 30.514600,10.223600 L 31.895500,9.3188000 L 31.895500,8.8247000 L 32.768500,8.2490000 L 34.149400,7.9194000 L 35.094700,8.2490000 L 36.838800,8.5781000 L 36.403300,9.0713000 L 35.458000,9.0713000 L 36.403300,10.058600 L 37.129900,9.2363000 L 37.350600,8.8745000 C 37.350600,8.8745000 40.137700,11.372500 41.730500,14.105000 C 43.323300,16.838400 44.071300,20.060100 44.071300,20.714400 z "
+ id="path4144" />
+ </g>
+ </g>
+ <g
+ id="g4146">
+ <g
+ id="g4150">
+ <path
+ d="M 26.070300,9.2363000 L 25.997100,9.7295000 L 26.506900,10.058600 L 27.378000,9.4829000 L 26.942500,8.9892000 L 26.360500,9.3188000 L 26.070500,9.2363000"
+ id="path4152" />
+ </g>
+ </g>
+ <g
+ id="g4154">
+ <g
+ id="g4158">
+ <path
+ d="M 26.870100,5.8633000 L 24.979500,5.1226000 L 22.799800,5.3692000 L 20.109400,6.1094000 L 19.600600,6.6035000 L 21.272500,7.7549000 L 21.272500,8.4131000 L 20.618200,9.0713000 L 21.491200,10.800300 L 22.071300,10.470200 L 22.799800,9.3188000 C 23.922800,8.9716000 24.929700,8.5781000 25.997100,8.0844000 L 26.870100,5.8632000"
+ id="path4160" />
+ </g>
+ </g>
+ <g
+ id="g4162">
+ <g
+ id="g4166">
+ <path
+ d="M 28.833000,12.774900 L 28.542000,12.033700 L 28.032200,12.198700 L 28.178700,13.103000 L 28.833000,12.774900"
+ id="path4168" />
+ </g>
+ </g>
+ <g
+ id="g4170">
+ <g
+ id="g4174">
+ <path
+ d="M 29.123000,12.608900 L 28.977500,13.597200 L 29.777300,13.432200 L 30.358400,12.857000 L 29.849600,12.362900 C 29.678700,11.907800 29.482400,11.483000 29.268500,11.046500 L 28.833000,11.046500 L 28.833000,11.539700 L 29.123000,11.868800 L 29.123000,12.609000"
+ id="path4176" />
+ </g>
+ </g>
+ <g
+ id="g4178">
+ <g
+ id="g4182">
+ <path
+ d="M 18.365200,28.242200 L 17.783200,27.089900 L 16.692900,26.843300 L 16.111400,25.280800 L 14.657800,25.444900 L 13.422400,24.540600 L 12.113300,25.692000 L 12.113300,25.873600 C 11.717300,25.759300 11.230500,25.743700 10.877900,25.526900 L 10.586900,24.704600 L 10.586900,23.799300 L 9.7148000,23.881300 C 9.7876000,23.305100 9.8598000,22.729900 9.9331000,22.153800 L 9.4238000,22.153800 L 8.9155000,22.812000 L 8.4062000,23.058100 L 7.6791000,22.647900 L 7.6063000,21.742600 L 7.7518000,20.755300 L 8.8426000,19.933000 L 9.7147000,19.933000 L 9.8597000,19.438900 L 10.950000,19.685000 L 11.749800,20.673300 L 11.895300,19.026800 L 13.276600,17.875400 L 13.785400,16.641000 L 14.803000,16.229900 L 15.384500,15.407600 L 16.692600,15.159600 L 17.347400,14.173300 C 16.693100,14.173300 16.038800,14.173300 15.384500,14.173300 L 16.620300,13.597100 L 17.491900,13.597100 L 18.728200,13.185000 L 18.873700,12.692800 L 18.437200,12.280700 L 17.928400,12.115700 L 18.073900,11.622500 L 17.710600,10.882300 L 16.838000,11.210400 L 16.983500,10.552700 L 15.965900,9.9765000 L 15.166600,11.374400 L 15.238900,11.868500 L 14.439600,12.198600 L 13.930300,13.267900 L 13.712500,12.280600 L 12.331200,11.704400 L 12.112900,10.964200 L 13.930300,9.8939000 L 14.730100,9.1537000 L 14.802900,8.2489000 L 14.366900,8.0018000 L 13.785400,7.9193000 L 13.422100,8.8246000 C 13.422100,8.8246000 12.814200,8.9437000 12.657900,8.9823000 C 10.661800,10.821700 6.6286000,14.792400 5.6916000,22.288500 C 5.7287000,22.462300 6.3708000,23.470100 6.3708000,23.470100 L 7.8972000,24.374400 L 9.4236000,24.786500 L 10.078400,25.609700 L 11.095500,26.349900 L 11.677000,26.267900 L 12.113000,26.464200 L 12.113000,26.597000 L 11.531900,28.160000 L 11.095400,28.818200 L 11.240900,29.148300 L 10.877600,30.380700 L 12.186200,32.767400 L 13.494300,33.919700 L 14.076300,34.742000 L 14.003100,36.470500 L 14.439600,37.456800 L 14.003100,39.349400 C 14.003100,39.349400 13.968900,39.337700 14.024600,39.527100 C 14.080800,39.716600 16.353700,40.978300 16.498200,40.870900 C 16.642200,40.761500 16.765300,40.665800 16.765300,40.665800 L 16.620300,40.255600 L 17.201400,39.679400 L 17.419700,39.103200 L 18.365000,38.773100 L 19.091600,36.962600 L 18.873800,36.470400 L 19.381600,35.730200 L 20.472400,35.482200 L 21.054400,34.165800 L 20.908900,32.521300 L 21.781000,31.286900 L 21.926500,30.052500 C 20.733100,29.460700 19.549500,28.851300 18.365000,28.242000"
+ id="path4184" />
+ </g>
+ </g>
+ <g
+ id="g4186">
+ <g
+ id="g4190">
+ <path
+ d="M 16.765600,9.5649000 L 17.492200,10.058600 L 18.074200,10.058600 L 18.074200,9.4829000 L 17.347600,9.1538000 L 16.765600,9.5649000"
+ id="path4192" />
+ </g>
+ </g>
+ <g
+ id="g4194">
+ <g
+ id="g4198">
+ <path
+ d="M 14.876000,8.9072000 L 14.512200,9.8120000 L 15.239300,9.8120000 L 15.603100,8.9892000 C 15.916600,8.7675000 16.228600,8.5444000 16.547900,8.3310000 L 17.275000,8.5781000 C 17.759400,8.9072000 18.243800,9.2363000 18.728600,9.5649000 L 19.456100,8.9072000 L 18.655800,8.5781000 L 18.292000,7.8374000 L 16.911100,7.6728000 L 16.838300,7.2612000 L 16.184000,7.4262000 L 15.893600,8.0020000 L 15.529800,7.2613000 L 15.384800,7.5904000 L 15.457600,8.4132000 L 14.876000,8.9072000"
+ id="path4200" />
+ </g>
+ </g>
+ <g
+ id="g4202">
+ <g
+ style="opacity:0.75000000"
+ id="g4204">
+ <path
+ id="path4206"
+ d="" />
+ </g>
+ <g
+ id="g4208">
+ <path
+ id="path4210"
+ d="" />
+ </g>
+ </g>
+ <g
+ id="g4212">
+ <g
+ style="opacity:0.75000000"
+ id="g4214">
+ <path
+ id="path4216"
+ d="" />
+ </g>
+ <g
+ id="g4218">
+ <path
+ id="path4220"
+ d="" />
+ </g>
+ </g>
+ <g
+ id="g4222">
+ <g
+ id="g4226">
+ <path
+ d="M 17.492200,6.8496000 L 17.856000,6.5210000 L 18.583100,6.3564000 C 19.081100,6.1142000 19.581100,5.9511000 20.109500,5.7802000 L 19.819500,5.2865000 L 18.881000,5.4213000 L 18.437600,5.8632000 L 17.706600,5.9692000 L 17.056700,6.2744000 L 16.740800,6.4272000 L 16.547900,6.6855000 L 17.492200,6.8496000"
+ id="path4228" />
+ </g>
+ </g>
+ <g
+ id="g4230">
+ <g
+ id="g4234">
+ <path
+ d="M 18.728500,14.666500 L 19.165000,14.008300 L 18.510200,13.515100 L 18.728500,14.666500"
+ id="path4236" />
+ </g>
+ </g>
+ </g>
+ <g
+ id="g3216"
+ style="color:#000000;fill:url(#radialGradient1460);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:1.0179454;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ transform="matrix(0.982371,0.000000,0.000000,0.982371,-8.095179e-2,3.088300e-2)">
+ <g
+ id="g3218"
+ style="color:#000000;fill:url(#radialGradient1462);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3222"
+ style="color:#000000;fill:url(#radialGradient1466);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 44.071300,20.714400 C 44.071300,20.977100 44.071300,20.714400 44.071300,20.714400 L 43.526400,21.331600 C 43.192400,20.938000 42.817400,20.607000 42.436600,20.261300 L 41.600700,20.384300 L 40.837000,19.521000 L 40.837000,20.589400 L 41.491300,21.084500 L 41.926800,21.577700 L 42.508800,20.919500 C 42.655300,21.193900 42.799800,21.468300 42.945300,21.742700 L 42.945300,22.565000 L 42.290000,23.305200 L 41.090800,24.128400 L 40.182600,25.034700 L 39.600600,24.374500 L 39.891600,23.634300 L 39.310500,22.976100 L 38.329100,20.878400 L 37.493200,19.933100 L 37.274400,20.179200 L 37.602500,21.372600 L 38.219700,22.071800 C 38.572200,23.089400 38.920900,24.062000 39.383800,25.034700 C 40.101600,25.034700 40.778300,24.958500 41.491200,24.868700 L 41.491200,25.444900 L 40.619100,27.584100 L 39.819300,28.488400 L 39.165000,29.888800 C 39.165000,30.656400 39.165000,31.424000 39.165000,32.191500 L 39.383800,33.097800 L 39.020500,33.508000 L 38.219700,34.002100 L 37.383800,34.701300 L 38.075200,35.482600 L 37.129900,36.306800 L 37.311500,36.840000 L 35.893500,38.445500 L 34.949200,38.445500 L 34.149400,38.939600 L 33.639600,38.939600 L 33.639600,38.281400 L 33.422800,36.963000 C 33.141500,36.136800 32.848600,35.316500 32.550700,34.496200 C 32.550700,33.890700 32.586800,33.291100 32.623000,32.685700 L 32.987300,31.863400 L 32.477500,30.875100 L 32.514600,29.517700 L 31.823200,28.736400 L 32.168900,27.605500 L 31.606400,26.967300 L 30.624000,26.967300 L 30.296900,26.597200 L 29.315500,27.214900 L 28.916100,26.761300 L 28.006900,27.543000 C 27.389700,26.843300 26.771500,26.144100 26.153400,25.444900 L 25.426800,23.716400 L 26.081100,22.730100 L 25.717800,22.319000 L 26.516600,20.425400 C 27.172900,19.609000 27.858400,18.825800 28.551800,18.039700 L 29.788100,17.710600 L 31.169000,17.546500 L 32.114300,17.793600 L 33.459000,19.150000 L 33.931700,18.615800 L 34.585000,18.533800 L 35.821300,18.944900 L 36.766600,18.944900 L 37.420900,18.368700 L 37.711900,17.957600 L 37.056600,17.546500 L 35.965800,17.464500 C 35.663100,17.044600 35.381800,16.603200 35.022400,16.230100 L 34.658100,16.394200 L 34.512600,17.464500 L 33.858300,16.724300 L 33.713800,15.900100 L 32.987200,15.325900 L 32.695200,15.325900 L 33.422700,16.148200 L 33.131700,16.888400 L 32.550600,17.052500 L 32.913900,16.312300 L 32.258600,15.984200 L 31.678500,15.326000 L 30.586700,15.572100 L 30.442200,15.900200 L 29.787900,16.312300 L 29.424600,17.217600 L 28.516400,17.669700 L 28.116000,17.217600 L 27.680500,17.217600 L 27.680500,15.736200 L 28.625800,15.242100 L 29.352400,15.242100 L 29.205900,14.666900 L 28.625800,14.090700 L 29.606300,13.884600 L 30.151200,13.268400 L 30.586700,12.527200 L 31.387500,12.527200 L 31.168700,11.952000 L 31.678500,11.622900 L 31.678500,12.281100 L 32.768300,12.527200 L 33.858100,11.622900 L 33.931300,11.210800 L 34.875600,10.553100 C 34.533800,10.595600 34.192000,10.626800 33.858000,10.717700 L 33.858000,9.9766000 L 34.221300,9.1538000 L 33.858000,9.1538000 L 33.059600,9.8940000 L 32.840800,10.305600 L 33.059600,10.882300 L 32.695300,11.868600 L 32.114200,11.539500 L 31.606400,10.964300 L 30.805600,11.539500 L 30.514600,10.223600 L 31.895500,9.3188000 L 31.895500,8.8247000 L 32.768500,8.2490000 L 34.149400,7.9194000 L 35.094700,8.2490000 L 36.838800,8.5781000 L 36.403300,9.0713000 L 35.458000,9.0713000 L 36.403300,10.058600 L 37.129900,9.2363000 L 37.350600,8.8745000 C 37.350600,8.8745000 40.137700,11.372500 41.730500,14.105000 C 43.323300,16.838400 44.071300,20.060100 44.071300,20.714400 z "
+ id="path3224"
+ style="color:#000000;fill:url(#radialGradient1468);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3226"
+ style="color:#000000;fill:url(#radialGradient1470);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3230"
+ style="color:#000000;fill:url(#radialGradient1474);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 26.070300,9.2363000 L 25.997100,9.7295000 L 26.506900,10.058600 L 27.378000,9.4829000 L 26.942500,8.9892000 L 26.360500,9.3188000 L 26.070500,9.2363000"
+ id="path3232"
+ style="color:#000000;fill:url(#radialGradient1476);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3234"
+ style="color:#000000;fill:url(#radialGradient1478);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3238"
+ style="color:#000000;fill:url(#radialGradient1482);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 26.870100,5.8633000 L 24.979500,5.1226000 L 22.799800,5.3692000 L 20.109400,6.1094000 L 19.600600,6.6035000 L 21.272500,7.7549000 L 21.272500,8.4131000 L 20.618200,9.0713000 L 21.491200,10.800300 L 22.071300,10.470200 L 22.799800,9.3188000 C 23.922800,8.9716000 24.929700,8.5781000 25.997100,8.0844000 L 26.870100,5.8632000"
+ id="path3240"
+ style="color:#000000;fill:url(#radialGradient1484);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3242"
+ style="color:#000000;fill:url(#radialGradient1486);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3246"
+ style="color:#000000;fill:url(#radialGradient1490);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 28.833000,12.774900 L 28.542000,12.033700 L 28.032200,12.198700 L 28.178700,13.103000 L 28.833000,12.774900"
+ id="path3248"
+ style="color:#000000;fill:url(#radialGradient1492);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3250"
+ style="color:#000000;fill:url(#radialGradient1494);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3254"
+ style="color:#000000;fill:url(#radialGradient1498);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 29.123000,12.608900 L 28.977500,13.597200 L 29.777300,13.432200 L 30.358400,12.857000 L 29.849600,12.362900 C 29.678700,11.907800 29.482400,11.483000 29.268500,11.046500 L 28.833000,11.046500 L 28.833000,11.539700 L 29.123000,11.868800 L 29.123000,12.609000"
+ id="path3256"
+ style="color:#000000;fill:url(#radialGradient1500);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3258"
+ style="color:#000000;fill:url(#radialGradient1502);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3262"
+ style="color:#000000;fill:url(#radialGradient1506);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 18.365200,28.242200 L 17.783200,27.089900 L 16.692900,26.843300 L 16.111400,25.280800 L 14.657800,25.444900 L 13.422400,24.540600 L 12.113300,25.692000 L 12.113300,25.873600 C 11.717300,25.759300 11.230500,25.743700 10.877900,25.526900 L 10.586900,24.704600 L 10.586900,23.799300 L 9.7148000,23.881300 C 9.7876000,23.305100 9.8598000,22.729900 9.9331000,22.153800 L 9.4238000,22.153800 L 8.9155000,22.812000 L 8.4062000,23.058100 L 7.6791000,22.647900 L 7.6063000,21.742600 L 7.7518000,20.755300 L 8.8426000,19.933000 L 9.7147000,19.933000 L 9.8597000,19.438900 L 10.950000,19.685000 L 11.749800,20.673300 L 11.895300,19.026800 L 13.276600,17.875400 L 13.785400,16.641000 L 14.803000,16.229900 L 15.384500,15.407600 L 16.692600,15.159600 L 17.347400,14.173300 C 16.693100,14.173300 16.038800,14.173300 15.384500,14.173300 L 16.620300,13.597100 L 17.491900,13.597100 L 18.728200,13.185000 L 18.873700,12.692800 L 18.437200,12.280700 L 17.928400,12.115700 L 18.073900,11.622500 L 17.710600,10.882300 L 16.838000,11.210400 L 16.983500,10.552700 L 15.965900,9.9765000 L 15.166600,11.374400 L 15.238900,11.868500 L 14.439600,12.198600 L 13.930300,13.267900 L 13.712500,12.280600 L 12.331200,11.704400 L 12.112900,10.964200 L 13.930300,9.8939000 L 14.730100,9.1537000 L 14.802900,8.2489000 L 14.366900,8.0018000 L 13.785400,7.9193000 L 13.422100,8.8246000 C 13.422100,8.8246000 12.814200,8.9437000 12.657900,8.9823000 C 10.661800,10.821700 6.6286000,14.792400 5.6916000,22.288500 C 5.7287000,22.462300 6.3708000,23.470100 6.3708000,23.470100 L 7.8972000,24.374400 L 9.4236000,24.786500 L 10.078400,25.609700 L 11.095500,26.349900 L 11.677000,26.267900 L 12.113000,26.464200 L 12.113000,26.597000 L 11.531900,28.160000 L 11.095400,28.818200 L 11.240900,29.148300 L 10.877600,30.380700 L 12.186200,32.767400 L 13.494300,33.919700 L 14.076300,34.742000 L 14.003100,36.470500 L 14.439600,37.456800 L 14.003100,39.349400 C 14.003100,39.349400 13.968900,39.337700 14.024600,39.527100 C 14.080800,39.716600 16.353700,40.978300 16.498200,40.870900 C 16.642200,40.761500 16.765300,40.665800 16.765300,40.665800 L 16.620300,40.255600 L 17.201400,39.679400 L 17.419700,39.103200 L 18.365000,38.773100 L 19.091600,36.962600 L 18.873800,36.470400 L 19.381600,35.730200 L 20.472400,35.482200 L 21.054400,34.165800 L 20.908900,32.521300 L 21.781000,31.286900 L 21.926500,30.052500 C 20.733100,29.460700 19.549500,28.851300 18.365000,28.242000"
+ id="path3264"
+ style="color:#000000;fill:url(#radialGradient1508);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3266"
+ style="color:#000000;fill:url(#radialGradient1510);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3270"
+ style="color:#000000;fill:url(#radialGradient1514);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 16.765600,9.5649000 L 17.492200,10.058600 L 18.074200,10.058600 L 18.074200,9.4829000 L 17.347600,9.1538000 L 16.765600,9.5649000"
+ id="path3272"
+ style="color:#000000;fill:url(#radialGradient1516);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3274"
+ style="color:#000000;fill:url(#radialGradient1518);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3278"
+ style="color:#000000;fill:url(#radialGradient1522);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 14.876000,8.9072000 L 14.512200,9.8120000 L 15.239300,9.8120000 L 15.603100,8.9892000 C 15.916600,8.7675000 16.228600,8.5444000 16.547900,8.3310000 L 17.275000,8.5781000 C 17.759400,8.9072000 18.243800,9.2363000 18.728600,9.5649000 L 19.456100,8.9072000 L 18.655800,8.5781000 L 18.292000,7.8374000 L 16.911100,7.6728000 L 16.838300,7.2612000 L 16.184000,7.4262000 L 15.893600,8.0020000 L 15.529800,7.2613000 L 15.384800,7.5904000 L 15.457600,8.4132000 L 14.876000,8.9072000"
+ id="path3280"
+ style="color:#000000;fill:url(#radialGradient1524);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3282"
+ style="color:#000000;fill:url(#radialGradient1526);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ style="opacity:0.75000000;color:#000000;fill:url(#radialGradient1528);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="g3284">
+ <path
+ d=""
+ style="color:#000000;fill:url(#radialGradient1530);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="path3286" />
+ </g>
+ <g
+ id="g3288"
+ style="color:#000000;fill:url(#radialGradient1532);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d=""
+ id="path3290"
+ style="color:#000000;fill:url(#radialGradient1534);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3292"
+ style="color:#000000;fill:url(#radialGradient1536);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ style="opacity:0.75000000;color:#000000;fill:url(#radialGradient1538);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="g3294">
+ <path
+ d=""
+ style="color:#000000;fill:url(#radialGradient1540);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="path3296" />
+ </g>
+ <g
+ id="g3298"
+ style="color:#000000;fill:url(#radialGradient1542);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d=""
+ id="path3300"
+ style="color:#000000;fill:url(#radialGradient1544);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3302"
+ style="color:#000000;fill:url(#radialGradient1546);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3306"
+ style="color:#000000;fill:url(#radialGradient1550);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 17.492200,6.8496000 L 17.856000,6.5210000 L 18.583100,6.3564000 C 19.081100,6.1142000 19.581100,5.9511000 20.109500,5.7802000 L 19.819500,5.2865000 L 18.881000,5.4213000 L 18.437600,5.8632000 L 17.706600,5.9692000 L 17.056700,6.2744000 L 16.740800,6.4272000 L 16.547900,6.6855000 L 17.492200,6.8496000"
+ id="path3308"
+ style="color:#000000;fill:url(#radialGradient1552);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3310"
+ style="color:#000000;fill:url(#radialGradient1554);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3314"
+ style="color:#000000;fill:url(#radialGradient1558);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 18.728500,14.666500 L 19.165000,14.008300 L 18.510200,13.515100 L 18.728500,14.666500"
+ id="path3316"
+ style="color:#000000;fill:url(#radialGradient4756);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ </g>
+ <path
+ style="fill:none;fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#radialGradient4132);stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 42.975093,23.485534 C 42.975093,33.651354 34.733915,41.892440 24.569493,41.892440 C 14.404139,41.892440 6.1634261,33.651261 6.1634261,23.485534 C 6.1634261,13.320180 14.404139,5.0799340 24.569493,5.0799340 C 34.733915,5.0799340 42.975093,13.320180 42.975093,23.485534 L 42.975093,23.485534 z "
+ id="path4122" />
+ </g>
+</svg>
diff --git a/mediagoblin/media_types/video/migrations.py b/mediagoblin/media_types/video/migrations.py
new file mode 100644
index 00000000..442bbd8d
--- /dev/null
+++ b/mediagoblin/media_types/video/migrations.py
@@ -0,0 +1,32 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.db.migration_tools import RegisterMigration, inspect_table
+
+from sqlalchemy import MetaData, Column, Unicode
+
+MIGRATIONS = {}
+
+@RegisterMigration(1, MIGRATIONS)
+def add_orig_metadata_column(db_conn):
+ metadata = MetaData(bind=db_conn.bind)
+
+ vid_data = inspect_table(metadata, "video__mediadata")
+
+ col = Column('orig_metadata', Unicode,
+ default=None, nullable=True)
+ col.create(vid_data)
+ db_conn.commit()
diff --git a/mediagoblin/media_types/video/models.py b/mediagoblin/media_types/video/models.py
new file mode 100644
index 00000000..0b52c53f
--- /dev/null
+++ b/mediagoblin/media_types/video/models.py
@@ -0,0 +1,97 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from mediagoblin.db.base import Base
+
+from sqlalchemy import (
+ Column, Integer, SmallInteger, ForeignKey)
+from sqlalchemy.orm import relationship, backref
+from mediagoblin.db.extratypes import JSONEncoded
+from mediagoblin.media_types import video
+
+
+BACKREF_NAME = "video__media_data"
+
+
+class VideoData(Base):
+ """
+ Attributes:
+ - media_data: the originating media entry (of course)
+ - width: width of the transcoded video
+ - height: height of the transcoded video
+ - orig_metadata: A loose json structure containing metadata gstreamer
+ pulled from the original video.
+ This field is NOT GUARANTEED to exist!
+
+ Likely metadata extracted:
+ "videoheight", "videolength", "videowidth",
+ "audiorate", "audiolength", "audiochannels", "audiowidth",
+ "mimetype", "tags"
+
+ TODO: document the above better.
+ """
+ __tablename__ = "video__mediadata"
+
+ # The primary key *and* reference to the main media_entry
+ media_entry = Column(Integer, ForeignKey('core__media_entries.id'),
+ primary_key=True)
+ get_media_entry = relationship("MediaEntry",
+ backref=backref(BACKREF_NAME, uselist=False,
+ cascade="all, delete-orphan"))
+
+ width = Column(SmallInteger)
+ height = Column(SmallInteger)
+
+ orig_metadata = Column(JSONEncoded)
+
+ def source_type(self):
+ """
+ Construct a useful type=... that is to say, used like:
+ <video><source type="{{ entry.media_data.source_type() }}" /></video>
+
+ Try to construct it out of self.orig_metadata... if we fail we
+ just dope'ily fall back on DEFAULT_WEBM_TYPE
+ """
+ orig_metadata = self.orig_metadata or {}
+
+ if "webm_640" not in self.get_media_entry.media_files \
+ and "mimetype" in orig_metadata \
+ and "tags" in orig_metadata \
+ and "audio-codec" in orig_metadata["tags"] \
+ and "video-codec" in orig_metadata["tags"]:
+ if orig_metadata['mimetype'] == 'application/ogg':
+ # stupid ambiguous .ogg extension
+ mimetype = "video/ogg"
+ else:
+ mimetype = orig_metadata['mimetype']
+
+ video_codec = orig_metadata["tags"]["video-codec"].lower()
+ audio_codec = orig_metadata["tags"]["audio-codec"].lower()
+
+ # We don't want the "video" at the end of vp8...
+ # not sure of a nicer way to be cleaning this stuff
+ if video_codec == "vp8 video":
+ video_codec = "vp8"
+
+ return '%s; codecs="%s, %s"' % (
+ mimetype, video_codec, audio_codec)
+ else:
+ return video.VideoMediaManager.default_webm_type
+
+
+DATA_MODEL = VideoData
+MODELS = [VideoData]
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
new file mode 100644
index 00000000..ff2c94a0
--- /dev/null
+++ b/mediagoblin/media_types/video/processing.py
@@ -0,0 +1,212 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from tempfile import NamedTemporaryFile
+import logging
+import datetime
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.processing import \
+ create_pub_filepath, FilenameBuilder, BaseProcessingFail, ProgressCallback
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+from . import transcoders
+from .util import skip_transcode
+
+_log = logging.getLogger(__name__)
+_log.setLevel(logging.DEBUG)
+
+
+class VideoTranscodingFail(BaseProcessingFail):
+ '''
+ Error raised if video transcoding fails
+ '''
+ general_message = _(u'Video transcoding failed')
+
+
+def sniff_handler(media_file, **kw):
+ transcoder = transcoders.VideoTranscoder()
+ data = transcoder.discover(media_file.name)
+
+ _log.debug('Discovered: {0}'.format(data))
+
+ if not data:
+ _log.error('Could not discover {0}'.format(
+ kw.get('media')))
+ return False
+
+ if data['is_video'] == True:
+ return True
+
+ return False
+
+
+def process_video(proc_state):
+ """
+ Process a video entry, transcode the queued media files (originals) and
+ create a thumbnail for the entry.
+
+ A Workbench() represents a local tempory dir. It is automatically
+ cleaned up when this function exits.
+ """
+ entry = proc_state.entry
+ workbench = proc_state.workbench
+ video_config = mgg.global_config['media_type:mediagoblin.media_types.video']
+
+ queued_filepath = entry.queued_media_file
+ queued_filename = proc_state.get_queued_filename()
+ name_builder = FilenameBuilder(queued_filename)
+
+ medium_filepath = create_pub_filepath(
+ entry, name_builder.fill('{basename}-640p.webm'))
+
+ thumbnail_filepath = create_pub_filepath(
+ entry, name_builder.fill('{basename}.thumbnail.jpg'))
+
+ # Create a temporary file for the video destination (cleaned up with workbench)
+ tmp_dst = NamedTemporaryFile(dir=workbench.dir, delete=False)
+ with tmp_dst:
+ # Transcode queued file to a VP8/vorbis file that fits in a 640x640 square
+ progress_callback = ProgressCallback(entry)
+
+ dimensions = (
+ mgg.global_config['media:medium']['max_width'],
+ mgg.global_config['media:medium']['max_height'])
+
+ # Extract metadata and keep a record of it
+ metadata = transcoders.VideoTranscoder().discover(queued_filename)
+ store_metadata(entry, metadata)
+
+ # Figure out whether or not we need to transcode this video or
+ # if we can skip it
+ if skip_transcode(metadata):
+ _log.debug('Skipping transcoding')
+
+ dst_dimensions = metadata['videowidth'], metadata['videoheight']
+
+ # Push original file to public storage
+ _log.debug('Saving original...')
+ proc_state.copy_original(queued_filepath[-1])
+
+ did_transcode = False
+ else:
+ transcoder = transcoders.VideoTranscoder()
+
+ transcoder.transcode(queued_filename, tmp_dst.name,
+ vp8_quality=video_config['vp8_quality'],
+ vp8_threads=video_config['vp8_threads'],
+ vorbis_quality=video_config['vorbis_quality'],
+ progress_callback=progress_callback,
+ dimensions=dimensions)
+
+ dst_dimensions = transcoder.dst_data.videowidth,\
+ transcoder.dst_data.videoheight
+
+ # Push transcoded video to public storage
+ _log.debug('Saving medium...')
+ mgg.public_store.copy_local_to_storage(tmp_dst.name, medium_filepath)
+ _log.debug('Saved medium')
+
+ entry.media_files['webm_640'] = medium_filepath
+
+ did_transcode = True
+
+ # Save the width and height of the transcoded video
+ entry.media_data_init(
+ width=dst_dimensions[0],
+ height=dst_dimensions[1])
+
+ # Temporary file for the video thumbnail (cleaned up with workbench)
+ tmp_thumb = NamedTemporaryFile(dir=workbench.dir, suffix='.jpg', delete=False)
+
+ with tmp_thumb:
+ # Create a thumbnail.jpg that fits in a 180x180 square
+ transcoders.VideoThumbnailerMarkII(
+ queued_filename,
+ tmp_thumb.name,
+ 180)
+
+ # Push the thumbnail to public storage
+ _log.debug('Saving thumbnail...')
+ mgg.public_store.copy_local_to_storage(tmp_thumb.name, thumbnail_filepath)
+ entry.media_files['thumb'] = thumbnail_filepath
+
+ # save the original... but only if we did a transcoding
+ # (if we skipped transcoding and just kept the original anyway as the main
+ # media, then why would we save the original twice?)
+ if video_config['keep_original'] and did_transcode:
+ # Push original file to public storage
+ _log.debug('Saving original...')
+ proc_state.copy_original(queued_filepath[-1])
+
+ # Remove queued media file from storage and database
+ proc_state.delete_queue_file()
+
+
+def store_metadata(media_entry, metadata):
+ """
+ Store metadata from this video for this media entry.
+ """
+ # Let's pull out the easy, not having to be converted ones first
+ stored_metadata = dict(
+ [(key, metadata[key])
+ for key in [
+ "videoheight", "videolength", "videowidth",
+ "audiorate", "audiolength", "audiochannels", "audiowidth",
+ "mimetype"]
+ if key in metadata])
+
+ # We have to convert videorate into a sequence because it's a
+ # special type normally..
+
+ if "videorate" in metadata:
+ videorate = metadata["videorate"]
+ stored_metadata["videorate"] = [videorate.num, videorate.denom]
+
+ # Also make a whitelist conversion of the tags.
+ if "tags" in metadata:
+ tags_metadata = metadata['tags']
+
+ # we don't use *all* of these, but we know these ones are
+ # safe...
+ tags = dict(
+ [(key, tags_metadata[key])
+ for key in [
+ "application-name", "artist", "audio-codec", "bitrate",
+ "container-format", "copyright", "encoder",
+ "encoder-version", "license", "nominal-bitrate", "title",
+ "video-codec"]
+ if key in tags_metadata])
+ if 'date' in tags_metadata:
+ date = tags_metadata['date']
+ tags['date'] = "%s-%s-%s" % (
+ date.year, date.month, date.day)
+
+ # TODO: handle timezone info; gst.get_time_zone_offset +
+ # python's tzinfo should help
+ if 'datetime' in tags_metadata:
+ dt = tags_metadata['datetime']
+ tags['datetime'] = datetime.datetime(
+ dt.get_year(), dt.get_month(), dt.get_day(), dt.get_hour(),
+ dt.get_minute(), dt.get_second(),
+ dt.get_microsecond()).isoformat()
+
+ metadata['tags'] = tags
+
+ # Only save this field if there's something to save
+ if len(stored_metadata):
+ media_entry.media_data_init(
+ orig_metadata=stored_metadata)
diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py
new file mode 100644
index 00000000..90a767dd
--- /dev/null
+++ b/mediagoblin/media_types/video/transcoders.py
@@ -0,0 +1,776 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import division
+
+import os
+import sys
+import logging
+import urllib
+import multiprocessing
+import gobject
+import pygst
+pygst.require('0.10')
+import gst
+import struct
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from gst.extend import discoverer
+
+_log = logging.getLogger(__name__)
+
+gobject.threads_init()
+
+CPU_COUNT = 2
+
+try:
+ CPU_COUNT = multiprocessing.cpu_count()
+except NotImplementedError:
+ _log.warning('multiprocessing.cpu_count not implemented')
+
+os.putenv('GST_DEBUG_DUMP_DOT_DIR', '/tmp')
+
+
+def pixbuf_to_pilbuf(buf):
+ data = list()
+ for i in range(0, len(buf), 3):
+ r, g, b = struct.unpack('BBB', buf[i:i + 3])
+ data.append((r, g, b))
+
+ return data
+
+
+class VideoThumbnailerMarkII(object):
+ '''
+ Creates a thumbnail from a video file. Rewrite of VideoThumbnailer.
+
+ Large parts of the functionality and overall architectue contained within
+ this object is taken from Participatory Culture Foundation's
+ `gst_extractor.Extractor` object last seen at
+ https://github.com/pculture/miro/blob/master/tv/lib/frontends/widgets/gst/gst_extractor.py
+ in the `miro` codebase.
+
+ The `miro` codebase and the gst_extractor.py are licensed under the GNU
+ General Public License v2 or later.
+ '''
+ STATE_NULL = 0
+ STATE_HALTING = 1
+ STATE_PROCESSING = 2
+ STATE_PROCESSING_THUMBNAIL = 3
+
+ def __init__(self, source_path, dest_path, width=None, height=None,
+ position_callback=None):
+ self.state = self.STATE_NULL
+
+ self.has_reached_playbin_pause = False
+
+ self.thumbnail_pipeline = None
+
+ self.permission_to_take_picture = False
+
+ self.buffer_probes = {}
+
+ self.errors = []
+
+ self.source_path = os.path.abspath(source_path)
+ self.dest_path = os.path.abspath(dest_path)
+
+ self.width = width
+ self.height = height
+ self.position_callback = position_callback \
+ or self.wadsworth_position_callback
+
+ self.mainloop = gobject.MainLoop()
+
+ self.playbin = gst.element_factory_make('playbin')
+
+ self.videosink = gst.element_factory_make('fakesink', 'videosink')
+ self.audiosink = gst.element_factory_make('fakesink', 'audiosink')
+
+ self.playbin.set_property('video-sink', self.videosink)
+ self.playbin.set_property('audio-sink', self.audiosink)
+
+ self.playbin_message_bus = self.playbin.get_bus()
+
+ self.playbin_message_bus.add_signal_watch()
+ self.playbin_bus_watch_id = self.playbin_message_bus.connect(
+ 'message',
+ self.on_playbin_message)
+
+ self.playbin.set_property(
+ 'uri',
+ 'file:{0}'.format(
+ urllib.pathname2url(self.source_path)))
+
+ self.playbin.set_state(gst.STATE_PAUSED)
+
+ try:
+ self.run()
+ except Exception as exc:
+ _log.critical(
+ 'Exception "{0}" caught, shutting down mainloop and re-raising'\
+ .format(exc))
+ self.disconnect()
+ raise
+
+ def wadsworth_position_callback(self, duration, gst):
+ return self.duration / 100 * 30
+
+ def run(self):
+ self.mainloop.run()
+
+ def on_playbin_message(self, message_bus, message):
+ # Silenced to prevent clobbering of output
+ #_log.debug('playbin message: {0}'.format(message))
+
+ if message.type == gst.MESSAGE_ERROR:
+ _log.error('playbin error: {0}'.format(message))
+ gobject.idle_add(self.on_playbin_error)
+
+ if message.type == gst.MESSAGE_STATE_CHANGED:
+ prev_state, cur_state, pending_state = \
+ message.parse_state_changed()
+
+ _log.debug('playbin state changed: \nprev: {0}\ncur: {1}\n \
+pending: {2}'.format(
+ prev_state,
+ cur_state,
+ pending_state))
+
+ if cur_state == gst.STATE_PAUSED:
+ if message.src == self.playbin:
+ _log.info('playbin ready')
+ gobject.idle_add(self.on_playbin_paused)
+
+ def on_playbin_paused(self):
+ if self.has_reached_playbin_pause:
+ _log.warn('Has already reached on_playbin_paused. Aborting \
+without doing anything this time.')
+ return False
+
+ self.has_reached_playbin_pause = True
+
+ # XXX: Why is this even needed at this point?
+ current_video = self.playbin.get_property('current-video')
+
+ if not current_video:
+ _log.critical('Could not get any video data \
+from playbin')
+ else:
+ _log.info('Got video data from playbin')
+
+ self.duration = self.get_duration(self.playbin)
+ self.permission_to_take_picture = True
+ self.buffer_probes = {}
+
+ pipeline = ''.join([
+ 'filesrc location="%s" ! decodebin ! ' % self.source_path,
+ 'ffmpegcolorspace ! videoscale ! ',
+ 'video/x-raw-rgb,depth=24,bpp=24,pixel-aspect-ratio=1/1',
+ ',width={0}'.format(self.width) if self.width else '',
+ ',height={0}'.format(self.height) if self.height else '',
+ ' ! ',
+ 'fakesink signal-handoffs=True'])
+
+ _log.debug('thumbnail_pipeline: {0}'.format(pipeline))
+
+ self.thumbnail_pipeline = gst.parse_launch(pipeline)
+ self.thumbnail_message_bus = self.thumbnail_pipeline.get_bus()
+ self.thumbnail_message_bus.add_signal_watch()
+ self.thumbnail_bus_watch_id = self.thumbnail_message_bus.connect(
+ 'message',
+ self.on_thumbnail_message)
+
+ self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
+
+ gobject.timeout_add(3000, self.on_gobject_timeout)
+
+ return False
+
+ def on_thumbnail_message(self, message_bus, message):
+ # This is silenced to prevent clobbering of the terminal window
+ #_log.debug('thumbnail message: {0}'.format(message))
+
+ if message.type == gst.MESSAGE_ERROR:
+ _log.error('thumbnail error: {0}'.format(message.parse_error()))
+ gobject.idle_add(self.on_thumbnail_error, message)
+
+ if message.type == gst.MESSAGE_STATE_CHANGED:
+ prev_state, cur_state, pending_state = \
+ message.parse_state_changed()
+
+ _log.debug('thumbnail state changed: \nprev: {0}\ncur: {1}\n \
+pending: {2}'.format(
+ prev_state,
+ cur_state,
+ pending_state))
+
+ if cur_state == gst.STATE_PAUSED and \
+ not self.state == self.STATE_PROCESSING_THUMBNAIL:
+ # Find the fakesink sink pad and attach the on_buffer_probe
+ # handler to it.
+ seek_amount = self.position_callback(self.duration, gst)
+
+ seek_result = self.thumbnail_pipeline.seek(
+ 1.0,
+ gst.FORMAT_TIME,
+ gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
+ gst.SEEK_TYPE_SET,
+ seek_amount,
+ gst.SEEK_TYPE_NONE,
+ 0)
+
+ if not seek_result:
+ _log.info('Could not seek.')
+ else:
+ _log.info('Seek successful, attaching buffer probe')
+ self.state = self.STATE_PROCESSING_THUMBNAIL
+ for sink in self.thumbnail_pipeline.sinks():
+ sink_name = sink.get_name()
+ sink_factory_name = sink.get_factory().get_name()
+
+ if sink_factory_name == 'fakesink':
+ sink_pad = sink.get_pad('sink')
+
+ self.buffer_probes[sink_name] = sink_pad\
+ .add_buffer_probe(
+ self.on_pad_buffer_probe,
+ sink_name)
+
+ _log.info('Attached buffer probes: {0}'.format(
+ self.buffer_probes))
+
+ break
+
+
+ elif self.state == self.STATE_PROCESSING_THUMBNAIL:
+ _log.info('Already processing thumbnail')
+
+ def on_pad_buffer_probe(self, *args):
+ _log.debug('buffer probe handler: {0}'.format(args))
+ gobject.idle_add(lambda: self.take_snapshot(*args))
+
+ def take_snapshot(self, pad, buff, name):
+ if self.state == self.STATE_HALTING:
+ _log.debug('Pipeline is halting, will not take snapshot')
+ return False
+
+ _log.info('Taking snapshot! ({0})'.format(
+ (pad, buff, name)))
+ try:
+ caps = buff.caps
+ if caps is None:
+ _log.error('No buffer caps present /take_snapshot')
+ self.disconnect()
+
+ _log.debug('caps: {0}'.format(caps))
+
+ filters = caps[0]
+ width = filters['width']
+ height = filters['height']
+
+ im = Image.new('RGB', (width, height))
+
+ data = pixbuf_to_pilbuf(buff.data)
+
+ im.putdata(data)
+
+ im.save(self.dest_path)
+
+ _log.info('Saved snapshot!')
+
+ self.disconnect()
+
+ except gst.QueryError as exc:
+ _log.error('take_snapshot - QueryError: {0}'.format(exc))
+
+ return False
+
+ def on_thumbnail_error(self, message):
+ scaling_failed = False
+
+ if 'Error calculating the output scaled size - integer overflow' \
+ in message.parse_error()[1]:
+ # GStreamer videoscale sometimes fails to calculate the dimensions
+ # given only one of the destination dimensions and the source
+ # dimensions. This is a workaround in case videoscale returns an
+ # error that indicates this has happened.
+ scaling_failed = True
+ _log.error('Thumbnailing failed because of videoscale integer'
+ ' overflow. Will retry with fallback.')
+ else:
+ _log.error('Thumbnailing failed: {0}'.format(message.parse_error()))
+
+ # Kill the current mainloop
+ self.disconnect()
+
+ if scaling_failed:
+ # Manually scale the destination dimensions
+ _log.info('Retrying with manually set sizes...')
+
+ info = VideoTranscoder().discover(self.source_path)
+
+ h = info['videoheight']
+ w = info['videowidth']
+ ratio = 180 / int(w)
+ h = int(h * ratio)
+
+ self.__init__(self.source_path, self.dest_path, 180, h)
+
+ def disconnect(self):
+ self.state = self.STATE_HALTING
+
+ if self.playbin is not None:
+ self.playbin.set_state(gst.STATE_NULL)
+
+ for sink in self.playbin.sinks():
+ sink_name = sink.get_name()
+ sink_factory_name = sink.get_factory().get_name()
+
+ if sink_factory_name == 'fakesink':
+ sink_pad = sink.get_pad('sink')
+ sink_pad.remove_buffer_probe(self.buffer_probes[sink_name])
+ del self.buffer_probes[sink_name]
+
+ self.playbin = None
+
+ if self.thumbnail_pipeline is not None:
+ self.thumbnail_pipeline.set_state(gst.STATE_NULL)
+ self.thumbnail_pipeline = None
+
+ if self.playbin_message_bus is not None:
+ self.playbin_message_bus.disconnect(self.playbin_bus_watch_id)
+ self.playbin_message_bus = None
+
+ self.halt()
+
+ def halt(self):
+ gobject.idle_add(self.mainloop.quit)
+
+ def on_gobject_timeout(self):
+ _log.critical('Reached gobject timeout')
+ self.disconnect()
+
+ def get_duration(self, pipeline, attempt=1):
+ if attempt == 5:
+ _log.critical('Pipeline duration query retry limit reached.')
+ return 0
+
+ try:
+ return pipeline.query_duration(gst.FORMAT_TIME)[0]
+ except gst.QueryError as exc:
+ _log.error('Could not get duration on attempt {0}: {1}'.format(
+ attempt,
+ exc))
+ return self.get_duration(pipeline, attempt + 1)
+
+
+class VideoTranscoder(object):
+ '''
+ Video transcoder
+
+ Transcodes the SRC video file to a VP8 WebM video file at DST
+
+ - Does the same thing as VideoThumbnailer, but produces a WebM vp8
+ and vorbis video file.
+ - The VideoTranscoder exceeds the VideoThumbnailer in the way
+ that it was refined afterwards and therefore is done more
+ correctly.
+ '''
+ def __init__(self):
+ _log.info('Initializing VideoTranscoder...')
+ self.progress_percentage = None
+ self.loop = gobject.MainLoop()
+
+ def transcode(self, src, dst, **kwargs):
+ '''
+ Transcode a video file into a 'medium'-sized version.
+ '''
+ self.source_path = src
+ self.destination_path = dst
+
+ # vp8enc options
+ self.destination_dimensions = kwargs.get('dimensions', (640, 640))
+ self.vp8_quality = kwargs.get('vp8_quality', 8)
+ # Number of threads used by vp8enc:
+ # number of real cores - 1 as per recommendation on
+ # <http://www.webmproject.org/tools/encoder-parameters/#6-multi-threaded-encode-and-decode>
+ self.vp8_threads = kwargs.get('vp8_threads', CPU_COUNT - 1)
+
+ # 0 means auto-detect, but dict.get() only falls back to CPU_COUNT
+ # if value is None, this will correct our incompatibility with
+ # dict.get()
+ # This will also correct cases where there's only 1 CPU core, see
+ # original self.vp8_threads assignment above.
+ if self.vp8_threads == 0:
+ self.vp8_threads = CPU_COUNT
+
+ # vorbisenc options
+ self.vorbis_quality = kwargs.get('vorbis_quality', 0.3)
+
+ self._progress_callback = kwargs.get('progress_callback') or None
+
+ if not type(self.destination_dimensions) == tuple:
+ raise Exception('dimensions must be tuple: (width, height)')
+
+ self._setup()
+ self._run()
+
+ # XXX: This could be a static method.
+ def discover(self, src):
+ '''
+ Discover properties about a media file
+ '''
+ _log.info('Discovering {0}'.format(src))
+
+ self.source_path = src
+ self._setup_discover(discovered_callback=self.__on_discovered)
+
+ self.discoverer.discover()
+
+ self.loop.run()
+
+ if hasattr(self, '_discovered_data'):
+ return self._discovered_data.__dict__
+ else:
+ return None
+
+ def __on_discovered(self, data, is_media):
+ _log.debug('Discovered: {0}'.format(data))
+ if not is_media:
+ self.__stop()
+ raise Exception('Could not discover {0}'.format(self.source_path))
+
+ self._discovered_data = data
+
+ self.__stop_mainloop()
+
+ def _setup(self):
+ self._setup_discover()
+ self._setup_pipeline()
+
+ def _run(self):
+ _log.info('Discovering...')
+ self.discoverer.discover()
+ _log.info('Done')
+
+ _log.debug('Initializing MainLoop()')
+ self.loop.run()
+
+ def _setup_discover(self, **kw):
+ _log.debug('Setting up discoverer')
+ self.discoverer = discoverer.Discoverer(self.source_path)
+
+ # Connect self.__discovered to the 'discovered' event
+ self.discoverer.connect(
+ 'discovered',
+ kw.get('discovered_callback', self.__discovered))
+
+ def __discovered(self, data, is_media):
+ '''
+ Callback for media discoverer.
+ '''
+ if not is_media:
+ self.__stop()
+ raise Exception('Could not discover {0}'.format(self.source_path))
+
+ _log.debug('__discovered, data: {0}'.format(data.__dict__))
+
+ self.data = data
+
+ # Launch things that should be done after discovery
+ self._link_elements()
+ self.__setup_videoscale_capsfilter()
+
+ # Tell the transcoding pipeline to start running
+ self.pipeline.set_state(gst.STATE_PLAYING)
+ _log.info('Transcoding...')
+
+ def _setup_pipeline(self):
+ _log.debug('Setting up transcoding pipeline')
+ # Create the pipeline bin.
+ self.pipeline = gst.Pipeline('VideoTranscoderPipeline')
+
+ # Create all GStreamer elements, starting with
+ # filesrc & decoder
+ self.filesrc = gst.element_factory_make('filesrc', 'filesrc')
+ self.filesrc.set_property('location', self.source_path)
+ self.pipeline.add(self.filesrc)
+
+ self.decoder = gst.element_factory_make('decodebin2', 'decoder')
+ self.decoder.connect('new-decoded-pad', self._on_dynamic_pad)
+ self.pipeline.add(self.decoder)
+
+ # Video elements
+ self.videoqueue = gst.element_factory_make('queue', 'videoqueue')
+ self.pipeline.add(self.videoqueue)
+
+ self.videorate = gst.element_factory_make('videorate', 'videorate')
+ self.pipeline.add(self.videorate)
+
+ self.ffmpegcolorspace = gst.element_factory_make(
+ 'ffmpegcolorspace', 'ffmpegcolorspace')
+ self.pipeline.add(self.ffmpegcolorspace)
+
+ self.videoscale = gst.element_factory_make('ffvideoscale', 'videoscale')
+ #self.videoscale.set_property('method', 2) # I'm not sure this works
+ #self.videoscale.set_property('add-borders', 0)
+ self.pipeline.add(self.videoscale)
+
+ self.capsfilter = gst.element_factory_make('capsfilter', 'capsfilter')
+ self.pipeline.add(self.capsfilter)
+
+ self.vp8enc = gst.element_factory_make('vp8enc', 'vp8enc')
+ self.vp8enc.set_property('quality', self.vp8_quality)
+ self.vp8enc.set_property('threads', self.vp8_threads)
+ self.vp8enc.set_property('max-latency', 25)
+ self.pipeline.add(self.vp8enc)
+
+ # Audio elements
+ self.audioqueue = gst.element_factory_make('queue', 'audioqueue')
+ self.pipeline.add(self.audioqueue)
+
+ self.audiorate = gst.element_factory_make('audiorate', 'audiorate')
+ self.audiorate.set_property('tolerance', 80000000)
+ self.pipeline.add(self.audiorate)
+
+ self.audioconvert = gst.element_factory_make('audioconvert', 'audioconvert')
+ self.pipeline.add(self.audioconvert)
+
+ self.audiocapsfilter = gst.element_factory_make('capsfilter',
+ 'audiocapsfilter')
+ audiocaps = ['audio/x-raw-float']
+ self.audiocapsfilter.set_property(
+ 'caps',
+ gst.caps_from_string(
+ ','.join(audiocaps)))
+ self.pipeline.add(self.audiocapsfilter)
+
+ self.vorbisenc = gst.element_factory_make('vorbisenc', 'vorbisenc')
+ self.vorbisenc.set_property('quality', self.vorbis_quality)
+ self.pipeline.add(self.vorbisenc)
+
+ # WebMmux & filesink
+ self.webmmux = gst.element_factory_make('webmmux', 'webmmux')
+ self.pipeline.add(self.webmmux)
+
+ self.filesink = gst.element_factory_make('filesink', 'filesink')
+ self.filesink.set_property('location', self.destination_path)
+ self.pipeline.add(self.filesink)
+
+ # Progressreport
+ self.progressreport = gst.element_factory_make(
+ 'progressreport', 'progressreport')
+ # Update every second
+ self.progressreport.set_property('update-freq', 1)
+ self.progressreport.set_property('silent', True)
+ self.pipeline.add(self.progressreport)
+
+ def _link_elements(self):
+ '''
+ Link all the elements
+
+ This code depends on data from the discoverer and is called
+ from __discovered
+ '''
+ _log.debug('linking elements')
+ # Link the filesrc element to the decoder. The decoder then emits
+ # 'new-decoded-pad' which links decoded src pads to either a video
+ # or audio sink
+ self.filesrc.link(self.decoder)
+
+ # Link all the video elements in a row to webmmux
+ gst.element_link_many(
+ self.videoqueue,
+ self.videorate,
+ self.ffmpegcolorspace,
+ self.videoscale,
+ self.capsfilter,
+ self.vp8enc,
+ self.webmmux)
+
+ if self.data.is_audio:
+ # Link all the audio elements in a row to webmux
+ gst.element_link_many(
+ self.audioqueue,
+ self.audiorate,
+ self.audioconvert,
+ self.audiocapsfilter,
+ self.vorbisenc,
+ self.webmmux)
+
+ gst.element_link_many(
+ self.webmmux,
+ self.progressreport,
+ self.filesink)
+
+ # Setup the message bus and connect _on_message to the pipeline
+ self._setup_bus()
+
+ def _on_dynamic_pad(self, dbin, pad, islast):
+ '''
+ Callback called when ``decodebin2`` has a pad that we can connect to
+ '''
+ # Intersect the capabilities of the video sink and the pad src
+ # Then check if they have no common capabilities.
+ if self.ffmpegcolorspace.get_pad_template('sink')\
+ .get_caps().intersect(pad.get_caps()).is_empty():
+ # It is NOT a video src pad.
+ pad.link(self.audioqueue.get_pad('sink'))
+ else:
+ # It IS a video src pad.
+ pad.link(self.videoqueue.get_pad('sink'))
+
+ def _setup_bus(self):
+ self.bus = self.pipeline.get_bus()
+ self.bus.add_signal_watch()
+ self.bus.connect('message', self._on_message)
+
+ def __setup_videoscale_capsfilter(self):
+ '''
+ Sets up the output format (width, height) for the video
+ '''
+ caps = ['video/x-raw-yuv', 'pixel-aspect-ratio=1/1', 'framerate=30/1']
+
+ if self.data.videoheight > self.data.videowidth:
+ # Whoa! We have ourselves a portrait video!
+ caps.append('height={0}'.format(
+ self.destination_dimensions[1]))
+ else:
+ # It's a landscape, phew, how normal.
+ caps.append('width={0}'.format(
+ self.destination_dimensions[0]))
+
+ self.capsfilter.set_property(
+ 'caps',
+ gst.caps_from_string(
+ ','.join(caps)))
+
+ def _on_message(self, bus, message):
+ _log.debug((bus, message, message.type))
+
+ t = message.type
+
+ if message.type == gst.MESSAGE_EOS:
+ self._discover_dst_and_stop()
+ _log.info('Done')
+
+ elif message.type == gst.MESSAGE_ELEMENT:
+ if message.structure.get_name() == 'progress':
+ data = dict(message.structure)
+ # Update progress state if it has changed
+ if self.progress_percentage != data.get('percent'):
+ self.progress_percentage = data.get('percent')
+ if self._progress_callback:
+ self._progress_callback(data.get('percent'))
+
+ _log.info('{percent}% done...'.format(
+ percent=data.get('percent')))
+ _log.debug(data)
+
+ elif t == gst.MESSAGE_ERROR:
+ _log.error((bus, message))
+ self.__stop()
+
+ def _discover_dst_and_stop(self):
+ self.dst_discoverer = discoverer.Discoverer(self.destination_path)
+
+ self.dst_discoverer.connect('discovered', self.__dst_discovered)
+
+ self.dst_discoverer.discover()
+
+ def __dst_discovered(self, data, is_media):
+ self.dst_data = data
+
+ self.__stop()
+
+ def __stop(self):
+ _log.debug(self.loop)
+
+ if hasattr(self, 'pipeline'):
+ # Stop executing the pipeline
+ self.pipeline.set_state(gst.STATE_NULL)
+
+ # This kills the loop, mercifully
+ gobject.idle_add(self.__stop_mainloop)
+
+ def __stop_mainloop(self):
+ '''
+ Wrapper for gobject.MainLoop.quit()
+
+ This wrapper makes us able to see if self.loop.quit has been called
+ '''
+ _log.info('Terminating MainLoop')
+
+ self.loop.quit()
+
+
+if __name__ == '__main__':
+ os.nice(19)
+ logging.basicConfig()
+ from optparse import OptionParser
+
+ parser = OptionParser(
+ usage='%prog [-v] -a [ video | thumbnail | discover ] SRC [ DEST ]')
+
+ parser.add_option('-a', '--action',
+ dest='action',
+ help='One of "video", "discover" or "thumbnail"')
+
+ parser.add_option('-v',
+ dest='verbose',
+ action='store_true',
+ help='Output debug information')
+
+ parser.add_option('-q',
+ dest='quiet',
+ action='store_true',
+ help='Dear program, please be quiet unless *error*')
+
+ parser.add_option('-w', '--width',
+ type=int,
+ default=180)
+
+ (options, args) = parser.parse_args()
+
+ if options.verbose:
+ _log.setLevel(logging.DEBUG)
+ else:
+ _log.setLevel(logging.INFO)
+
+ if options.quiet:
+ _log.setLevel(logging.ERROR)
+
+ _log.debug(args)
+
+ if not len(args) == 2 and not options.action == 'discover':
+ parser.print_help()
+ sys.exit()
+
+ transcoder = VideoTranscoder()
+
+ if options.action == 'thumbnail':
+ args.append(options.width)
+ VideoThumbnailerMarkII(*args)
+ elif options.action == 'video':
+ def cb(data):
+ print('I\'m a callback!')
+ transcoder.transcode(*args, progress_callback=cb)
+ elif options.action == 'discover':
+ print transcoder.discover(*args)
diff --git a/mediagoblin/media_types/video/util.py b/mediagoblin/media_types/video/util.py
new file mode 100644
index 00000000..5765ecfb
--- /dev/null
+++ b/mediagoblin/media_types/video/util.py
@@ -0,0 +1,59 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+from mediagoblin import mg_globals as mgg
+
+_log = logging.getLogger(__name__)
+
+
+def skip_transcode(metadata):
+ '''
+ Checks video metadata against configuration values for skip_transcode.
+
+ Returns True if the video matches the requirements in the configuration.
+ '''
+ config = mgg.global_config['media_type:mediagoblin.media_types.video']\
+ ['skip_transcode']
+
+ medium_config = mgg.global_config['media:medium']
+
+ _log.debug('skip_transcode config: {0}'.format(config))
+
+ if config['mime_types'] and metadata.get('mimetype'):
+ if not metadata['mimetype'] in config['mime_types']:
+ return False
+
+ if config['container_formats'] and metadata['tags'].get('audio-codec'):
+ if not metadata['tags']['container-format'] in config['container_formats']:
+ return False
+
+ if config['video_codecs'] and metadata['tags'].get('audio-codec'):
+ if not metadata['tags']['video-codec'] in config['video_codecs']:
+ return False
+
+ if config['audio_codecs'] and metadata['tags'].get('audio-codec'):
+ if not metadata['tags']['audio-codec'] in config['audio_codecs']:
+ return False
+
+ if config['dimensions_match']:
+ if not metadata['videoheight'] <= medium_config['max_height']:
+ return False
+ if not metadata['videowidth'] <= medium_config['max_width']:
+ return False
+
+ return True
diff --git a/mediagoblin/messages.py b/mediagoblin/messages.py
new file mode 100644
index 00000000..d58f13d4
--- /dev/null
+++ b/mediagoblin/messages.py
@@ -0,0 +1,50 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools import common
+
+DEBUG = 'debug'
+INFO = 'info'
+SUCCESS = 'success'
+WARNING = 'warning'
+ERROR = 'error'
+
+ADD_MESSAGE_TEST = []
+
+
+def add_message(request, level, text):
+ messages = request.session.setdefault('messages', [])
+ messages.append({'level': level, 'text': text})
+
+ if common.TESTS_ENABLED:
+ ADD_MESSAGE_TEST.append(messages)
+
+ request.session.save()
+
+
+def fetch_messages(request, clear_from_session=True):
+ messages = request.session.get('messages')
+ if messages and clear_from_session:
+ # Save that we removed the messages from the session
+ request.session['messages'] = []
+ request.session.save()
+
+ return messages
+
+
+def clear_add_message():
+ global ADD_MESSAGE_TEST
+ ADD_MESSAGE_TEST = []
diff --git a/mediagoblin/mg_globals.py b/mediagoblin/mg_globals.py
new file mode 100644
index 00000000..26ed66fa
--- /dev/null
+++ b/mediagoblin/mg_globals.py
@@ -0,0 +1,70 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+In some places, we need to access the database, public_store, queue_store
+"""
+
+import gettext
+import pkg_resources
+import threading
+
+
+#############################
+# General mediagoblin globals
+#############################
+
+# SQL database engine
+database = None
+
+# should be the same as the
+public_store = None
+queue_store = None
+
+# A WorkBenchManager
+workbench_manager = None
+
+# A thread-local scope
+thread_scope = threading.local()
+
+# gettext (this needs to default to English so it doesn't break
+# in case we're running a script without the app like
+# ./bin/gmg theme assetlink)
+thread_scope.translations = gettext.translation(
+ 'mediagoblin',
+ pkg_resources.resource_filename(
+ 'mediagoblin', 'i18n'), ['en'], fallback=True)
+
+# app and global config objects
+app_config = None
+global_config = None
+
+# The actual app object
+app = None
+
+
+def setup_globals(**kwargs):
+ """
+ Sets up a bunch of globals in this module.
+
+ Takes the globals to setup as keyword arguments. If globals are
+ specified that aren't set as variables above, then throw an error.
+ """
+ from mediagoblin import mg_globals
+
+ for key, value in kwargs.iteritems():
+ if not hasattr(mg_globals, key):
+ raise AssertionError("Global %s not known" % key)
+ setattr(mg_globals, key, value)
diff --git a/mediagoblin/plugins/README b/mediagoblin/plugins/README
new file mode 100644
index 00000000..a2b92334
--- /dev/null
+++ b/mediagoblin/plugins/README
@@ -0,0 +1,6 @@
+========
+ README
+========
+
+This directory holds the MediaGoblin core plugins. These plugins are not
+enabled by default. See documentation for enabling plugins.
diff --git a/mediagoblin/plugins/__init__.py b/mediagoblin/plugins/__init__.py
new file mode 100644
index 00000000..719b56e7
--- /dev/null
+++ b/mediagoblin/plugins/__init__.py
@@ -0,0 +1,16 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
diff --git a/mediagoblin/plugins/api/__init__.py b/mediagoblin/plugins/api/__init__.py
new file mode 100644
index 00000000..1eddd9e0
--- /dev/null
+++ b/mediagoblin/plugins/api/__init__.py
@@ -0,0 +1,47 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+from mediagoblin.tools import pluginapi
+
+_log = logging.getLogger(__name__)
+
+PLUGIN_DIR = os.path.dirname(__file__)
+
+def setup_plugin():
+ _log.info('Setting up API...')
+
+ config = pluginapi.get_config(__name__)
+
+ _log.debug('API config: {0}'.format(config))
+
+ routes = [
+ ('mediagoblin.plugins.api.test',
+ '/api/test',
+ 'mediagoblin.plugins.api.views:api_test'),
+ ('mediagoblin.plugins.api.entries',
+ '/api/entries',
+ 'mediagoblin.plugins.api.views:get_entries'),
+ ('mediagoblin.plugins.api.post_entry',
+ '/api/submit',
+ 'mediagoblin.plugins.api.views:post_entry')]
+
+ pluginapi.register_routes(routes)
+
+hooks = {
+ 'setup': setup_plugin}
diff --git a/mediagoblin/plugins/api/tools.py b/mediagoblin/plugins/api/tools.py
new file mode 100644
index 00000000..92411f4b
--- /dev/null
+++ b/mediagoblin/plugins/api/tools.py
@@ -0,0 +1,164 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import json
+
+from functools import wraps
+from urlparse import urljoin
+from werkzeug.exceptions import Forbidden
+from werkzeug.wrappers import Response
+from mediagoblin import mg_globals
+from mediagoblin.tools.pluginapi import PluginManager
+from mediagoblin.storage.filestorage import BasicFileStorage
+
+_log = logging.getLogger(__name__)
+
+
+class Auth(object):
+ '''
+ An object with two significant methods, 'trigger' and 'run'.
+
+ Using a similar object to this, plugins can register specific
+ authentication logic, for example the GET param 'access_token' for OAuth.
+
+ - trigger: Analyze the 'request' argument, return True if you think you
+ can handle the request, otherwise return False
+ - run: The authentication logic, set the request.user object to the user
+ you intend to authenticate and return True, otherwise return False.
+
+ If run() returns False, an HTTP 403 Forbidden error will be shown.
+
+ You may also display custom errors, just raise them within the run()
+ method.
+ '''
+ def trigger(self, request):
+ raise NotImplemented()
+
+ def __call__(self, request, *args, **kw):
+ raise NotImplemented()
+
+
+def json_response(serializable, _disable_cors=False, *args, **kw):
+ '''
+ Serializes a json objects and returns a werkzeug Response object with the
+ serialized value as the response body and Content-Type: application/json.
+
+ :param serializable: A json-serializable object
+
+ Any extra arguments and keyword arguments are passed to the
+ Response.__init__ method.
+ '''
+ response = Response(json.dumps(serializable), *args, content_type='application/json', **kw)
+
+ if not _disable_cors:
+ cors_headers = {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'}
+ for key, value in cors_headers.iteritems():
+ response.headers.set(key, value)
+
+ return response
+
+
+def get_entry_serializable(entry, urlgen):
+ '''
+ Returns a serializable dict() of a MediaEntry instance.
+
+ :param entry: A MediaEntry instance
+ :param urlgen: An urlgen instance, can be found on the request object passed
+ to views.
+ '''
+ return {
+ 'user': entry.get_uploader.username,
+ 'user_id': entry.get_uploader.id,
+ 'user_bio': entry.get_uploader.bio,
+ 'user_bio_html': entry.get_uploader.bio_html,
+ 'user_permalink': urlgen('mediagoblin.user_pages.user_home',
+ user=entry.get_uploader.username,
+ qualified=True),
+ 'id': entry.id,
+ 'created': entry.created.isoformat(),
+ 'title': entry.title,
+ 'license': entry.license,
+ 'description': entry.description,
+ 'description_html': entry.description_html,
+ 'media_type': entry.media_type,
+ 'state': entry.state,
+ 'permalink': entry.url_for_self(urlgen, qualified=True),
+ 'media_files': get_media_file_paths(entry.media_files, urlgen)}
+
+
+def get_media_file_paths(media_files, urlgen):
+ '''
+ Returns a dictionary of media files with `file_handle` => `qualified URL`
+
+ :param media_files: dict-like object consisting of `file_handle => `listy
+ filepath` pairs.
+ :param urlgen: An urlgen object, usually found on request.urlgen.
+ '''
+ media_urls = {}
+
+ for key, val in media_files.items():
+ if isinstance(mg_globals.public_store, BasicFileStorage):
+ # BasicFileStorage does not provide a qualified URI
+ media_urls[key] = urljoin(
+ urlgen('index', qualified=True),
+ mg_globals.public_store.file_url(val))
+ else:
+ media_urls[key] = mg_globals.public_store.file_url(val)
+
+ return media_urls
+
+
+def api_auth(controller):
+ '''
+ Decorator, allows plugins to register auth methods that will then be
+ evaluated against the request, finally a worthy authenticator object is
+ chosen and used to decide whether to grant or deny access.
+ '''
+ @wraps(controller)
+ def wrapper(request, *args, **kw):
+ auth_candidates = []
+
+ for auth in PluginManager().get_hook_callables('auth'):
+ if auth.trigger(request):
+ _log.debug('{0} believes it is capable of authenticating this request.'.format(auth))
+ auth_candidates.append(auth)
+
+ # If we can't find any authentication methods, we should not let them
+ # pass.
+ if not auth_candidates:
+ raise Forbidden()
+
+ # For now, just select the first one in the list
+ auth = auth_candidates[0]
+
+ _log.debug('Using {0} to authorize request {1}'.format(
+ auth, request.url))
+
+ if not auth(request, *args, **kw):
+ if getattr(auth, 'errors', []):
+ return json_response({
+ 'status': 403,
+ 'errors': auth.errors})
+
+ raise Forbidden()
+
+ return controller(request, *args, **kw)
+
+ return wrapper
diff --git a/mediagoblin/plugins/api/views.py b/mediagoblin/plugins/api/views.py
new file mode 100644
index 00000000..9159fe65
--- /dev/null
+++ b/mediagoblin/plugins/api/views.py
@@ -0,0 +1,122 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import logging
+
+from os.path import splitext
+from werkzeug.exceptions import BadRequest, Forbidden
+from werkzeug.wrappers import Response
+
+from mediagoblin.decorators import require_active_login
+from mediagoblin.meddleware.csrf import csrf_exempt
+from mediagoblin.media_types import sniff_media
+from mediagoblin.plugins.api.tools import api_auth, get_entry_serializable, \
+ json_response
+from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \
+ run_process_media, new_upload_entry
+
+_log = logging.getLogger(__name__)
+
+
+@csrf_exempt
+@api_auth
+@require_active_login
+def post_entry(request):
+ _log.debug('Posting entry')
+
+ if request.method == 'OPTIONS':
+ return json_response({'status': 200})
+
+ if request.method != 'POST':
+ _log.debug('Must POST against post_entry')
+ raise BadRequest()
+
+ if not check_file_field(request, 'file'):
+ _log.debug('File field not found')
+ raise BadRequest()
+
+ media_file = request.files['file']
+
+ media_type, media_manager = sniff_media(media_file)
+
+ entry = new_upload_entry(request.user)
+ entry.media_type = unicode(media_type)
+ entry.title = unicode(request.form.get('title')
+ or splitext(media_file.filename)[0])
+
+ entry.description = unicode(request.form.get('description'))
+ entry.license = unicode(request.form.get('license', ''))
+
+ entry.generate_slug()
+
+ # queue appropriately
+ queue_file = prepare_queue_task(request.app, entry, media_file.filename)
+
+ with queue_file:
+ queue_file.write(request.files['file'].stream.read())
+
+ # Save now so we have this data before kicking off processing
+ entry.save()
+
+ if request.form.get('callback_url'):
+ metadata = request.db.ProcessingMetaData()
+ metadata.media_entry = entry
+ metadata.callback_url = unicode(request.form['callback_url'])
+ metadata.save()
+
+ # Pass off to processing
+ #
+ # (... don't change entry after this point to avoid race
+ # conditions with changes to the document via processing code)
+ feed_url = request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ qualified=True, user=request.user.username)
+ run_process_media(entry, feed_url)
+
+ return json_response(get_entry_serializable(entry, request.urlgen))
+
+
+@api_auth
+@require_active_login
+def api_test(request):
+ user_data = {
+ 'username': request.user.username,
+ 'email': request.user.email}
+
+ # TODO: This is the *only* thing using Response() here, should that
+ # not simply use json_response()?
+ return Response(json.dumps(user_data))
+
+
+def get_entries(request):
+ entries = request.db.MediaEntry.query
+
+ # TODO: Make it possible to fetch unprocessed media, or media in-processing
+ entries = entries.filter_by(state=u'processed')
+
+ # TODO: Add sort order customization
+ entries = entries.order_by(request.db.MediaEntry.created.desc())
+
+ # TODO: Fetch default and upper limit from config
+ entries = entries.limit(int(request.GET.get('limit') or 10))
+
+ entries_serializable = []
+
+ for entry in entries:
+ entries_serializable.append(get_entry_serializable(entry, request.urlgen))
+
+ return json_response(entries_serializable)
diff --git a/mediagoblin/plugins/flatpagesfile/README.rst b/mediagoblin/plugins/flatpagesfile/README.rst
new file mode 100644
index 00000000..59cd6217
--- /dev/null
+++ b/mediagoblin/plugins/flatpagesfile/README.rst
@@ -0,0 +1,163 @@
+.. _flatpagesfile-chapter:
+
+======================
+ flatpagesfile plugin
+======================
+
+This is the flatpages file plugin. It allows you to add pages to your
+MediaGoblin instance which are not generated from user content. For
+example, this is useful for these pages:
+
+* About this site
+* Terms of service
+* Privacy policy
+* How to get an account here
+* ...
+
+
+How to configure
+================
+
+Add the following to your MediaGoblin .ini file in the ``[plugins]``
+section::
+
+ [[mediagoblin.plugins.flatpagesfile]]
+
+
+This tells MediaGoblin to load the flatpagesfile plugin. This is the
+subsection that you'll do all flatpagesfile plugin configuration in.
+
+
+How to add pages
+================
+
+To add a new page to your site, you need to do two things:
+
+1. add a route to the MediaGoblin .ini file in the flatpagesfile
+ subsection
+
+2. write a template that will get served when that route is requested
+
+
+Routes
+------
+
+First, let's talk about the route.
+
+A route is a key/value in your configuration file.
+
+The key for the route is the route name You can use this with `url()`
+in templates to have MediaGoblin automatically build the urls for
+you. It's very handy.
+
+It should be "unique" and it should be alphanumeric characters and
+hyphens. I wouldn't put spaces in there.
+
+Examples: ``flatpages-about``, ``about-view``, ``contact-view``, ...
+
+The value has two parts separated by commas:
+
+1. **route path**: This is the url that this route matches.
+
+ Examples: ``/about``, ``/contact``, ``/pages/about``, ...
+
+ You can do anything with this that you can do with the routepath
+ parameter of `routes.Route`. For more details, see `the routes
+ documentation <http://routes.readthedocs.org/en/latest/>`_.
+
+ Example: ``/siteadmin/{adminname:\w+}``
+
+ .. Note::
+
+ If you're doing something fancy, enclose the route in single
+ quotes.
+
+ For example: ``'/siteadmin/{adminname:\w+}'``
+
+2. **template**: The template to use for this url. The template is in
+ the flatpagesfile template directory, so you just need to specify
+ the file name.
+
+ Like with other templates, if it's an HTML file, it's good to use
+ the ``.html`` extensions.
+
+ Examples: ``index.html``, ``about.html``, ``contact.html``, ...
+
+
+Here's an example configuration that adds two flat pages: one for an
+"About this site" page and one for a "Terms of service" page::
+
+ [[mediagoblin.plugins.flatpagesfile]]
+ about-view = '/about', about.html
+ terms-view = '/terms', terms.html
+
+
+.. Note::
+
+ The order in which you define the routes in the config file is the
+ order in which they're checked for incoming requests.
+
+
+Templates
+---------
+
+To add pages, you must edit template files on the file system in your
+`local_templates` directory.
+
+The directory structure looks kind of like this::
+
+ local_templates
+ |- flatpagesfile
+ |- flatpage1.html
+ |- flatpage2.html
+ |- ...
+
+
+The ``.html`` file contains the content of your page. It's just a
+template like all the other templates you have.
+
+Here's an example that extends the `flatpagesfile/base.html`
+template::
+
+ {% extends "flatpagesfile/base.html" %}
+ {% block mediagoblin_content %}
+ <h1>About this site</h1>
+ <p>
+ This site is a MediaGoblin instance set up to host media for
+ me, my family and my friends.
+ </p>
+ {% endblock %}
+
+
+.. Note::
+
+ If you have a bunch of flatpages that kind of look like one
+ another, take advantage of Jinja2 template extending and create a
+ base template that the others extend.
+
+
+Recipes
+=======
+
+Url variables
+-------------
+
+You can handle urls like ``/about/{name}`` and access the name that's
+passed in in the template.
+
+Sample route::
+
+ about-page = '/about/{name}', about.html
+
+Sample template::
+
+ {% extends "flatpagesfile/base.html" %}
+ {% block mediagoblin_content %}
+
+ <h1>About page for {{ request.matchdict['name'] }}</h1>
+
+ {% endblock %}
+
+See the `the routes documentation
+<http://routes.readthedocs.org/en/latest/>`_ for syntax details for
+the route. Values will end up in the ``request.matchdict`` dict.
diff --git a/mediagoblin/plugins/flatpagesfile/__init__.py b/mediagoblin/plugins/flatpagesfile/__init__.py
new file mode 100644
index 00000000..3d797809
--- /dev/null
+++ b/mediagoblin/plugins/flatpagesfile/__init__.py
@@ -0,0 +1,78 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import logging
+import os
+
+import jinja2
+
+from mediagoblin.tools import pluginapi
+from mediagoblin.tools.response import render_to_response
+
+
+PLUGIN_DIR = os.path.dirname(__file__)
+
+
+_log = logging.getLogger(__name__)
+
+
+@jinja2.contextfunction
+def print_context(c):
+ s = []
+ for key, val in c.items():
+ s.append('%s: %s' % (key, repr(val)))
+ return '\n'.join(s)
+
+
+def flatpage_handler_builder(template):
+ """Flatpage view generator
+
+ Given a template, generates the controller function for handling that
+ route.
+
+ """
+ def _flatpage_handler_builder(request):
+ return render_to_response(
+ request, 'flatpagesfile/%s' % template,
+ {'request': request})
+ return _flatpage_handler_builder
+
+
+def setup_plugin():
+ config = pluginapi.get_config('mediagoblin.plugins.flatpagesfile')
+
+ _log.info('Setting up flatpagesfile....')
+
+ # Register the template path.
+ pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates'))
+
+ pages = config.items()
+
+ routes = []
+ for name, (url, template) in pages:
+ name = 'flatpagesfile.%s' % name.strip()
+ controller = flatpage_handler_builder(template)
+ routes.append(
+ (name, url, controller))
+
+ pluginapi.register_routes(routes)
+ _log.info('Done setting up flatpagesfile!')
+
+
+hooks = {
+ 'setup': setup_plugin
+ }
diff --git a/mediagoblin/plugins/flatpagesfile/templates/flatpagesfile/base.html b/mediagoblin/plugins/flatpagesfile/templates/flatpagesfile/base.html
new file mode 100644
index 00000000..1cf9dd9d
--- /dev/null
+++ b/mediagoblin/plugins/flatpagesfile/templates/flatpagesfile/base.html
@@ -0,0 +1,18 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+{% extends "mediagoblin/base.html" %}
diff --git a/mediagoblin/plugins/geolocation/__init__.py b/mediagoblin/plugins/geolocation/__init__.py
new file mode 100644
index 00000000..5d14590e
--- /dev/null
+++ b/mediagoblin/plugins/geolocation/__init__.py
@@ -0,0 +1,35 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools import pluginapi
+import os
+
+PLUGIN_DIR = os.path.dirname(__file__)
+
+def setup_plugin():
+ config = pluginapi.get_config('mediagoblin.plugins.geolocation')
+
+ # Register the template path.
+ pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates'))
+
+ pluginapi.register_template_hooks(
+ {"image_sideinfo": "mediagoblin/plugins/geolocation/map.html",
+ "image_head": "mediagoblin/plugins/geolocation/map_js_head.html"})
+
+
+hooks = {
+ 'setup': setup_plugin
+ }
diff --git a/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html
new file mode 100644
index 00000000..70f837ff
--- /dev/null
+++ b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html
@@ -0,0 +1,59 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block geolocation_map %}
+ {% if media.media_data.gps_latitude is defined
+ and media.media_data.gps_latitude
+ and media.media_data.gps_longitude is defined
+ and media.media_data.gps_longitude %}
+ <h3>{% trans %}Location{% endtrans %}</h3>
+ <div>
+ {%- set lon = media.media_data.gps_longitude %}
+ {%- set lat = media.media_data.gps_latitude %}
+ {%- set osm_url = "http://openstreetmap.org/?mlat={lat}&mlon={lon}".format(lat=lat, lon=lon) %}
+ <div id="tile-map" style="width: 100%; height: 196px;">
+ <input type="hidden" id="gps-longitude"
+ value="{{ lon }}" />
+ <input type="hidden" id="gps-latitude"
+ value="{{ lat }}" />
+ </div>
+ <script> <!-- pop up full OSM license when clicked -->
+ $(document).ready(function(){
+ $("#osm_license_link").click(function () {
+ $("#osm_attrib").slideToggle("slow");
+ });
+ });
+ </script>
+ <div id="osm_attrib" class="hidden"><ul><li>
+ Data &copy;<a
+ href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
+ contributors
+ </li><li>Imaging &copy;<a
+ href="http://mapquest.com">MapQuest</a></li><li>Maps powered by
+ <a href="http://leafletjs.com/"> Leaflet</a></li></ul>
+ </div>
+ <p>
+ <small>
+ {% trans -%}
+ View on <a href="{{ osm_url }}">OpenStreetMap</a>
+ {%- endtrans %}
+ </small>
+ </p>
+ </div>
+ {% endif %}
+{% endblock %}
diff --git a/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map_js_head.html b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map_js_head.html
new file mode 100644
index 00000000..aca0f730
--- /dev/null
+++ b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map_js_head.html
@@ -0,0 +1,25 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+<link rel="stylesheet"
+ href="{{ request.staticdirect('/extlib/leaflet/leaflet.css') }}" />
+
+<script type="text/javascript"
+ src="{{ request.staticdirect('/extlib/leaflet/leaflet.js') }}"></script>
+<script type="text/javascript"
+ src="{{ request.staticdirect('/js/geolocation-map.js') }}"></script>
diff --git a/mediagoblin/plugins/httpapiauth/__init__.py b/mediagoblin/plugins/httpapiauth/__init__.py
new file mode 100644
index 00000000..2b2d593c
--- /dev/null
+++ b/mediagoblin/plugins/httpapiauth/__init__.py
@@ -0,0 +1,58 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+from werkzeug.exceptions import Unauthorized
+
+from mediagoblin.auth.tools import check_login_simple
+from mediagoblin.plugins.api.tools import Auth
+
+_log = logging.getLogger(__name__)
+
+
+def setup_http_api_auth():
+ _log.info('Setting up HTTP API Auth...')
+
+
+class HTTPAuth(Auth):
+ def trigger(self, request):
+ if request.authorization:
+ return True
+
+ return False
+
+ def __call__(self, request, *args, **kw):
+ _log.debug('Trying to authorize the user agent via HTTP Auth')
+ if not request.authorization:
+ return False
+
+ user = check_login_simple(unicode(request.authorization['username']),
+ request.authorization['password'])
+
+ if user:
+ request.user = user
+ return True
+ else:
+ raise Unauthorized()
+
+ return False
+
+
+
+hooks = {
+ 'setup': setup_http_api_auth,
+ 'auth': HTTPAuth()}
diff --git a/mediagoblin/plugins/oauth/README.rst b/mediagoblin/plugins/oauth/README.rst
new file mode 100644
index 00000000..753b180f
--- /dev/null
+++ b/mediagoblin/plugins/oauth/README.rst
@@ -0,0 +1,148 @@
+==============
+ OAuth plugin
+==============
+
+.. warning::
+ In its current state. This plugin has received no security audit.
+ Development has been entirely focused on Making It Work(TM). Use this
+ plugin with caution.
+
+ Additionally, this and the API may break... consider it pre-alpha.
+ There's also a known issue that the OAuth client doesn't do
+ refresh tokens so this might result in issues for users.
+
+The OAuth plugin enables third party web applications to authenticate as one or
+more GNU MediaGoblin users in a safe way in order retrieve, create and update
+content stored on the GNU MediaGoblin instance.
+
+The OAuth plugin is based on the `oauth v2.25 draft`_ and is pointing by using
+the ``oauthlib.oauth2.draft25.WebApplicationClient`` from oauthlib_ to a
+mediagoblin instance and building the OAuth 2 provider logic around the client.
+
+There are surely some aspects of the OAuth v2.25 draft that haven't made it
+into this plugin due to the technique used to develop it.
+
+.. _`oauth v2.25 draft`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25
+.. _oauthlib: http://pypi.python.org/pypi/oauthlib
+
+
+Set up the OAuth plugin
+=======================
+
+1. Add the following to your MediaGoblin .ini file in the ``[plugins]`` section::
+
+ [[mediagoblin.plugins.oauth]]
+
+2. Run::
+
+ gmg dbupdate
+
+ in order to create and apply migrations to any database tables that the
+ plugin requires.
+
+.. note::
+ This only enables the OAuth plugin. To be able to let clients fetch data
+ from the MediaGoblin instance you should also enable the API plugin or some
+ other plugin that supports authenticating with OAuth credentials.
+
+
+Authenticate against GNU MediaGoblin
+====================================
+
+.. note::
+ As mentioned in `capabilities`_ GNU MediaGoblin currently only supports the
+ `Authorization Code Grant`_ procedure for obtaining an OAuth access token.
+
+Authorization Code Grant
+------------------------
+
+.. note::
+ As mentioned in `incapabilities`_ GNU MediaGoblin currently does not
+ support `client registration`_
+
+The `authorization code grant`_ works in the following way:
+
+`Definitions`
+
+ Authorization server
+ The GNU MediaGoblin instance
+ Resource server
+ Also the GNU MediaGoblin instance ;)
+ Client
+ The web application intended to use the data
+ Redirect uri
+ An URI pointing to a page controlled by the *client*
+ Resource owner
+ The GNU MediaGoblin user who's resources the client requests access to
+ User agent
+ Commonly the GNU MediaGoblin user's web browser
+ Authorization code
+ An intermediate token that is exchanged for an *access token*
+ Access token
+ A secret token that the *client* uses to authenticate itself agains the
+ *resource server* as a specific *resource owner*.
+
+
+Brief description of the procedure
+++++++++++++++++++++++++++++++++++
+
+1. The *client* requests an *authorization code* from the *authorization
+ server* by redirecting the *user agent* to the `Authorization Endpoint`_.
+ Which parameters should be included in the redirect are covered later in
+ this document.
+2. The *authorization server* authenticates the *resource owner* and redirects
+ the *user agent* back to the *redirect uri* (covered later in this
+ document).
+3. The *client* receives the request from the *user agent*, attached is the
+ *authorization code*.
+4. The *client* requests an *access token* from the *authorization server*
+5. \?\?\?\?\?
+6. Profit!
+
+
+Detailed description of the procedure
++++++++++++++++++++++++++++++++++++++
+
+TBD, in the meantime here is a proof-of-concept GNU MediaGoblin client:
+
+https://github.com/jwandborg/omgmg/
+
+and here are some detailed descriptions from other OAuth 2
+providers:
+
+- https://developers.google.com/accounts/docs/OAuth2WebServer
+- https://developers.facebook.com/docs/authentication/server-side/
+
+and if you're unsure about anything, there's the `OAuth v2.25 draft
+<http://tools.ietf.org/html/draft-ietf-oauth-v2-25>`_, the `OAuth plugin
+source code
+<http://gitorious.org/mediagoblin/mediagoblin/trees/master/mediagoblin/plugins/oauth>`_
+and the `#mediagoblin IRC channel <http://mediagoblin.org/pages/join.html#irc>`_.
+
+
+Capabilities
+============
+
+- `Authorization endpoint`_ - Located at ``/oauth/authorize``
+- `Token endpoint`_ - Located at ``/oauth/access_token``
+- `Authorization Code Grant`_
+- `Client Registration`_
+
+.. _`Authorization endpoint`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.1
+.. _`Token endpoint`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.2
+.. _`Authorization Code Grant`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.1
+.. _`Client Registration`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-2
+
+Incapabilities
+==============
+
+- Only `bearer tokens`_ are issued.
+- `Implicit Grant`_
+- `Force TLS for token endpoint`_ - This one is up the the siteadmin
+- Authorization `scope`_ and `state`
+- ...
+
+.. _`bearer tokens`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08
+.. _`scope`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.3
+.. _`Implicit Grant`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.2
+.. _`Force TLS for token endpoint`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.2
diff --git a/mediagoblin/plugins/oauth/__init__.py b/mediagoblin/plugins/oauth/__init__.py
new file mode 100644
index 00000000..5762379d
--- /dev/null
+++ b/mediagoblin/plugins/oauth/__init__.py
@@ -0,0 +1,109 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+from mediagoblin.tools import pluginapi
+from mediagoblin.plugins.oauth.models import OAuthToken, OAuthClient, \
+ OAuthUserClient
+from mediagoblin.plugins.api.tools import Auth
+
+_log = logging.getLogger(__name__)
+
+PLUGIN_DIR = os.path.dirname(__file__)
+
+
+def setup_plugin():
+ config = pluginapi.get_config('mediagoblin.plugins.oauth')
+
+ _log.info('Setting up OAuth...')
+ _log.debug('OAuth config: {0}'.format(config))
+
+ routes = [
+ ('mediagoblin.plugins.oauth.authorize',
+ '/oauth/authorize',
+ 'mediagoblin.plugins.oauth.views:authorize'),
+ ('mediagoblin.plugins.oauth.authorize_client',
+ '/oauth/client/authorize',
+ 'mediagoblin.plugins.oauth.views:authorize_client'),
+ ('mediagoblin.plugins.oauth.access_token',
+ '/oauth/access_token',
+ 'mediagoblin.plugins.oauth.views:access_token'),
+ ('mediagoblin.plugins.oauth.list_connections',
+ '/oauth/client/connections',
+ 'mediagoblin.plugins.oauth.views:list_connections'),
+ ('mediagoblin.plugins.oauth.register_client',
+ '/oauth/client/register',
+ 'mediagoblin.plugins.oauth.views:register_client'),
+ ('mediagoblin.plugins.oauth.list_clients',
+ '/oauth/client/list',
+ 'mediagoblin.plugins.oauth.views:list_clients')]
+
+ pluginapi.register_routes(routes)
+ pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates'))
+
+
+class OAuthAuth(Auth):
+ def trigger(self, request):
+ if 'access_token' in request.GET:
+ return True
+
+ return False
+
+ def __call__(self, request, *args, **kw):
+ self.errors = []
+ # TODO: Add suport for client credentials authorization
+ client_id = request.GET.get('client_id') # TODO: Not used
+ client_secret = request.GET.get('client_secret') # TODO: Not used
+ access_token = request.GET.get('access_token')
+
+ _log.debug('Authorizing request {0}'.format(request.url))
+
+ if access_token:
+ token = OAuthToken.query.filter(OAuthToken.token == access_token)\
+ .first()
+
+ if not token:
+ self.errors.append('Invalid access token')
+ return False
+
+ _log.debug('Access token: {0}'.format(token))
+ _log.debug('Client: {0}'.format(token.client))
+
+ relation = OAuthUserClient.query.filter(
+ (OAuthUserClient.user == token.user)
+ & (OAuthUserClient.client == token.client)
+ & (OAuthUserClient.state == u'approved')).first()
+
+ _log.debug('Relation: {0}'.format(relation))
+
+ if not relation:
+ self.errors.append(
+ u'Client has not been approved by the resource owner')
+ return False
+
+ request.user = token.user
+ return True
+
+ self.errors.append(u'No access_token specified')
+
+ return False
+
+hooks = {
+ 'setup': setup_plugin,
+ 'auth': OAuthAuth()
+ }
diff --git a/mediagoblin/plugins/oauth/forms.py b/mediagoblin/plugins/oauth/forms.py
new file mode 100644
index 00000000..5edd992a
--- /dev/null
+++ b/mediagoblin/plugins/oauth/forms.py
@@ -0,0 +1,69 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import wtforms
+
+from urlparse import urlparse
+
+from mediagoblin.tools.extlib.wtf_html5 import URLField
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+
+class AuthorizationForm(wtforms.Form):
+ client_id = wtforms.HiddenField(u'',
+ validators=[wtforms.validators.Required()])
+ next = wtforms.HiddenField(u'', validators=[wtforms.validators.Required()])
+ allow = wtforms.SubmitField(_(u'Allow'))
+ deny = wtforms.SubmitField(_(u'Deny'))
+
+
+class ClientRegistrationForm(wtforms.Form):
+ name = wtforms.TextField(_('Name'), [wtforms.validators.Required()],
+ description=_('The name of the OAuth client'))
+ description = wtforms.TextAreaField(_('Description'),
+ [wtforms.validators.Length(min=0, max=500)],
+ description=_('''This will be visible to users allowing your
+ application to authenticate as them.'''))
+ type = wtforms.SelectField(_('Type'),
+ [wtforms.validators.Required()],
+ choices=[
+ ('confidential', 'Confidential'),
+ ('public', 'Public')],
+ description=_('''<strong>Confidential</strong> - The client can
+ make requests to the GNU MediaGoblin instance that can not be
+ intercepted by the user agent (e.g. server-side client).<br />
+ <strong>Public</strong> - The client can't make confidential
+ requests to the GNU MediaGoblin instance (e.g. client-side
+ JavaScript client).'''))
+
+ redirect_uri = URLField(_('Redirect URI'),
+ [wtforms.validators.Optional(), wtforms.validators.URL()],
+ description=_('''The redirect URI for the applications, this field
+ is <strong>required</strong> for public clients.'''))
+
+ def __init__(self, *args, **kw):
+ wtforms.Form.__init__(self, *args, **kw)
+
+ def validate(self):
+ if not wtforms.Form.validate(self):
+ return False
+
+ if self.type.data == 'public' and not self.redirect_uri.data:
+ self.redirect_uri.errors.append(
+ _('This field is required for public clients'))
+ return False
+
+ return True
diff --git a/mediagoblin/plugins/oauth/migrations.py b/mediagoblin/plugins/oauth/migrations.py
new file mode 100644
index 00000000..d7b89da3
--- /dev/null
+++ b/mediagoblin/plugins/oauth/migrations.py
@@ -0,0 +1,158 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from datetime import datetime, timedelta
+from sqlalchemy import (MetaData, Table, Column,
+ Integer, Unicode, Enum, DateTime, ForeignKey)
+from sqlalchemy.ext.declarative import declarative_base
+
+from mediagoblin.db.migration_tools import RegisterMigration
+from mediagoblin.db.models import User
+
+
+MIGRATIONS = {}
+
+
+class OAuthClient_v0(declarative_base()):
+ __tablename__ = 'oauth__client'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+
+ name = Column(Unicode)
+ description = Column(Unicode)
+
+ identifier = Column(Unicode, unique=True, index=True)
+ secret = Column(Unicode, index=True)
+
+ owner_id = Column(Integer, ForeignKey(User.id))
+ redirect_uri = Column(Unicode)
+
+ type = Column(Enum(
+ u'confidential',
+ u'public',
+ name=u'oauth__client_type'))
+
+
+class OAuthUserClient_v0(declarative_base()):
+ __tablename__ = 'oauth__user_client'
+ id = Column(Integer, primary_key=True)
+
+ user_id = Column(Integer, ForeignKey(User.id))
+ client_id = Column(Integer, ForeignKey(OAuthClient_v0.id))
+
+ state = Column(Enum(
+ u'approved',
+ u'rejected',
+ name=u'oauth__relation_state'))
+
+
+class OAuthToken_v0(declarative_base()):
+ __tablename__ = 'oauth__tokens'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+ expires = Column(DateTime, nullable=False,
+ default=lambda: datetime.now() + timedelta(days=30))
+ token = Column(Unicode, index=True)
+ refresh_token = Column(Unicode, index=True)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False,
+ index=True)
+
+ client_id = Column(Integer, ForeignKey(OAuthClient_v0.id), nullable=False)
+
+ def __repr__(self):
+ return '<{0} #{1} expires {2} [{3}, {4}]>'.format(
+ self.__class__.__name__,
+ self.id,
+ self.expires.isoformat(),
+ self.user,
+ self.client)
+
+
+class OAuthCode_v0(declarative_base()):
+ __tablename__ = 'oauth__codes'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+ expires = Column(DateTime, nullable=False,
+ default=lambda: datetime.now() + timedelta(minutes=5))
+ code = Column(Unicode, index=True)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False,
+ index=True)
+
+ client_id = Column(Integer, ForeignKey(OAuthClient_v0.id), nullable=False)
+
+
+class OAuthRefreshToken_v0(declarative_base()):
+ __tablename__ = 'oauth__refresh_tokens'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+
+ token = Column(Unicode, index=True)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+
+ # XXX: Is it OK to use OAuthClient_v0.id in this way?
+ client_id = Column(Integer, ForeignKey(OAuthClient_v0.id), nullable=False)
+
+
+@RegisterMigration(1, MIGRATIONS)
+def remove_and_replace_token_and_code(db):
+ metadata = MetaData(bind=db.bind)
+
+ token_table = Table('oauth__tokens', metadata, autoload=True,
+ autoload_with=db.bind)
+
+ token_table.drop()
+
+ code_table = Table('oauth__codes', metadata, autoload=True,
+ autoload_with=db.bind)
+
+ code_table.drop()
+
+ OAuthClient_v0.__table__.create(db.bind)
+ OAuthUserClient_v0.__table__.create(db.bind)
+ OAuthToken_v0.__table__.create(db.bind)
+ OAuthCode_v0.__table__.create(db.bind)
+
+ db.commit()
+
+
+@RegisterMigration(2, MIGRATIONS)
+def remove_refresh_token_field(db):
+ metadata = MetaData(bind=db.bind)
+
+ token_table = Table('oauth__tokens', metadata, autoload=True,
+ autoload_with=db.bind)
+
+ refresh_token = token_table.columns['refresh_token']
+
+ refresh_token.drop()
+ db.commit()
+
+@RegisterMigration(3, MIGRATIONS)
+def create_refresh_token_table(db):
+ OAuthRefreshToken_v0.__table__.create(db.bind)
+
+ db.commit()
diff --git a/mediagoblin/plugins/oauth/models.py b/mediagoblin/plugins/oauth/models.py
new file mode 100644
index 00000000..439424d3
--- /dev/null
+++ b/mediagoblin/plugins/oauth/models.py
@@ -0,0 +1,192 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from datetime import datetime, timedelta
+
+
+from sqlalchemy import (
+ Column, Unicode, Integer, DateTime, ForeignKey, Enum)
+from sqlalchemy.orm import relationship, backref
+from mediagoblin.db.base import Base
+from mediagoblin.db.models import User
+from mediagoblin.plugins.oauth.tools import generate_identifier, \
+ generate_secret, generate_token, generate_code, generate_refresh_token
+
+# Don't remove this, I *think* it applies sqlalchemy-migrate functionality onto
+# the models.
+from migrate import changeset
+
+
+class OAuthClient(Base):
+ __tablename__ = 'oauth__client'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+
+ name = Column(Unicode)
+ description = Column(Unicode)
+
+ identifier = Column(Unicode, unique=True, index=True,
+ default=generate_identifier)
+ secret = Column(Unicode, index=True, default=generate_secret)
+
+ owner_id = Column(Integer, ForeignKey(User.id))
+ owner = relationship(
+ User,
+ backref=backref('registered_clients', cascade='all, delete-orphan'))
+
+ redirect_uri = Column(Unicode)
+
+ type = Column(Enum(
+ u'confidential',
+ u'public',
+ name=u'oauth__client_type'))
+
+ def update_secret(self):
+ self.secret = generate_secret()
+
+ def __repr__(self):
+ return '<{0} {1}:{2} ({3})>'.format(
+ self.__class__.__name__,
+ self.id,
+ self.name.encode('ascii', 'replace'),
+ self.owner.username.encode('ascii', 'replace'))
+
+
+class OAuthUserClient(Base):
+ __tablename__ = 'oauth__user_client'
+ id = Column(Integer, primary_key=True)
+
+ user_id = Column(Integer, ForeignKey(User.id))
+ user = relationship(
+ User,
+ backref=backref('oauth_client_relations',
+ cascade='all, delete-orphan'))
+
+ client_id = Column(Integer, ForeignKey(OAuthClient.id))
+ client = relationship(
+ OAuthClient,
+ backref=backref('oauth_user_relations', cascade='all, delete-orphan'))
+
+ state = Column(Enum(
+ u'approved',
+ u'rejected',
+ name=u'oauth__relation_state'))
+
+ def __repr__(self):
+ return '<{0} #{1} {2} [{3}, {4}]>'.format(
+ self.__class__.__name__,
+ self.id,
+ self.state.encode('ascii', 'replace'),
+ self.user,
+ self.client)
+
+
+class OAuthToken(Base):
+ __tablename__ = 'oauth__tokens'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+ expires = Column(DateTime, nullable=False,
+ default=lambda: datetime.now() + timedelta(days=30))
+ token = Column(Unicode, index=True, default=generate_token)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False,
+ index=True)
+ user = relationship(
+ User,
+ backref=backref('oauth_tokens', cascade='all, delete-orphan'))
+
+ client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False)
+ client = relationship(
+ OAuthClient,
+ backref=backref('oauth_tokens', cascade='all, delete-orphan'))
+
+ def __repr__(self):
+ return '<{0} #{1} expires {2} [{3}, {4}]>'.format(
+ self.__class__.__name__,
+ self.id,
+ self.expires.isoformat(),
+ self.user,
+ self.client)
+
+class OAuthRefreshToken(Base):
+ __tablename__ = 'oauth__refresh_tokens'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+
+ token = Column(Unicode, index=True,
+ default=generate_refresh_token)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False)
+
+ user = relationship(User, backref=backref('oauth_refresh_tokens',
+ cascade='all, delete-orphan'))
+
+ client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False)
+ client = relationship(OAuthClient,
+ backref=backref(
+ 'oauth_refresh_tokens',
+ cascade='all, delete-orphan'))
+
+ def __repr__(self):
+ return '<{0} #{1} [{3}, {4}]>'.format(
+ self.__class__.__name__,
+ self.id,
+ self.user,
+ self.client)
+
+
+class OAuthCode(Base):
+ __tablename__ = 'oauth__codes'
+
+ id = Column(Integer, primary_key=True)
+ created = Column(DateTime, nullable=False,
+ default=datetime.now)
+ expires = Column(DateTime, nullable=False,
+ default=lambda: datetime.now() + timedelta(minutes=5))
+ code = Column(Unicode, index=True, default=generate_code)
+
+ user_id = Column(Integer, ForeignKey(User.id), nullable=False,
+ index=True)
+ user = relationship(User, backref=backref('oauth_codes',
+ cascade='all, delete-orphan'))
+
+ client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False)
+ client = relationship(OAuthClient, backref=backref(
+ 'oauth_codes',
+ cascade='all, delete-orphan'))
+
+ def __repr__(self):
+ return '<{0} #{1} expires {2} [{3}, {4}]>'.format(
+ self.__class__.__name__,
+ self.id,
+ self.expires.isoformat(),
+ self.user,
+ self.client)
+
+
+MODELS = [
+ OAuthToken,
+ OAuthRefreshToken,
+ OAuthCode,
+ OAuthClient,
+ OAuthUserClient]
diff --git a/mediagoblin/plugins/oauth/templates/oauth/authorize.html b/mediagoblin/plugins/oauth/templates/oauth/authorize.html
new file mode 100644
index 00000000..8a00c925
--- /dev/null
+++ b/mediagoblin/plugins/oauth/templates/oauth/authorize.html
@@ -0,0 +1,31 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#, se, seee
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+{% extends "mediagoblin/base.html" %}
+{% import "mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+<form action="{{ request.urlgen('mediagoblin.plugins.oauth.authorize_client') }}"
+ method="POST">
+ <div class="form_box_xl">
+ {{ csrf_token }}
+ <h2>Authorize {{ client.name }}?</h2>
+ <p class="client-description">{{ client.description }}</p>
+ {{ wtforms_util.render_divs(form) }}
+ </div>
+</form>
+{% endblock %}
diff --git a/mediagoblin/plugins/oauth/templates/oauth/client/connections.html b/mediagoblin/plugins/oauth/templates/oauth/client/connections.html
new file mode 100644
index 00000000..63b0230a
--- /dev/null
+++ b/mediagoblin/plugins/oauth/templates/oauth/client/connections.html
@@ -0,0 +1,34 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+{% extends "mediagoblin/base.html" %}
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+<h1>{% trans %}OAuth client connections{% endtrans %}</h1>
+{% if connections %}
+<ul>
+ {% for connection in connections %}
+ <li><span title="{{ connection.client.description }}">{{
+ connection.client.name }}</span> - {{ connection.state }}
+ </li>
+ {% endfor %}
+</ul>
+{% else %}
+<p>You haven't connected using an OAuth client before.</p>
+{% endif %}
+{% endblock %}
diff --git a/mediagoblin/plugins/oauth/templates/oauth/client/list.html b/mediagoblin/plugins/oauth/templates/oauth/client/list.html
new file mode 100644
index 00000000..21024bb7
--- /dev/null
+++ b/mediagoblin/plugins/oauth/templates/oauth/client/list.html
@@ -0,0 +1,45 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+{% extends "mediagoblin/base.html" %}
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+<h1>{% trans %}Your OAuth clients{% endtrans %}</h1>
+{% if clients %}
+<ul>
+ {% for client in clients %}
+ <li>{{ client.name }}
+ <dl>
+ <dt>Type</dt>
+ <dd>{{ client.type }}</dd>
+ <dt>Description</dt>
+ <dd>{{ client.description }}</dd>
+ <dt>Identifier</dt>
+ <dd>{{ client.identifier }}</dd>
+ <dt>Secret</dt>
+ <dd>{{ client.secret }}</dd>
+ <dt>Redirect URI<dt>
+ <dd>{{ client.redirect_uri }}</dd>
+ </dl>
+ </li>
+ {% endfor %}
+</ul>
+{% else %}
+<p>You don't have any clients yet. <a href="{{ request.urlgen('mediagoblin.plugins.oauth.register_client') }}">Add one</a>.</p>
+{% endif %}
+{% endblock %}
diff --git a/mediagoblin/plugins/oauth/templates/oauth/client/register.html b/mediagoblin/plugins/oauth/templates/oauth/client/register.html
new file mode 100644
index 00000000..6fd700d3
--- /dev/null
+++ b/mediagoblin/plugins/oauth/templates/oauth/client/register.html
@@ -0,0 +1,34 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+{% extends "mediagoblin/base.html" %}
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+<form action="{{ request.urlgen('mediagoblin.plugins.oauth.register_client') }}"
+ method="POST">
+ <div class="form_box_xl">
+ <h1>Register OAuth client</h1>
+ {{ wtforms_util.render_divs(form) }}
+ <div class="form_submit_buttons">
+ {{ csrf_token }}
+ <input type="submit" value="{% trans %}Add{% endtrans %}"
+ class="button_form" />
+ </div>
+ </div>
+</form>
+{% endblock %}
diff --git a/mediagoblin/plugins/oauth/tools.py b/mediagoblin/plugins/oauth/tools.py
new file mode 100644
index 00000000..27ff32b4
--- /dev/null
+++ b/mediagoblin/plugins/oauth/tools.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import uuid
+
+from random import getrandbits
+
+from datetime import datetime
+
+from functools import wraps
+
+from mediagoblin.plugins.api.tools import json_response
+
+
+def require_client_auth(controller):
+ '''
+ View decorator
+
+ - Requires the presence of ``?client_id``
+ '''
+ # Avoid circular import
+ from mediagoblin.plugins.oauth.models import OAuthClient
+
+ @wraps(controller)
+ def wrapper(request, *args, **kw):
+ if not request.GET.get('client_id'):
+ return json_response({
+ 'status': 400,
+ 'errors': [u'No client identifier in URL']},
+ _disable_cors=True)
+
+ client = OAuthClient.query.filter(
+ OAuthClient.identifier == request.GET.get('client_id')).first()
+
+ if not client:
+ return json_response({
+ 'status': 400,
+ 'errors': [u'No such client identifier']},
+ _disable_cors=True)
+
+ return controller(request, client)
+
+ return wrapper
+
+
+def create_token(client, user):
+ '''
+ Create an OAuthToken and an OAuthRefreshToken entry in the database
+
+ Returns the data structure expected by the OAuth clients.
+ '''
+ from mediagoblin.plugins.oauth.models import OAuthToken, OAuthRefreshToken
+
+ token = OAuthToken()
+ token.user = user
+ token.client = client
+ token.save()
+
+ refresh_token = OAuthRefreshToken()
+ refresh_token.user = user
+ refresh_token.client = client
+ refresh_token.save()
+
+ # expire time of token in full seconds
+ # timedelta.total_seconds is python >= 2.7 or we would use that
+ td = token.expires - datetime.now()
+ exp_in = 86400*td.days + td.seconds # just ignore µsec
+
+ return {'access_token': token.token, 'token_type': 'bearer',
+ 'refresh_token': refresh_token.token, 'expires_in': exp_in}
+
+
+def generate_identifier():
+ ''' Generates a ``uuid.uuid4()`` '''
+ return unicode(uuid.uuid4())
+
+
+def generate_token():
+ ''' Uses generate_identifier '''
+ return generate_identifier()
+
+
+def generate_refresh_token():
+ ''' Uses generate_identifier '''
+ return generate_identifier()
+
+
+def generate_code():
+ ''' Uses generate_identifier '''
+ return generate_identifier()
+
+
+def generate_secret():
+ '''
+ Generate a long string of pseudo-random characters
+ '''
+ # XXX: We might not want it to use bcrypt, since bcrypt takes its time to
+ # generate the result.
+ return unicode(getrandbits(192))
+
diff --git a/mediagoblin/plugins/oauth/views.py b/mediagoblin/plugins/oauth/views.py
new file mode 100644
index 00000000..d6fd314f
--- /dev/null
+++ b/mediagoblin/plugins/oauth/views.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+from urllib import urlencode
+
+from werkzeug.exceptions import BadRequest
+
+from mediagoblin.tools.response import render_to_response, redirect
+from mediagoblin.decorators import require_active_login
+from mediagoblin.messages import add_message, SUCCESS
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.plugins.oauth.models import OAuthCode, OAuthClient, \
+ OAuthUserClient, OAuthRefreshToken
+from mediagoblin.plugins.oauth.forms import ClientRegistrationForm, \
+ AuthorizationForm
+from mediagoblin.plugins.oauth.tools import require_client_auth, \
+ create_token
+from mediagoblin.plugins.api.tools import json_response
+
+_log = logging.getLogger(__name__)
+
+
+@require_active_login
+def register_client(request):
+ '''
+ Register an OAuth client
+ '''
+ form = ClientRegistrationForm(request.form)
+
+ if request.method == 'POST' and form.validate():
+ client = OAuthClient()
+ client.name = unicode(form.name.data)
+ client.description = unicode(form.description.data)
+ client.type = unicode(form.type.data)
+ client.owner_id = request.user.id
+ client.redirect_uri = unicode(form.redirect_uri.data)
+
+ client.save()
+
+ add_message(request, SUCCESS, _('The client {0} has been registered!')\
+ .format(
+ client.name))
+
+ return redirect(request, 'mediagoblin.plugins.oauth.list_clients')
+
+ return render_to_response(
+ request,
+ 'oauth/client/register.html',
+ {'form': form})
+
+
+@require_active_login
+def list_clients(request):
+ clients = request.db.OAuthClient.query.filter(
+ OAuthClient.owner_id == request.user.id).all()
+ return render_to_response(request, 'oauth/client/list.html',
+ {'clients': clients})
+
+
+@require_active_login
+def list_connections(request):
+ connections = OAuthUserClient.query.filter(
+ OAuthUserClient.user == request.user).all()
+ return render_to_response(request, 'oauth/client/connections.html',
+ {'connections': connections})
+
+
+@require_active_login
+def authorize_client(request):
+ form = AuthorizationForm(request.form)
+
+ client = OAuthClient.query.filter(OAuthClient.id ==
+ form.client_id.data).first()
+
+ if not client:
+ _log.error('No such client id as received from client authorization \
+form.')
+ raise BadRequest()
+
+ if form.validate():
+ relation = OAuthUserClient()
+ relation.user_id = request.user.id
+ relation.client_id = form.client_id.data
+ if form.allow.data:
+ relation.state = u'approved'
+ elif form.deny.data:
+ relation.state = u'rejected'
+ else:
+ raise BadRequest()
+
+ relation.save()
+
+ return redirect(request, location=form.next.data)
+
+ return render_to_response(
+ request,
+ 'oauth/authorize.html',
+ {'form': form,
+ 'client': client})
+
+
+@require_client_auth
+@require_active_login
+def authorize(request, client):
+ # TODO: Get rid of the JSON responses in this view, it's called by the
+ # user-agent, not the client.
+ user_client_relation = OAuthUserClient.query.filter(
+ (OAuthUserClient.user == request.user)
+ & (OAuthUserClient.client == client))
+
+ if user_client_relation.filter(OAuthUserClient.state ==
+ u'approved').count():
+ redirect_uri = None
+
+ if client.type == u'public':
+ if not client.redirect_uri:
+ return json_response({
+ 'status': 400,
+ 'errors':
+ [u'Public clients should have a redirect_uri pre-set.']},
+ _disable_cors=True)
+
+ redirect_uri = client.redirect_uri
+
+ if client.type == u'confidential':
+ redirect_uri = request.GET.get('redirect_uri', client.redirect_uri)
+ if not redirect_uri:
+ return json_response({
+ 'status': 400,
+ 'errors': [u'No redirect_uri supplied!']},
+ _disable_cors=True)
+
+ code = OAuthCode()
+ code.user = request.user
+ code.client = client
+ code.save()
+
+ redirect_uri = ''.join([
+ redirect_uri,
+ '?',
+ urlencode({'code': code.code})])
+
+ _log.debug('Redirecting to {0}'.format(redirect_uri))
+
+ return redirect(request, location=redirect_uri)
+ else:
+ # Show prompt to allow client to access data
+ # - on accept: send the user agent back to the redirect_uri with the
+ # code parameter
+ # - on deny: send the user agent back to the redirect uri with error
+ # information
+ form = AuthorizationForm(request.form)
+ form.client_id.data = client.id
+ form.next.data = request.url
+ return render_to_response(
+ request,
+ 'oauth/authorize.html',
+ {'form': form,
+ 'client': client})
+
+
+def access_token(request):
+ '''
+ Access token endpoint provides access tokens to any clients that have the
+ right grants/credentials
+ '''
+
+ client = None
+ user = None
+
+ if request.GET.get('code'):
+ # Validate the code arg, then get the client object from the db.
+ code = OAuthCode.query.filter(OAuthCode.code ==
+ request.GET.get('code')).first()
+
+ if not code:
+ return json_response({
+ 'error': 'invalid_request',
+ 'error_description':
+ 'Invalid code.'})
+
+ client = code.client
+ user = code.user
+
+ elif request.args.get('refresh_token'):
+ # Validate a refresh token, then get the client object from the db.
+ refresh_token = OAuthRefreshToken.query.filter(
+ OAuthRefreshToken.token ==
+ request.args.get('refresh_token')).first()
+
+ if not refresh_token:
+ return json_response({
+ 'error': 'invalid_request',
+ 'error_description':
+ 'Invalid refresh token.'})
+
+ client = refresh_token.client
+ user = refresh_token.user
+
+ if client:
+ client_identifier = request.GET.get('client_id')
+
+ if not client_identifier:
+ return json_response({
+ 'error': 'invalid_request',
+ 'error_description':
+ 'Missing client_id in request.'})
+
+ if not client_identifier == client.identifier:
+ return json_response({
+ 'error': 'invalid_client',
+ 'error_description':
+ 'Mismatching client credentials.'})
+
+ if client.type == u'confidential':
+ client_secret = request.GET.get('client_secret')
+
+ if not client_secret:
+ return json_response({
+ 'error': 'invalid_request',
+ 'error_description':
+ 'Missing client_secret in request.'})
+
+ if not client_secret == client.secret:
+ return json_response({
+ 'error': 'invalid_client',
+ 'error_description':
+ 'Mismatching client credentials.'})
+
+
+ access_token_data = create_token(client, user)
+
+ return json_response(access_token_data, _disable_cors=True)
+
+ return json_response({
+ 'error': 'invalid_request',
+ 'error_description':
+ 'Missing `code` or `refresh_token` parameter in request.'})
diff --git a/mediagoblin/plugins/piwigo/README.rst b/mediagoblin/plugins/piwigo/README.rst
new file mode 100644
index 00000000..0c71ffbc
--- /dev/null
+++ b/mediagoblin/plugins/piwigo/README.rst
@@ -0,0 +1,23 @@
+===================
+ piwigo api plugin
+===================
+
+.. danger::
+ This plugin does not work.
+ It might make your instance unstable or even insecure.
+ So do not use it, unless you want to help to develop it.
+
+.. warning::
+ You should not depend on this plugin in any way for now.
+ It might even go away without any notice.
+
+Okay, so if you still want to test this plugin,
+add the following to your mediagoblin_local.ini:
+
+.. code-block:: ini
+
+ [plugins]
+ [[mediagoblin.plugins.piwigo]]
+
+Then try to connect using some piwigo client.
+There should be some logging, that might help.
diff --git a/mediagoblin/plugins/piwigo/__init__.py b/mediagoblin/plugins/piwigo/__init__.py
new file mode 100644
index 00000000..c4da708a
--- /dev/null
+++ b/mediagoblin/plugins/piwigo/__init__.py
@@ -0,0 +1,42 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+from mediagoblin.tools import pluginapi
+from mediagoblin.tools.session import SessionManager
+from .tools import PWGSession
+
+_log = logging.getLogger(__name__)
+
+
+def setup_plugin():
+ _log.info('Setting up piwigo...')
+
+ routes = [
+ ('mediagoblin.plugins.piwigo.wsphp',
+ '/api/piwigo/ws.php',
+ 'mediagoblin.plugins.piwigo.views:ws_php'),
+ ]
+
+ pluginapi.register_routes(routes)
+
+ PWGSession.session_manager = SessionManager("pwg_id", "plugins.piwigo")
+
+
+hooks = {
+ 'setup': setup_plugin
+}
diff --git a/mediagoblin/plugins/piwigo/forms.py b/mediagoblin/plugins/piwigo/forms.py
new file mode 100644
index 00000000..fb04aa6a
--- /dev/null
+++ b/mediagoblin/plugins/piwigo/forms.py
@@ -0,0 +1,44 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import wtforms
+
+
+class AddSimpleForm(wtforms.Form):
+ image = wtforms.FileField()
+ name = wtforms.TextField(
+ validators=[wtforms.validators.Length(min=0, max=500)])
+ comment = wtforms.TextField()
+ # tags = wtforms.FieldList(wtforms.TextField())
+ category = wtforms.IntegerField()
+ level = wtforms.IntegerField()
+
+
+_md5_validator = wtforms.validators.Regexp(r"^[0-9a-fA-F]{32}$")
+
+
+class AddForm(wtforms.Form):
+ original_sum = wtforms.TextField(None,
+ [_md5_validator,
+ wtforms.validators.Required()])
+ thumbnail_sum = wtforms.TextField(None,
+ [wtforms.validators.Optional(),
+ _md5_validator])
+ file_sum = wtforms.TextField(None, [_md5_validator])
+ name = wtforms.TextField()
+ date_creation = wtforms.TextField()
+ categories = wtforms.TextField()
diff --git a/mediagoblin/plugins/piwigo/tools.py b/mediagoblin/plugins/piwigo/tools.py
new file mode 100644
index 00000000..484ea531
--- /dev/null
+++ b/mediagoblin/plugins/piwigo/tools.py
@@ -0,0 +1,165 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from collections import namedtuple
+import logging
+
+import six
+import lxml.etree as ET
+from werkzeug.exceptions import MethodNotAllowed, BadRequest
+
+from mediagoblin.tools.request import setup_user_in_request
+from mediagoblin.tools.response import Response
+
+
+_log = logging.getLogger(__name__)
+
+
+PwgError = namedtuple("PwgError", ["code", "msg"])
+
+
+class PwgNamedArray(list):
+ def __init__(self, l, item_name, as_attrib=()):
+ self.item_name = item_name
+ self.as_attrib = as_attrib
+ list.__init__(self, l)
+
+ def fill_element_xml(self, el):
+ for it in self:
+ n = ET.SubElement(el, self.item_name)
+ if isinstance(it, dict):
+ _fill_element_dict(n, it, self.as_attrib)
+ else:
+ _fill_element(n, it)
+
+
+def _fill_element_dict(el, data, as_attr=()):
+ for k, v in data.iteritems():
+ if k in as_attr:
+ if not isinstance(v, six.string_types):
+ v = str(v)
+ el.set(k, v)
+ else:
+ n = ET.SubElement(el, k)
+ _fill_element(n, v)
+
+
+def _fill_element(el, data):
+ if isinstance(data, bool):
+ if data:
+ el.text = "1"
+ else:
+ el.text = "0"
+ elif isinstance(data, six.string_types):
+ el.text = data
+ elif isinstance(data, int):
+ el.text = str(data)
+ elif isinstance(data, dict):
+ _fill_element_dict(el, data)
+ elif isinstance(data, PwgNamedArray):
+ data.fill_element_xml(el)
+ else:
+ _log.warn("Can't convert to xml: %r", data)
+
+
+def response_xml(result):
+ r = ET.Element("rsp")
+ r.set("stat", "ok")
+ status = None
+ if isinstance(result, PwgError):
+ r.set("stat", "fail")
+ err = ET.SubElement(r, "err")
+ err.set("code", str(result.code))
+ err.set("msg", result.msg)
+ if result.code >= 100 and result.code < 600:
+ status = result.code
+ else:
+ _fill_element(r, result)
+ return Response(ET.tostring(r, encoding="utf-8", xml_declaration=True),
+ mimetype='text/xml', status=status)
+
+
+class CmdTable(object):
+ _cmd_table = {}
+
+ def __init__(self, cmd_name, only_post=False):
+ assert not cmd_name in self._cmd_table
+ self.cmd_name = cmd_name
+ self.only_post = only_post
+
+ def __call__(self, to_be_wrapped):
+ assert not self.cmd_name in self._cmd_table
+ self._cmd_table[self.cmd_name] = (to_be_wrapped, self.only_post)
+ return to_be_wrapped
+
+ @classmethod
+ def find_func(cls, request):
+ if request.method == "GET":
+ cmd_name = request.args.get("method")
+ else:
+ cmd_name = request.form.get("method")
+ entry = cls._cmd_table.get(cmd_name)
+ if not entry:
+ return entry
+ _log.debug("Found method %s", cmd_name)
+ func, only_post = entry
+ if only_post and request.method != "POST":
+ _log.warn("Method %s only allowed for POST", cmd_name)
+ raise MethodNotAllowed()
+ return func
+
+
+def check_form(form):
+ if not form.validate():
+ _log.error("form validation failed for form %r", form)
+ for f in form:
+ if len(f.errors):
+ _log.error("Errors for %s: %r", f.name, f.errors)
+ raise BadRequest()
+ dump = []
+ for f in form:
+ dump.append("%s=%r" % (f.name, f.data))
+ _log.debug("form: %s", " ".join(dump))
+
+
+class PWGSession(object):
+ session_manager = None
+
+ def __init__(self, request):
+ self.request = request
+ self.in_pwg_session = False
+
+ def __enter__(self):
+ # Backup old state
+ self.old_session = self.request.session
+ self.old_user = self.request.user
+ # Load piwigo session into state
+ self.request.session = self.session_manager.load_session_from_cookie(
+ self.request)
+ setup_user_in_request(self.request)
+ self.in_pwg_session = True
+ return self
+
+ def __exit__(self, *args):
+ # Restore state
+ self.request.session = self.old_session
+ self.request.user = self.old_user
+ self.in_pwg_session = False
+
+ def save_to_cookie(self, response):
+ assert self.in_pwg_session
+ self.session_manager.save_session_to_cookie(self.request.session,
+ self.request, response)
diff --git a/mediagoblin/plugins/piwigo/views.py b/mediagoblin/plugins/piwigo/views.py
new file mode 100644
index 00000000..ca723189
--- /dev/null
+++ b/mediagoblin/plugins/piwigo/views.py
@@ -0,0 +1,249 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import re
+from os.path import splitext
+import shutil
+
+from werkzeug.exceptions import MethodNotAllowed, BadRequest, NotImplemented
+from werkzeug.wrappers import BaseResponse
+
+from mediagoblin.meddleware.csrf import csrf_exempt
+from mediagoblin.auth.tools import check_login_simple
+from mediagoblin.media_types import sniff_media
+from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \
+ run_process_media, new_upload_entry
+
+from mediagoblin.user_pages.lib import add_media_to_collection
+from mediagoblin.db.models import Collection
+
+from .tools import CmdTable, response_xml, check_form, \
+ PWGSession, PwgNamedArray, PwgError
+from .forms import AddSimpleForm, AddForm
+
+
+_log = logging.getLogger(__name__)
+
+
+@CmdTable("pwg.session.login", True)
+def pwg_login(request):
+ username = request.form.get("username")
+ password = request.form.get("password")
+ user = check_login_simple(username, password)
+ if not user:
+ return PwgError(999, 'Invalid username/password')
+ request.session["user_id"] = user.id
+ request.session.save()
+ return True
+
+
+@CmdTable("pwg.session.logout")
+def pwg_logout(request):
+ _log.info("Logout")
+ request.session.delete()
+ return True
+
+
+@CmdTable("pwg.getVersion")
+def pwg_getversion(request):
+ return "2.5.0 (MediaGoblin)"
+
+
+@CmdTable("pwg.session.getStatus")
+def pwg_session_getStatus(request):
+ if request.user:
+ username = request.user.username
+ else:
+ username = "guest"
+ return {'username': username}
+
+
+@CmdTable("pwg.categories.getList")
+def pwg_categories_getList(request):
+ catlist = [{'id': -29711,
+ 'uppercats': "-29711",
+ 'name': "All my images"}]
+
+ if request.user:
+ collections = Collection.query.filter_by(
+ get_creator=request.user).order_by(Collection.title)
+
+ for c in collections:
+ catlist.append({'id': c.id,
+ 'uppercats': str(c.id),
+ 'name': c.title,
+ 'comment': c.description
+ })
+
+ return {
+ 'categories': PwgNamedArray(
+ catlist,
+ 'category',
+ (
+ 'id',
+ 'url',
+ 'nb_images',
+ 'total_nb_images',
+ 'nb_categories',
+ 'date_last',
+ 'max_date_last',
+ )
+ )
+ }
+
+
+@CmdTable("pwg.images.exist")
+def pwg_images_exist(request):
+ return {}
+
+
+@CmdTable("pwg.images.addSimple", True)
+def pwg_images_addSimple(request):
+ form = AddSimpleForm(request.form)
+ if not form.validate():
+ _log.error("addSimple: form failed")
+ raise BadRequest()
+ dump = []
+ for f in form:
+ dump.append("%s=%r" % (f.name, f.data))
+ _log.info("addSimple: %r %s %r", request.form, " ".join(dump),
+ request.files)
+
+ if not check_file_field(request, 'image'):
+ raise BadRequest()
+
+ filename = request.files['image'].filename
+
+ # Sniff the submitted media to determine which
+ # media plugin should handle processing
+ media_type, media_manager = sniff_media(
+ request.files['image'])
+
+ # create entry and save in database
+ entry = new_upload_entry(request.user)
+ entry.media_type = unicode(media_type)
+ entry.title = (
+ unicode(form.name.data)
+ or unicode(splitext(filename)[0]))
+
+ entry.description = unicode(form.comment.data)
+
+ '''
+ # Process the user's folksonomy "tags"
+ entry.tags = convert_to_tag_list_of_dicts(
+ form.tags.data)
+ '''
+
+ # Generate a slug from the title
+ entry.generate_slug()
+
+ queue_file = prepare_queue_task(request.app, entry, filename)
+
+ with queue_file:
+ shutil.copyfileobj(request.files['image'].stream,
+ queue_file,
+ length=4 * 1048576)
+
+ # Save now so we have this data before kicking off processing
+ entry.save()
+
+ # Pass off to processing
+ #
+ # (... don't change entry after this point to avoid race
+ # conditions with changes to the document via processing code)
+ feed_url = request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ qualified=True, user=request.user.username)
+ run_process_media(entry, feed_url)
+
+ collection_id = form.category.data
+ if collection_id > 0:
+ collection = Collection.query.get(collection_id)
+ if collection is not None and collection.creator == request.user.id:
+ add_media_to_collection(collection, entry, "")
+
+ return {'image_id': entry.id, 'url': entry.url_for_self(request.urlgen,
+ qualified=True)}
+
+
+md5sum_matcher = re.compile(r"^[0-9a-fA-F]{32}$")
+
+
+def fetch_md5(request, parm_name, optional_parm=False):
+ val = request.form.get(parm_name)
+ if (val is None) and (not optional_parm):
+ _log.error("Parameter %s missing", parm_name)
+ raise BadRequest("Parameter %s missing" % parm_name)
+ if not md5sum_matcher.match(val):
+ _log.error("Parameter %s=%r has no valid md5 value", parm_name, val)
+ raise BadRequest("Parameter %s is not md5" % parm_name)
+ return val
+
+
+@CmdTable("pwg.images.addChunk", True)
+def pwg_images_addChunk(request):
+ o_sum = fetch_md5(request, 'original_sum')
+ typ = request.form.get('type')
+ pos = request.form.get('position')
+ data = request.form.get('data')
+
+ # Validate params:
+ pos = int(pos)
+ if not typ in ("file", "thumb"):
+ _log.error("type %r not allowed for now", typ)
+ return False
+
+ _log.info("addChunk for %r, type %r, position %d, len: %d",
+ o_sum, typ, pos, len(data))
+ if typ == "thumb":
+ _log.info("addChunk: Ignoring thumb, because we create our own")
+ return True
+
+ return True
+
+
+@CmdTable("pwg.images.add", True)
+def pwg_images_add(request):
+ _log.info("add: %r", request.form)
+ form = AddForm(request.form)
+ check_form(form)
+
+ return {'image_id': 123456, 'url': ''}
+
+
+@csrf_exempt
+def ws_php(request):
+ if request.method not in ("GET", "POST"):
+ _log.error("Method %r not supported", request.method)
+ raise MethodNotAllowed()
+
+ func = CmdTable.find_func(request)
+ if not func:
+ _log.warn("wsphp: Unhandled %s %r %r", request.method,
+ request.args, request.form)
+ raise NotImplemented()
+
+ with PWGSession(request) as session:
+ result = func(request)
+
+ if isinstance(result, BaseResponse):
+ return result
+
+ response = response_xml(result)
+ session.save_to_cookie(response)
+
+ return response
diff --git a/mediagoblin/plugins/raven/README.rst b/mediagoblin/plugins/raven/README.rst
new file mode 100644
index 00000000..4006060d
--- /dev/null
+++ b/mediagoblin/plugins/raven/README.rst
@@ -0,0 +1,17 @@
+==============
+ raven plugin
+==============
+
+.. _raven-setup:
+
+Warning: this plugin is somewhat experimental.
+
+Set up the raven plugin
+=======================
+
+1. Add the following to your MediaGoblin .ini file in the ``[plugins]`` section::
+
+ [[mediagoblin.plugins.raven]]
+ sentry_dsn = <YOUR SENTRY DSN>
+ # Logging is very high-volume, set to 0 if you want to turn off logging
+ setup_logging = 1
diff --git a/mediagoblin/plugins/raven/__init__.py b/mediagoblin/plugins/raven/__init__.py
new file mode 100644
index 00000000..8cfaed0a
--- /dev/null
+++ b/mediagoblin/plugins/raven/__init__.py
@@ -0,0 +1,92 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+from mediagoblin.tools import pluginapi
+
+_log = logging.getLogger(__name__)
+
+
+def get_client():
+ from raven import Client
+ config = pluginapi.get_config('mediagoblin.plugins.raven')
+
+ sentry_dsn = config.get('sentry_dsn')
+
+ client = None
+
+ if sentry_dsn:
+ _log.info('Setting up raven from plugin config: {0}'.format(
+ sentry_dsn))
+ client = Client(sentry_dsn)
+ elif os.environ.get('SENTRY_DSN'):
+ _log.info('Setting up raven from SENTRY_DSN environment variable: {0}'\
+ .format(os.environ.get('SENTRY_DSN')))
+ client = Client() # Implicitly looks for SENTRY_DSN
+
+ if not client:
+ _log.error('Could not set up client, missing sentry DSN')
+ return None
+
+ return client
+
+
+def setup_celery():
+ from raven.contrib.celery import register_signal
+
+ client = get_client()
+
+ register_signal(client)
+
+
+def setup_logging():
+ config = pluginapi.get_config('mediagoblin.plugins.raven')
+
+ conf_setup_logging = False
+ if config.get('setup_logging'):
+ conf_setup_logging = bool(int(config.get('setup_logging')))
+
+ if not conf_setup_logging:
+ return
+
+ from raven.handlers.logging import SentryHandler
+ from raven.conf import setup_logging
+
+ client = get_client()
+
+ _log.info('Setting up raven logging handler')
+
+ setup_logging(SentryHandler(client))
+
+
+def wrap_wsgi(app):
+ from raven.middleware import Sentry
+
+ client = get_client()
+
+ _log.info('Attaching raven middleware...')
+
+ return Sentry(app, client)
+
+
+hooks = {
+ 'setup': setup_logging,
+ 'wrap_wsgi': wrap_wsgi,
+ 'celery_logging_setup': setup_logging,
+ 'celery_setup': setup_celery,
+ }
diff --git a/mediagoblin/plugins/sampleplugin/README.rst b/mediagoblin/plugins/sampleplugin/README.rst
new file mode 100644
index 00000000..73897133
--- /dev/null
+++ b/mediagoblin/plugins/sampleplugin/README.rst
@@ -0,0 +1,8 @@
+==============
+ sampleplugin
+==============
+
+This is a sample plugin. It does nothing interesting other than show
+one way to structure a MediaGoblin plugin.
+
+The code for this plugin is in ``mediagoblin/plugins/sampleplugin/``.
diff --git a/mediagoblin/plugins/sampleplugin/__init__.py b/mediagoblin/plugins/sampleplugin/__init__.py
new file mode 100644
index 00000000..2cd077a2
--- /dev/null
+++ b/mediagoblin/plugins/sampleplugin/__init__.py
@@ -0,0 +1,42 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import logging
+
+from mediagoblin.tools.pluginapi import get_config
+
+
+_log = logging.getLogger(__name__)
+
+
+_setup_plugin_called = 0
+
+def setup_plugin():
+ global _setup_plugin_called
+
+ _log.info('Sample plugin set up!')
+ config = get_config('mediagoblin.plugins.sampleplugin')
+ if config:
+ _log.info('%r' % config)
+ else:
+ _log.info('There is no configuration set.')
+ _setup_plugin_called += 1
+
+
+hooks = {
+ 'setup': setup_plugin
+ }
diff --git a/mediagoblin/plugins/trim_whitespace/README.rst b/mediagoblin/plugins/trim_whitespace/README.rst
new file mode 100644
index 00000000..b55ce35e
--- /dev/null
+++ b/mediagoblin/plugins/trim_whitespace/README.rst
@@ -0,0 +1,25 @@
+=======================
+ Trim whitespace plugin
+=======================
+
+Mediagoblin templates are written with 80 char limit for better
+readability. However that means that the html output is very verbose
+containing LOTS of whitespace. This plugin inserts a Middleware that
+filters out whitespace from the returned HTML in the Response() objects.
+
+Simply enable this plugin by putting it somewhere where python can reach it and put it's path into the [plugins] section of your mediagoblin.ini or mediagoblin_local.ini like for example this:
+
+ [plugins]
+ [[mediagoblin.plugins.trim_whitespace]]
+
+There is no further configuration required. If this plugin is enabled,
+all text/html documents should not have lots of whitespace in between
+elements, although it does a very naive filtering right now (just keep
+the first whitespace and delete all subsequent ones).
+
+Nonetheless, it is a useful plugin that might serve as inspiration for
+other plugin writers.
+
+It was originally conceived by Sebastian Spaeth. It is licensed under
+the GNU AGPL v3 (or any later version) license.
+
diff --git a/mediagoblin/plugins/trim_whitespace/__init__.py b/mediagoblin/plugins/trim_whitespace/__init__.py
new file mode 100644
index 00000000..3da1e8b4
--- /dev/null
+++ b/mediagoblin/plugins/trim_whitespace/__init__.py
@@ -0,0 +1,73 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from __future__ import unicode_literals
+import logging
+import re
+
+from mediagoblin import meddleware
+
+_log = logging.getLogger(__name__)
+
+class TrimWhiteSpaceMeddleware(meddleware.BaseMeddleware):
+ _setup_plugin_called = 0
+ RE_MULTI_WHITESPACE = re.compile(b'(\s)\s+', re.M)
+
+ def process_response(self, request, response):
+ """Perform very naive html tidying by removing multiple whitespaces"""
+ # werkzeug.BaseResponse has no content_type attr, this comes via
+ # werkzeug.wrappers.CommonRequestDescriptorsMixin (part of
+ # wrappers.Response)
+ if getattr(response ,'content_type', None) != 'text/html':
+ return
+
+ # This is a tad more complex than needed to be able to handle
+ # response.data and response.body, depending on whether we have
+ # a werkzeug Resonse or a webob one. Let's kill webob soon!
+ if hasattr(response, 'body') and not hasattr(response, 'data'):
+ # Old-style webob Response object.
+ # TODO: Remove this once we transition away from webob
+ resp_attr = 'body'
+ else:
+ resp_attr = 'data'
+ # Don't flatten iterator to list when we fudge the response body
+ # (see werkzeug.Response documentation)
+ response.implicit_sequence_conversion = False
+
+ # Set the tidied text. Very naive tidying for now, just strip all
+ # subsequent whitespaces (this preserves most newlines)
+ setattr(response, resp_attr, re.sub(
+ TrimWhiteSpaceMeddleware.RE_MULTI_WHITESPACE, br'\1',
+ getattr(response, resp_attr)))
+
+ @classmethod
+ def setup_plugin(cls):
+ """Set up this meddleware as a plugin during 'setup' hook"""
+ global _log
+ if cls._setup_plugin_called:
+ _log.info('Trim whitespace plugin was already set up.')
+ return
+
+ _log.debug('Trim whitespace plugin set up.')
+ cls._setup_plugin_called += 1
+
+ # Append ourselves to the list of enabled Meddlewares
+ meddleware.ENABLED_MEDDLEWARE.append(
+ '{0}:{1}'.format(cls.__module__, cls.__name__))
+
+
+hooks = {
+ 'setup': TrimWhiteSpaceMeddleware.setup_plugin
+ }
diff --git a/mediagoblin/processing/__init__.py b/mediagoblin/processing/__init__.py
new file mode 100644
index 00000000..f3a85940
--- /dev/null
+++ b/mediagoblin/processing/__init__.py
@@ -0,0 +1,193 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import os
+
+from mediagoblin.db.util import atomic_update
+from mediagoblin import mg_globals as mgg
+
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+_log = logging.getLogger(__name__)
+
+
+class ProgressCallback(object):
+ def __init__(self, entry):
+ self.entry = entry
+
+ def __call__(self, progress):
+ if progress:
+ self.entry.transcoding_progress = progress
+ self.entry.save()
+
+
+def create_pub_filepath(entry, filename):
+ return mgg.public_store.get_unique_filepath(
+ ['media_entries',
+ unicode(entry.id),
+ filename])
+
+
+class FilenameBuilder(object):
+ """Easily slice and dice filenames.
+
+ Initialize this class with an original file path, then use the fill()
+ method to create new filenames based on the original.
+
+ """
+ MAX_FILENAME_LENGTH = 255 # VFAT's maximum filename length
+
+ def __init__(self, path):
+ """Initialize a builder from an original file path."""
+ self.dirpath, self.basename = os.path.split(path)
+ self.basename, self.ext = os.path.splitext(self.basename)
+ self.ext = self.ext.lower()
+
+ def fill(self, fmtstr):
+ """Build a new filename based on the original.
+
+ The fmtstr argument can include the following:
+ {basename} -- the original basename, with the extension removed
+ {ext} -- the original extension, always lowercase
+
+ If necessary, {basename} will be truncated so the filename does not
+ exceed this class' MAX_FILENAME_LENGTH in length.
+
+ """
+ basename_len = (self.MAX_FILENAME_LENGTH -
+ len(fmtstr.format(basename='', ext=self.ext)))
+ return fmtstr.format(basename=self.basename[:basename_len],
+ ext=self.ext)
+
+
+class ProcessingState(object):
+ """
+ The first and only argument to the "processor" of a media type
+
+ This could be thought of as a "request" to the processor
+ function. It has the main info for the request (media entry)
+ and a bunch of tools for the request on it.
+ It can get more fancy without impacting old media types.
+ """
+ def __init__(self, entry):
+ self.entry = entry
+ self.workbench = None
+ self.queued_filename = None
+
+ def set_workbench(self, wb):
+ self.workbench = wb
+
+ def get_queued_filename(self):
+ """
+ Get the a filename for the original, on local storage
+ """
+ if self.queued_filename is not None:
+ return self.queued_filename
+ queued_filepath = self.entry.queued_media_file
+ queued_filename = self.workbench.localized_file(
+ mgg.queue_store, queued_filepath,
+ 'source')
+ self.queued_filename = queued_filename
+ return queued_filename
+
+ def copy_original(self, target_name, keyname=u"original"):
+ self.store_public(keyname, self.get_queued_filename(), target_name)
+
+ def store_public(self, keyname, local_file, target_name=None):
+ if target_name is None:
+ target_name = os.path.basename(local_file)
+ target_filepath = create_pub_filepath(self.entry, target_name)
+ if keyname in self.entry.media_files:
+ _log.warn("store_public: keyname %r already used for file %r, "
+ "replacing with %r", keyname,
+ self.entry.media_files[keyname], target_filepath)
+ mgg.public_store.copy_local_to_storage(local_file, target_filepath)
+ self.entry.media_files[keyname] = target_filepath
+
+ def delete_queue_file(self):
+ # Remove queued media file from storage and database.
+ # queued_filepath is in the task_id directory which should
+ # be removed too, but fail if the directory is not empty to be on
+ # the super-safe side.
+ queued_filepath = self.entry.queued_media_file
+ mgg.queue_store.delete_file(queued_filepath) # rm file
+ mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir
+ self.entry.queued_media_file = []
+
+
+def mark_entry_failed(entry_id, exc):
+ """
+ Mark a media entry as having failed in its conversion.
+
+ Uses the exception that was raised to mark more information. If
+ the exception is a derivative of BaseProcessingFail then we can
+ store extra information that can be useful for users telling them
+ why their media failed to process.
+
+ Args:
+ - entry_id: The id of the media entry
+
+ """
+ # Was this a BaseProcessingFail? In other words, was this a
+ # type of error that we know how to handle?
+ if isinstance(exc, BaseProcessingFail):
+ # Looks like yes, so record information about that failure and any
+ # metadata the user might have supplied.
+ atomic_update(mgg.database.MediaEntry,
+ {'id': entry_id},
+ {u'state': u'failed',
+ u'fail_error': unicode(exc.exception_path),
+ u'fail_metadata': exc.metadata})
+ else:
+ _log.warn("No idea what happened here, but it failed: %r", exc)
+ # Looks like no, so just mark it as failed and don't record a
+ # failure_error (we'll assume it wasn't handled) and don't record
+ # metadata (in fact overwrite it if somehow it had previous info
+ # here)
+ atomic_update(mgg.database.MediaEntry,
+ {'id': entry_id},
+ {u'state': u'failed',
+ u'fail_error': None,
+ u'fail_metadata': {}})
+
+
+class BaseProcessingFail(Exception):
+ """
+ Base exception that all other processing failure messages should
+ subclass from.
+
+ You shouldn't call this itself; instead you should subclass it
+ and provid the exception_path and general_message applicable to
+ this error.
+ """
+ general_message = u''
+
+ @property
+ def exception_path(self):
+ return u"%s:%s" % (
+ self.__class__.__module__, self.__class__.__name__)
+
+ def __init__(self, **metadata):
+ self.metadata = metadata or {}
+
+
+class BadMediaFail(BaseProcessingFail):
+ """
+ Error that should be raised when an inappropriate file was given
+ for the media type specified.
+ """
+ general_message = _(u'Invalid file given for media type.')
diff --git a/mediagoblin/processing/task.py b/mediagoblin/processing/task.py
new file mode 100644
index 00000000..9af192ed
--- /dev/null
+++ b/mediagoblin/processing/task.py
@@ -0,0 +1,145 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import urllib
+import urllib2
+
+from celery import registry, task
+
+from mediagoblin import mg_globals as mgg
+from mediagoblin.db.models import MediaEntry
+from . import mark_entry_failed, BaseProcessingFail, ProcessingState
+from mediagoblin.tools.processing import json_processing_callback
+
+_log = logging.getLogger(__name__)
+logging.basicConfig()
+_log.setLevel(logging.DEBUG)
+
+
+@task.task(default_retry_delay=2 * 60)
+def handle_push_urls(feed_url):
+ """Subtask, notifying the PuSH servers of new content
+
+ Retry 3 times every 2 minutes if run in separate process before failing."""
+ if not mgg.app_config["push_urls"]:
+ return # Nothing to do
+ _log.debug('Notifying Push servers for feed {0}'.format(feed_url))
+ hubparameters = {
+ 'hub.mode': 'publish',
+ 'hub.url': feed_url}
+ hubdata = urllib.urlencode(hubparameters)
+ hubheaders = {
+ "Content-type": "application/x-www-form-urlencoded",
+ "Connection": "close"}
+ for huburl in mgg.app_config["push_urls"]:
+ hubrequest = urllib2.Request(huburl, hubdata, hubheaders)
+ try:
+ hubresponse = urllib2.urlopen(hubrequest)
+ except (urllib2.HTTPError, urllib2.URLError) as exc:
+ # We retry by default 3 times before failing
+ _log.info("PuSH url %r gave error %r", huburl, exc)
+ try:
+ return handle_push_urls.retry(exc=exc, throw=False)
+ except Exception as e:
+ # All retries failed, Failure is no tragedy here, probably.
+ _log.warn('Failed to notify PuSH server for feed {0}. '
+ 'Giving up.'.format(feed_url))
+ return False
+
+################################
+# Media processing initial steps
+################################
+
+class ProcessMedia(task.Task):
+ """
+ Pass this entry off for processing.
+ """
+ def run(self, media_id, feed_url):
+ """
+ Pass the media entry off to the appropriate processing function
+ (for now just process_image...)
+
+ :param feed_url: The feed URL that the PuSH server needs to be
+ updated for.
+ """
+ entry = MediaEntry.query.get(media_id)
+
+ # Try to process, and handle expected errors.
+ try:
+ entry.state = u'processing'
+ entry.save()
+
+ _log.debug('Processing {0}'.format(entry))
+
+ proc_state = ProcessingState(entry)
+ with mgg.workbench_manager.create() as workbench:
+ proc_state.set_workbench(workbench)
+ # run the processing code
+ entry.media_manager.processor(proc_state)
+
+ # We set the state to processed and save the entry here so there's
+ # no need to save at the end of the processing stage, probably ;)
+ entry.state = u'processed'
+ entry.save()
+
+ # Notify the PuSH servers as async task
+ if mgg.app_config["push_urls"] and feed_url:
+ handle_push_urls.subtask().delay(feed_url)
+
+ json_processing_callback(entry)
+ except BaseProcessingFail as exc:
+ mark_entry_failed(entry.id, exc)
+ json_processing_callback(entry)
+ return
+
+ except ImportError as exc:
+ _log.error(
+ 'Entry {0} failed to process due to an import error: {1}'\
+ .format(
+ entry.title,
+ exc))
+
+ mark_entry_failed(entry.id, exc)
+ json_processing_callback(entry)
+
+ except Exception as exc:
+ _log.error('An unhandled exception was raised while'
+ + ' processing {0}'.format(
+ entry))
+
+ mark_entry_failed(entry.id, exc)
+ json_processing_callback(entry)
+ raise
+
+ def on_failure(self, exc, task_id, args, kwargs, einfo):
+ """
+ If the processing failed we should mark that in the database.
+
+ Assuming that the exception raised is a subclass of
+ BaseProcessingFail, we can use that to get more information
+ about the failure and store that for conveying information to
+ users about the failure, etc.
+ """
+ entry_id = args[0]
+ mark_entry_failed(entry_id, exc)
+
+ entry = mgg.database.MediaEntry.query.filter_by(id=entry_id).first()
+ json_processing_callback(entry)
+
+# Register the task
+process_media = registry.tasks[ProcessMedia.name]
+
diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py
new file mode 100644
index 00000000..a650f22f
--- /dev/null
+++ b/mediagoblin/routing.py
@@ -0,0 +1,42 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+from mediagoblin.tools.routing import add_route, mount, url_map
+from mediagoblin.tools.pluginapi import PluginManager
+from mediagoblin.admin.routing import admin_routes
+from mediagoblin.auth.routing import auth_routes
+
+
+_log = logging.getLogger(__name__)
+
+
+def get_url_map():
+ add_route('index', '/', 'mediagoblin.views:root_view')
+ mount('/auth', auth_routes)
+ mount('/a', admin_routes)
+
+ import mediagoblin.submit.routing
+ import mediagoblin.user_pages.routing
+ import mediagoblin.edit.routing
+ import mediagoblin.webfinger.routing
+ import mediagoblin.listings.routing
+
+ for route in PluginManager().get_routes():
+ add_route(*route)
+
+ return url_map
diff --git a/mediagoblin/static/css/audio.css b/mediagoblin/static/css/audio.css
new file mode 100644
index 00000000..e007a0e1
--- /dev/null
+++ b/mediagoblin/static/css/audio.css
@@ -0,0 +1,84 @@
+.audio-spectrogram {
+ position: relative;
+}
+.playhead {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background: rgba(134, 212, 177, 0.3);
+ border-right: thin solid #ffaa00;
+ height: 100%;
+ -webkit-transition: width .1s ease-out;
+ -moz-transition: width .1s ease-out;
+ transition: width .1s ease-out;
+}
+.audio-control-play-pause {
+ position: absolute;
+ bottom: 0;
+ left: 5px;
+ cursor: pointer;
+ /* background: rgba(0, 0, 0, 0.7); */
+ font-size: 40px;
+ width: 50px;
+ text-shadow: 0 0 10px black;
+}
+ .audio-control-play-pause.playing {
+ color: #b71500;
+ letter-spacing: -17px;
+ margin-left: -7px;
+ }
+ .audio-control-play-pause.paused {
+ /* Warning: this means the the play button shows! */
+ color: rgb(134, 212, 177);
+ }
+
+.buffered-indicators {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ height: 2px;
+}
+ .buffered-indicators div {
+ position: absolute;
+ height: 2px;
+ left: 0;
+ background: rgba(134, 177, 212, 1);
+
+ -webkit-transition: left 1s ease-out;
+ -moz-transition: left 1s ease-out;
+ transition: left 1s ease-out;
+
+ -webkit-transition: width 1s ease-out;
+ -moz-transition: width 1s ease-out;
+ transition: width 1s ease-out;
+
+ cursor: pointer;
+ }
+
+.seekbar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.audio-currentTime {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ background: rgba(0, 0, 0, 0.7);
+}
+
+.audio-volume {
+ position: absolute;
+ left: 50px;
+ bottom: 10px;
+ opacity: 0.3;
+ -moz-transition: opacity .1s ease-in-out;
+ -webkit-transition: opacity .1s ease-in-out;
+ transition: opacity .1s ease-in-out;
+}
+ .audio-spectrogram:hover .audio-volume {
+ opacity: 0.7;
+ }
diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css
new file mode 100644
index 00000000..5b8226e6
--- /dev/null
+++ b/mediagoblin/static/css/base.css
@@ -0,0 +1,748 @@
+/* @font-face */
+
+@font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 700;
+ src: local('Lato Bold'), local('Lato-Bold'), url('../fonts/Lato-Bold.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Lato';
+ font-style: italic;
+ font-weight: 400;
+ src: local('Lato Italic'), local('Lato-Italic'), url('../fonts/Lato-Italic.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Lato';
+ font-style: italic;
+ font-weight: 700;
+ src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url('../fonts/Lato-BoldItalic.ttf') format('truetype');
+}
+@font-face {
+ font-family: 'Lato';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Lato Regular'), local('Lato-Regular'), url('../fonts/Lato-Regular.ttf') format('truetype');
+}
+
+body {
+ background-color: #161616;
+ color: #C3C3C3;
+ padding: 0;
+ margin: 0px;
+ height: 100%;
+ font: 16px 'Lato', 'Helvetica Neue', Arial, 'Liberation Sans', FreeSans, sans-serif;
+}
+
+form {
+ margin: 0px;
+ padding: 0px;
+}
+
+/* text styles */
+
+h1,h2,h3,p {
+ margin-bottom: 20px;
+}
+
+h1,h2,h3 {
+ font-weight: bold;
+}
+
+h1 {
+ margin-top: 15px;
+ color: #fff;
+ font-size: 1.875em;
+}
+
+h2 {
+ font-size: 1.375em;
+ margin-top: 20px;
+ color: #fff;
+}
+
+h3 {
+ border-bottom: 1px solid #333;
+ font-size: 1.125em;
+}
+
+p {
+ margin-top: 0px;
+}
+
+a {
+ color: #86D4B1;
+}
+
+a.highlight {
+ color: #fff;
+}
+
+em {
+ font-style: italic;
+}
+
+strong {
+ font-weight: bold;
+}
+
+ul {
+ list-style: disc inside;
+}
+
+ol {
+ list-style: decimal inside;
+}
+
+label {
+ font-weight: normal;
+}
+
+input, textarea {
+ font-size:1em;
+ font-family:'Lato', sans-serif;
+}
+
+/* website structure */
+
+.container {
+ margin: auto;
+ width: 96%;
+ max-width: 940px;
+}
+
+header {
+ width: 100%;
+ max-width: 940px;
+ margin-left: auto;
+ margin-right: auto;
+ padding: 0;
+ margin-bottom: 42px;
+ border-bottom: 1px solid #333;
+}
+
+.header_right {
+ margin: 8px;
+ display: inline-block;
+ float: right;
+}
+
+.header_dropdown {
+ margin-bottom: 20px;
+}
+
+.header_dropdown li {
+ margin: 4px 0;
+ list-style: none;
+}
+
+.header_dropdown p {
+ margin-top: 12px;
+ margin-bottom: 10px;
+}
+
+.dropdown_title {
+ font-size: 20px;
+}
+
+a.logo {
+ color: #fff;
+ font-weight: bold;
+}
+
+.logo img {
+ vertical-align: middle;
+ margin: 6px 8px 6px 0;
+}
+
+.mediagoblin_content {
+ width: 100%;
+ padding-bottom: 74px;
+}
+
+footer {
+ width: 100%;
+ height: 30px;
+ border-top: 1px solid #333;
+ bottom: 0px;
+ padding: 8px 0;
+ text-align: center;
+ font-size: 0.875em;
+ clear: both;
+}
+
+.media_pane {
+ width: 640px;
+ margin-left: 0px;
+ margin-right: 10px;
+ float: left;
+}
+
+.media_sidebar {
+ width: 280px;
+ margin-left: 10px;
+ float: left;
+}
+
+.profile_sidebar {
+ width: 340px;
+ margin-right: 10px;
+ float: left;
+}
+
+.profile_showcase {
+ width: 580px;
+ margin-left: 10px;
+ float: left;
+}
+
+/* common website elements */
+
+.button_action, .button_action_highlight, .button_form {
+ display: inline-block;
+ color: #c3c3c3;
+ background-color: #363636;
+ border: 1px solid;
+ border-color: #464646 #2B2B2B #252525;
+ border-radius: 4px;
+ padding: 3px 8px;
+ font-size: 16px;
+ text-decoration: none;
+ font-style: normal;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.button_action_highlight, .button_form {
+ background-color: #86D4B1;
+ border-color: #A2DEC3 #6CAA8E #5C9179;
+ color: #283F35;
+}
+
+.button_form {
+ min-width: 99px;
+ margin: 10px 0px 10px 15px;
+ text-align: center;
+ font-family: 'Lato', sans-serif;
+}
+
+.pagination {
+text-align: center;
+}
+
+.pagination_arrow {
+ margin: 5px;
+}
+
+.empty_space {
+ background-image: url("../images/empty_back.png");
+ font-style: italic;
+ text-align: center;
+ height: 160px;
+ padding-top: 70px;
+}
+
+.right_align {
+ float: right;
+}
+
+.clear {
+ clear: both;
+ display: block;
+ overflow: hidden;
+ visibility: hidden;
+ width: 0;
+ height: 0;
+}
+
+.hidden {
+ display: none;
+}
+
+.media_sidebar h3 {
+ font-size: 1em;
+ margin: 0 0 5px;
+ border: none;
+}
+
+.media_sidebar p {
+ margin-left: 8px;
+}
+
+/* forms */
+
+.form_box,.form_box_xl {
+ background-color: #222;
+ border-top: 6px solid #D49086;
+ padding: 3% 5%;
+ display: block;
+ float: none;
+ width: 90%;
+ max-width: 340px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.form_box_xl {
+ max-width: 460px;
+}
+
+.edit_box {
+ border-top: 6px dashed #D49086
+}
+
+.form_field_input input, .form_field_input textarea {
+ width: 100%;
+}
+
+.form_field_input {
+ margin-bottom: 10px;
+}
+
+.form_field_label {
+ margin-bottom: 4px;
+}
+
+.form_field_label {
+ font-size:1.125em;
+}
+
+.form_field_description {
+ font-style: italic;
+}
+
+.form_field_error {
+ background-color: #87453b;
+ color: #fff;
+ border: none;
+ padding: 9px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+
+.form_submit_buttons {
+ text-align: right;
+}
+
+.subform {
+ margin: 2em;
+}
+
+#password_boolean {
+ margin-top: 4px;
+ width: 20px;
+}
+
+textarea#description, textarea#bio {
+ resize: vertical;
+ height: 100px;
+}
+
+.delete {
+ margin-top: 36px;
+ display: block;
+ text-align: center;
+}
+
+/* comments */
+
+.comment_wrapper {
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+.comment_wrapper p {
+ margin-bottom: 2px;
+}
+
+.comment_author {
+ padding-top: 4px;
+ font-size: 0.9em;
+}
+
+a.comment_authorlink {
+ text-decoration: none;
+ padding-right: 5px;
+ font-weight: bold;
+ padding-left: 2px;
+}
+
+a.comment_authorlink:hover {
+ text-decoration: underline;
+}
+
+a.comment_whenlink {
+ text-decoration: none;
+}
+
+a.comment_whenlink:hover {
+ text-decoration: underline;
+}
+
+.comment_content {
+ margin-left: 8px;
+ margin-top: 8px;
+}
+
+textarea#comment_content {
+ resize: vertical;
+ width: 100%;
+ height: 90px;
+ border: none;
+ background-color: #f1f1f1;
+ padding: 3px;
+}
+
+#form_comment .form_field_input {
+ padding-right: 6px;
+}
+
+/* media galleries */
+
+.media_thumbnail {
+ float: left;
+ padding: 0px;
+ width: 180px;
+ overflow: hidden;
+ margin: 0px 3px 10px;
+ text-align: center;
+ font-size: 0.875em;
+ background-color: #222;
+ border-radius: 0 0 5px 5px;
+ padding: 0 0 6px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ border-color: #0D0D0D;
+ border-style: solid;
+ border-width: 1px 1px 2px;
+}
+
+.media_thumbnail a {
+ color: #eee;
+ text-decoration: none;
+ display: block;
+}
+
+.media_thumbnail a.remove {
+ color: #86D4B1;
+ text-decoration: underline;
+}
+
+a.thumb_entry_title {
+ padding: 8px;
+}
+
+/* For now, this is commented out since our thumbnails are actually 180px high.
+ *
+ * .media_thumbnail img {
+ * max-height: 135px;
+ * }
+ */
+
+.thumb_entry_last {
+ margin-right: 0px;
+}
+
+/* media detail */
+
+h2.media_title {
+ margin-bottom: 0px;
+ display: inline-block;
+}
+
+p.context {
+ display: inline-block;
+ padding-top: 4px;
+}
+
+p.media_specs {
+ font-size: 0.9em;
+ border-top: 1px solid #222;
+ padding: 10px 0px;
+ color: #888;
+}
+
+.no_html5 {
+ background: black;
+ color: white;
+ text-align: center;
+ height: 160px;
+ padding: 130px 10px 20px 10px;
+}
+
+a img.media_image {
+ cursor: -webkit-zoom-in;
+ cursor: -moz-zoom-in;
+ cursor: zoom-in;
+}
+
+/* icons */
+
+img.media_icon {
+ margin: 0 4px;
+ vertical-align: sub;
+}
+
+/* EXIF information */
+
+#exif_content h3 {
+ border-bottom: 1px solid #333;
+}
+
+#exif_camera_information {
+ margin-bottom: 20px;
+}
+
+#exif_additional_info {
+ display: none;
+}
+
+#exif_additional_info table {
+ font-size: 11px;
+ margin-top: 10px;
+}
+
+#exif_additional_info td {
+ vertical-align: top;
+ padding-bottom: 5px;
+}
+
+#exif_content .col1 {
+ padding-right: 20px;
+}
+
+#exif_additional_info table tr {
+ margin-bottom: 10px;
+}
+
+/* navigation */
+
+.navigation {
+ float: right;
+}
+
+.navigation_button {
+ width: 135px;
+ display: inline-block;
+ text-align: center;
+ background-color: #1d1d1d;
+ border: 1px solid;
+ border-color: #2c2c2c #232323 #1a1a1a;
+ border-radius: 4px;
+ text-decoration: none;
+ padding: 4px 0 8px;
+ margin: 0 0 6px;
+}
+
+.navigation_left {
+ margin-right: 6px;
+}
+
+/* messages */
+
+ul.mediagoblin_messages {
+ list-style: none inside;
+ color: #f7f7f7;
+ padding: 0;
+}
+
+.mediagoblin_messages li {
+ margin: 5px 0;
+ padding: 8px;
+ text-align: center;
+}
+
+.message_success {
+ background-color: #378566;
+}
+
+.message_warning {
+ background-color: #87453b;
+}
+
+.message_error {
+ background-color: #87453b;
+}
+
+.message_info {
+ background-color: #378566;
+}
+
+.message_debug {
+ background-color: #f7f7f7;
+ color: #272727;
+}
+
+ul.mediaentry_tags {
+ list-style-type: none;
+}
+
+ul.mediaentry_tags li {
+ display: inline;
+ margin: 0px 5px 0px 0px;
+ padding: 0px;
+}
+
+
+/* media processing panel */
+
+table.media_panel {
+ width: 100%;
+}
+
+table.media_panel th {
+ font-weight: bold;
+ padding-bottom: 4px;
+ text-align: left;
+}
+
+
+/* Delete panel */
+
+.delete_checkbox_box {
+ margin-top: 10px;
+ margin-left: 10px;
+}
+
+/* ASCII art and code */
+
+@font-face {
+ font-family: Inconsolata;
+ src: local('Inconsolata'), url('../fonts/Inconsolata.otf') format('opentype')
+}
+
+pre, code {
+ font-family: Inconsolata, monospace;
+ line-height: 1em;
+}
+
+pre {
+ overflow: auto;
+ margin-bottom: 20px;
+}
+
+.comment_wrapper pre {
+ margin-bottom: 2px;
+}
+
+.ascii-wrapper pre {
+ /* but it should not affect the ASCII art */
+ margin: 0;
+}
+
+/* Media queries and other responsivisivity */
+@media screen and (max-width: 940px) {
+ .media_pane {
+ width: 100%;
+ margin: 0px;
+ }
+
+ .media_sidebar {
+ width: 100%;
+ margin: 0px;
+ }
+
+ img.media_image {
+ width: 100%;
+ display: inline;
+ }
+
+ .media_thumbnail {
+ width: 21%;
+ }
+
+ .profile_sidebar {
+ width: 100%;
+ margin: 0px;
+ }
+
+ .profile_showcase {
+ width: 100%;
+ margin: 0px;
+ }
+
+ .navigation {
+ float: none;
+ }
+
+ .navigation_button {
+ width: 49%;
+ float: right;
+ }
+
+ .navigation_left {
+ margin-right: 0;
+ float: left;
+ }
+
+ .navigation {
+ float: none;
+ }
+
+ .navigation_button {
+ width: 49%;
+ float: right;
+ padding: 10px 0 14px;
+ }
+
+ .navigation_left {
+ margin-right: 0;
+ float: left;
+ }
+
+ .button_action, .button_action_highlight, .button_form {
+ padding: 9px 14px;
+ }
+
+ header {
+ text-align: center;
+ }
+
+ .header_right {
+ margin-right: 2%;
+ float: none;
+ }
+
+ a.logo {
+ margin-left: 2%;
+ }
+}
+
+@media screen and (max-width: 570px) {
+ .media_thumbnail {
+ width: 29%;
+ }
+}
+
+@media screen and (max-width: 380px) {
+ .media_thumbnail {
+ width: 46%;
+ }
+}
+
+/* Exif display */
+#exif_content h3 {
+ border-bottom: 1px solid #333;
+}
+#exif_camera_information {
+ margin-bottom: 20px;
+}
+
+#exif_additional_info {
+ display: none;
+}
+#exif_additional_info table {
+ font-size: 11px;
+ margin-top: 10px;
+}
+#exif_additional_info td {
+ vertical-align: top;
+ padding-bottom: 5px;
+}
+#exif_content .col1 {
+ padding-right: 20px;
+}
+#exif_additional_info table tr {
+ margin-bottom: 10px;
+}
diff --git a/mediagoblin/static/css/extlib/reset.css b/mediagoblin/static/css/extlib/reset.css
new file mode 120000
index 00000000..6084e137
--- /dev/null
+++ b/mediagoblin/static/css/extlib/reset.css
@@ -0,0 +1 @@
+../../../../extlib/reset/reset.css \ No newline at end of file
diff --git a/mediagoblin/static/css/vjs-mg-skin.css b/mediagoblin/static/css/vjs-mg-skin.css
new file mode 100644
index 00000000..98c4eb95
--- /dev/null
+++ b/mediagoblin/static/css/vjs-mg-skin.css
@@ -0,0 +1,415 @@
+.video-js {
+ background-color: #000; position: relative; padding: 0; outline: none;
+
+ /* Start with 10px for base font size so other dimensions can be em based and easily calculable. */
+ font-size: 10px;
+
+ width: 650px !important;
+ height: 366px !important;
+
+
+ /* Allow poster to be vertially aligned. */
+ vertical-align: middle;
+ /* display: table-cell; */ /*This works in Safari but not Firefox.*/
+}
+
+/* Playback technology elements expand to the width/height of the containing div. <video> or <object> */
+.video-js .vjs-tech { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+/* Fix for Firefox 9 fullscreen (only if it is enabled). Not needed when checking fullScreenEnabled. */
+.video-js:-moz-full-screen { position: absolute; }
+
+/* Fullscreen Styles */
+body.vjs-full-window {
+ padding: 0; margin: 0;
+ height: 100%; overflow-y: auto; /* Fix for IE6 full-window. http://www.cssplay.co.uk/layouts/fixed.html */
+}
+.video-js.vjs-fullscreen {
+ position: fixed; overflow: hidden; z-index: 1000; left: 0; top: 0; bottom: 0; right: 0; width: 100% !important; height: 100% !important;
+ _position: absolute; /* IE6 Full-window (underscore hack) */
+}
+.video-js:-webkit-full-screen {
+ width: 100% !important; height: 100% !important;
+}
+
+/* Poster Styles */
+.vjs-poster {
+ margin: 0 auto; padding: 0; cursor: pointer;
+
+ /* Scale with the size of the player div. Works when poster is vertically shorter, but stretches when it's less wide. */
+ position: relative; width: 100%; max-height: 100%;
+}
+
+/* Subtiles Styles */
+.video-js .vjs-subtitles { color: #fff; font-size: 20px; text-align: center; position: absolute; bottom: 40px; left: 0; right: 0; }
+
+/* Fading sytles, used to fade control bar. */
+.vjs-fade-in {
+ visibility: visible !important; /* Needed to make sure things hide in older browsers too. */
+ opacity: 0.9 !important;
+
+ -webkit-transition: visibility 0s linear 0s, opacity 0.3s linear;
+ -moz-transition: visibility 0s linear 0s, opacity 0.3s linear;
+ -ms-transition: visibility 0s linear 0s, opacity 0.3s linear;
+ -o-transition: visibility 0s linear 0s, opacity 0.3s linear;
+ transition: visibility 0s linear 0s, opacity 0.3s linear;
+}
+.vjs-fade-out {
+ visibility: hidden !important;
+ opacity: 0 !important;
+
+ -webkit-transition: visibility 0s linear 1.5s,opacity 1.5s linear;
+ -moz-transition: visibility 0s linear 1.5s,opacity 1.5s linear;
+ -ms-transition: visibility 0s linear 1.5s,opacity 1.5s linear;
+ -o-transition: visibility 0s linear 1.5s,opacity 1.5s linear;
+ transition: visibility 0s linear 1.5s,opacity 1.5s linear;
+}
+
+/* The control bar
+---------------------------------------------------------------------------------- */
+.vjs-mg-skin .vjs-controls {
+ position: absolute;
+ bottom: 0; /* Distance from the bottom of the box/video. Keep 0. Use height to add more bottom margin. */
+ left: 0; right: 0; /* 100% width of div */
+ margin: 0; padding: 0; /* Controls are absolutely position, so no padding necessary */
+ height: 30px; /* Including any margin you want above or below control items */
+ color: #fff; border-top: 1px solid #404040; border-bottom: 1px solid #1f1f1f;
+
+ /* CSS Gradient */
+ /* Can use the Ultimate CSS Gradient Generator: http://www.colorzilla.com/gradient-editor/ */
+ background: #242424; /* Old browsers */
+ background: -moz-linear-gradient(top, #242424 50%, #1f1f1f 50%, #171717 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(50%,#242424), color-stop(50%,#1f1f1f), color-stop(100%,#171717)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* IE10+ */
+ /* Filter was causing a lot of weird issues in IE. Elements would stop showing up, or other styles would break. */
+ /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#242424', endColorstr='#171717',GradientType=0 );*/ /* IE6-9 */
+ background: linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* W3C */
+
+ /* Start hidden and with 0 opacity. Opacity is used to fade in modern browsers. */
+ /* Can't use display block to hide initially because widths of slider handles aren't calculated and avaialbe for positioning correctly. */
+ visibility: hidden;
+ opacity: 0;
+}
+
+/* General styles for individual controls. */
+.vjs-mg-skin .vjs-control {
+ position: relative; float: left;
+ text-align: center; margin: 0; padding: 0;
+}
+
+.vjs-mg-skin .vjs-control:focus {
+ outline: 0;
+}
+
+/* Hide control text visually, but have it available for screenreaders: h5bp.com/v */
+.vjs-mg-skin .vjs-control-text { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+
+
+
+/* Play/Pause
+-------------------------------------------------------------------------------- */
+.vjs-mg-skin .vjs-play-control { width: 38px; height: 30px; border-right: 1px solid #101010; cursor: pointer !important; border-left: 1px solid #333; border-bottom: 1px solid #1F1F1F; }
+/* Play Icon */
+.vjs-mg-skin.vjs-paused .vjs-play-control div { width: 15px; height: 17px; background: url('../images/video-js.png'); margin: 0; margin-left: 13px; margin-top: 7px; }
+.vjs-mg-skin.vjs-playing .vjs-play-control div { width: 15px; height: 17px; background: url('../images/video-js.png') -25px 0; margin: 0; margin-left: 13px; margin-top: 7px; }
+
+
+/* Rewind
+-------------------------------------------------------------------------------- */
+.vjs-mg-skin .vjs-rewind-control { width: 5em; cursor: pointer !important; }
+.vjs-mg-skin .vjs-rewind-control div { width: 19px; height: 16px; background: url('../images/video-js.png'); margin: 0.5em auto 0; }
+
+/* Volume/Mute
+-------------------------------------------------------------------------------- */
+.vjs-mg-skin .vjs-mute-control { width: 38px; height: 30px; border-left: 1px solid #333; cursor: pointer !important; float: right; }
+.vjs-mg-skin .vjs-mute-control div { width: 22px; height: 16px; background: url('../images/video-js.png') -75px -25px; margin:0; margin-left: 8px; margin-top: 8px; }
+.vjs-mg-skin .vjs-mute-control.vjs-vol-0 div { background: url('../images/video-js.png') 0 -25px; }
+.vjs-mg-skin .vjs-mute-control.vjs-vol-1 div { background: url('../images/video-js.png') -25px -25px; }
+.vjs-mg-skin .vjs-mute-control.vjs-vol-2 div { background: url('../images/video-js.png') -50px -25px; }
+
+
+.vjs-mg-skin .vjs-volume-control { width: 85px; height: 30px; float: right; border-right: 1px solid #333; }
+.vjs-mg-skin .vjs-volume-bar {
+ position: relative; width: 70px; height: 0.6em; margin:0; margin-left: 2px; margin-top: 11px; cursor: pointer !important;
+
+ /* -moz-border-radius: 0.3em; -webkit-border-radius: 0.3em; border-radius: 0.3em; */
+
+ background: #666;
+ background: -moz-linear-gradient(top, #333, #666);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#333), to(#666));
+ background: -webkit-linear-gradient(top, #333, #666);
+ background: -o-linear-gradient(top, #333, #666);
+ background: -ms-linear-gradient(top, #333, #666);
+ background: linear-gradient(top, #333, #666);
+}
+
+.video-js:-moz .vjs-volume-bar { margin-top: 12px; }
+
+.vjs-mg-skin .vjs-volume-level {
+ position: absolute; top: 0; left: 0; height: 0.6em;
+
+ /* -moz-border-radius: 0.3em; -webkit-border-radius: 0.3em; border-radius: 0.3em; */
+ /* CSS Gradient. */
+ background: #86D4B1; /* Old browsers */
+ background: -moz-linear-gradient(top, #86D4B1 0%, #5d937a 50%, #86D4B1 100%);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#86D4B1), color-stop(50%,#5d937a), color-stop(100%,#86D4B1));
+ background: -webkit-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+ background: -o-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+ background: -ms-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+ background: linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+
+
+}
+.vjs-mg-skin .vjs-volume-handle {
+ position: absolute; top: -4px; width: 14px; height: 14px; left: 0;
+ background: url('../images/video-js.png') 0 -50px;
+}
+
+.video-js:-moz .vjs-volume-handle { top: -1px;}
+
+
+
+
+/* Progress
+-------------------------------------------------------------------------------- */
+.vjs-mg-skin div.vjs-progress-control {
+ position: absolute;
+ top: -15px;
+ width: 100%;
+ height: 12px;
+}
+
+/* Box containing play and load progresses. Also acts as seek scrubber. */
+.vjs-mg-skin .vjs-progress-holder {
+ position: relative; cursor: pointer !important; /*overflow: hidden;*/
+ padding: 0; margin: 0; /* Placement within the progress control item */
+ height: 12px;
+ border-top: 1px solid #333;
+ border-bottom: 1px solid #111;
+
+
+/* -moz-border-radius: 0.6em; -webkit-border-radius: 0.6em; border-radius: 0.6em; */
+
+ /* CSS Gradient */
+ background: #111;
+ background: -moz-linear-gradient(top, #111, #262626);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#111), to(#262626));
+ background: -webkit-linear-gradient(top, #111, #262626);
+ background: -o-linear-gradient(top, #111, #262626);
+ background: -ms-linear-gradient(top, #111, #262626);
+ background: linear-gradient(top, #111, #262626);
+}
+.vjs-mg-skin .vjs-progress-holder .vjs-play-progress,
+.vjs-mg-skin .vjs-progress-holder .vjs-load-progress { /* Progress Bars */
+ position: absolute; display: block; height: 12px; margin: 0; padding: 0;
+ left: 0; top: 0; /*Needed for IE6*/
+ /* -moz-border-radius: 0.6em; -webkit-border-radius: 0.6em; border-radius: 0.6em; */
+
+ /*width: 0;*/
+}
+
+.vjs-mg-skin .vjs-play-progress {
+ /* CSS Gradient. */
+ background: #86D4B1; /* Old browsers */
+ background: -moz-linear-gradient(top, #86D4B1 0%, #5d937a 50%, #86D4B1 100%);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#86D4B1), color-stop(50%,#5d937a), color-stop(100%,#86D4B1));
+ background: -webkit-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+ background: -o-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+ background: -ms-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+ background: linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%);
+
+ background: #86D4B1;
+ background: -moz-linear-gradient(top, #5d937a 0%, #5d937a 50%, #86D4B1 50%, #5d937a 100%);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#5d937a), color-stop(50%,#86D4B1), color-stop(50%,#86D4B1), color-stop(100%,#5d937a));
+ background: -webkit-linear-gradient(top, #5d937a 0%,#86D4B1 50%,#86D4B1 50%,#5d937a 100%);
+ background: -o-linear-gradient(top, #5d937a 0%,#86D4B1 50%,#5d937a 50%, 100%);
+ background: -ms-linear-gradient(top, #5d937a 0%,#86D4B1 50%,#86D4B1 50%,#5d937a 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#86D4B1', endColorstr='#5d937a',GradientType=0 );
+ background: linear-gradient(top, #5d937a 0%,#86D4B1 50%,#86D4B1 50%,#5d937a 100%);
+}
+.vjs-mg-skin .vjs-load-progress {
+ opacity: 0.8;
+
+ /* CSS Gradient */
+ background: #666;
+ background: -moz-linear-gradient(top, #666, #333);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#666), to(#333));
+ background: -webkit-linear-gradient(top, #666, #333);
+ background: -o-linear-gradient(top, #666, #333);
+ background: -ms-linear-gradient(top, #666, #333);
+ background: linear-gradient(top, #666, #333);
+}
+
+.vjs-mg-skin div.vjs-seek-handle {
+ position: absolute;
+ width: 16px; height: 16px; /* Match img pixles */
+ margin-top: -0.2em;
+ left: 0; top: 0; /*Needed for IE6*/
+
+ background: url('../images/video-js.png') 0 -50px;
+ /* CSS Curved Corners. Needed to make shadows curved. */
+ -moz-border-radius: 0.8em; -webkit-border-radius: 0.8em; border-radius: 0.8em;
+ /* CSS Shadows */
+ -webkit-box-shadow: 0 2px 4px 0 #000; -moz-box-shadow: 0 2px 4px 0 #000; box-shadow: 0 2px 4px 0 #000;
+}
+/* Time Display
+-------------------------------------------------------------------------------- */
+.vjs-mg-skin .vjs-time-controls {
+ height: 18px; width: 45px;
+ margin-top: 5px;
+ margin-left: 5px;
+ font-size: 14px; line-height: 18px; font-weight: normal; font-family: Helvetica, Arial, sans-serif;
+ border-left: 1px solid #000000;
+ border-top: 1px solid #000;
+ border-bottom: 1px solid #333;
+ border-right: 1px solid #333;
+ -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px;
+
+ /* CSS Gradient */
+ background: #111;
+ background: -moz-linear-gradient(top, #111, #262626);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#111), to(#262626));
+ background: -webkit-linear-gradient(top, #111, #262626);
+ background: -o-linear-gradient(top, #111, #262626);
+ background: -ms-linear-gradient(top, #111, #262626);
+ background: linear-gradient(top, #111, #262626);
+
+
+}
+
+.vjs-mg-skin .vjs-current-time { }
+
+.vjs-mg-skin .vjs-duration { right: 0; display: none; }
+.vjs-mg-skin .vjs-remaining-time { display: block; }
+
+.vjs-time-divider { }
+
+.vjs-mg-skin .vjs-time-control { font-size: 12px; line-height: 16px; font-weight: normal; font-family: Helvetica, Arial, sans-serif; }
+.vjs-mg-skin .vjs-time-control span { line-height: 25px; /* Centering vertically */ }
+
+.vjs-mg-skin .vjs-time-divider { display: none; visibility: hidden; }
+
+/* Fullscreen
+-------------------------------------------------------------------------------- */
+.vjs-secondary-controls { float: right; }
+
+.vjs-mg-skin .vjs-fullscreen-control { height: 30px; width: 38px; cursor: pointer !important; float: right; border-left: 1px solid #111; }
+.vjs-mg-skin .vjs-fullscreen-control div { width: 16px; height: 16px; background: url('../images/video-js.png') -50px 0; margin: 0; margin-left: 11px; margin-top: 8px; }
+
+.video-js.vjs-fullscreen .vjs-fullscreen-control div { background: url('../images/video-js.png') -75px 0; }
+.video-js:-webkit-full-screen .vjs-fullscreen-control div { background: url('../images/video-js.png') -75px 0; }
+
+
+
+/* Big Play Button (at start)
+---------------------------------------------------------*/
+.vjs-mg-skin .vjs-big-play-button {
+ display: block; /* Start hidden */ z-index: 2;
+ position: absolute; top: 50%; left: 50%; width: 8.0em; height: 8.0em; margin: -43px 0 0 -43px; text-align: center; vertical-align: center; cursor: pointer !important;
+ border: 0.3em solid #86D4B1; opacity: 0.95;
+ -webkit-border-radius: 25px; -moz-border-radius: 25px; border-radius: 25px;
+
+ background: #454545;
+ background: -moz-linear-gradient(top, #454545 0%, #232323 50%, #161616 50%, #3f3f3f 100%);
+ background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#454545), color-stop(50%,#232323), color-stop(50%,#161616), color-stop(100%,#3f3f3f));
+ background: -webkit-linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%);
+ background: -o-linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%);
+ background: -ms-linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#454545', endColorstr='#3f3f3f',GradientType=0 );
+ background: linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%);
+
+ /* CSS Shadows */
+ -webkit-box-shadow: 4px 4px 8px #000; -moz-box-shadow: 4px 4px 8px #000; box-shadow: 4px 4px 8px #000;
+}
+
+.vjs-mg-skin div.vjs-big-play-button:hover {
+ -webkit-box-shadow: 0 0 80px #fff; -moz-box-shadow: 0 0 80px #fff; box-shadow: 0 0 80px #fff;
+}
+
+.vjs-mg-skin div.vjs-big-play-button span {
+ position: absolute; top: 50%; left: 50%;
+ display: block; width: 35px; height: 42px;
+ margin: -20px 0 0 -15px; /* Using negative margin to center image. */
+ background: url('../images/video-js.png') -100px 0;
+}
+
+/* Loading Spinner
+---------------------------------------------------------*/
+/* CSS Spinners by Kilian Valkhof - http://kilianvalkhof.com/2010/css-xhtml/css3-loading-spinners-without-images/ */
+.vjs-loading-spinner {
+ display: none;
+ position: absolute; top: 50%; left: 50%; width: 55px; height: 55px;
+ margin: -28px 0 0 -28px;
+ -webkit-animation-name: rotatethis;
+ -webkit-animation-duration:1s;
+ -webkit-animation-iteration-count:infinite;
+ -webkit-animation-timing-function:linear;
+ -moz-animation-name: rotatethis;
+ -moz-animation-duration:1s;
+ -moz-animation-iteration-count:infinite;
+ -moz-animation-timing-function:linear;
+}
+
+@-webkit-keyframes rotatethis {
+ 0% {-webkit-transform:scale(0.6) rotate(0deg); }
+ 12.5% {-webkit-transform:scale(0.6) rotate(0deg); }
+ 12.51% {-webkit-transform:scale(0.6) rotate(45deg); }
+ 25% {-webkit-transform:scale(0.6) rotate(45deg); }
+ 25.01% {-webkit-transform:scale(0.6) rotate(90deg);}
+ 37.5% {-webkit-transform:scale(0.6) rotate(90deg);}
+ 37.51% {-webkit-transform:scale(0.6) rotate(135deg);}
+ 50% {-webkit-transform:scale(0.6) rotate(135deg);}
+ 50.01% {-webkit-transform:scale(0.6) rotate(180deg);}
+ 62.5% {-webkit-transform:scale(0.6) rotate(180deg);}
+ 62.51% {-webkit-transform:scale(0.6) rotate(225deg);}
+ 75% {-webkit-transform:scale(0.6) rotate(225deg);}
+ 75.01% {-webkit-transform:scale(0.6) rotate(270deg);}
+ 87.5% {-webkit-transform:scale(0.6) rotate(270deg);}
+ 87.51% {-webkit-transform:scale(0.6) rotate(315deg);}
+ 100% {-webkit-transform:scale(0.6) rotate(315deg);}
+}
+
+@-moz-keyframes rotatethis {
+ 0% {-moz-transform:scale(0.6) rotate(0deg);}
+ 12.5% {-moz-transform:scale(0.6) rotate(0deg);}
+ 12.51% {-moz-transform:scale(0.6) rotate(45deg);}
+ 25% {-moz-transform:scale(0.6) rotate(45deg);}
+ 25.01% {-moz-transform:scale(0.6) rotate(90deg);}
+ 37.5% {-moz-transform:scale(0.6) rotate(90deg);}
+ 37.51% {-moz-transform:scale(0.6) rotate(135deg);}
+ 50% {-moz-transform:scale(0.6) rotate(135deg);}
+ 50.01% {-moz-transform:scale(0.6) rotate(180deg);}
+ 62.5% {-moz-transform:scale(0.6) rotate(180deg);}
+ 62.51% {-moz-transform:scale(0.6) rotate(225deg);}
+ 75% {-moz-transform:scale(0.6) rotate(225deg);}
+ 75.01% {-moz-transform:scale(0.6) rotate(270deg);}
+ 87.5% {-moz-transform:scale(0.6) rotate(270deg);}
+ 87.51% {-moz-transform:scale(0.6) rotate(315deg);}
+ 100% {-moz-transform:scale(0.6) rotate(315deg);}
+}
+/* Each circle */
+div.vjs-loading-spinner .ball1 { opacity: 0.12; position:absolute; left: 20px; top: 0px; width: 13px; height: 13px; background: #fff;
+ border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; }
+
+div.vjs-loading-spinner .ball2 { opacity: 0.25; position:absolute; left: 34px; top: 6px; width: 13px; height: 13px; background: #fff;
+ border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; }
+
+div.vjs-loading-spinner .ball3 { opacity: 0.37; position:absolute; left: 40px; top: 20px; width: 13px; height: 13px; background: #fff;
+ border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; }
+
+div.vjs-loading-spinner .ball4 { opacity: 0.50; position:absolute; left: 34px; top: 34px; width: 13px; height: 13px; background: #fff;
+ border-radius: 10px; -webkit-border-radius: 10px; -moz-border-radius: 15px; border: 1px solid #ccc; }
+
+div.vjs-loading-spinner .ball5 { opacity: 0.62; position:absolute; left: 20px; top: 40px; width: 13px; height: 13px; background: #fff;
+ border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; }
+
+div.vjs-loading-spinner .ball6 { opacity: 0.75; position:absolute; left: 6px; top: 34px; width: 13px; height: 13px; background: #fff;
+ border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; }
+
+div.vjs-loading-spinner .ball7 { opacity: 0.87; position:absolute; left: 0px; top: 20px; width: 13px; height: 13px; background: #fff;
+ border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; }
+
+div.vjs-loading-spinner .ball8 { opacity: 1.00; position:absolute; left: 6px; top: 6px; width: 13px; height: 13px; background: #fff;
+ border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; }
diff --git a/mediagoblin/static/extlib/leaflet b/mediagoblin/static/extlib/leaflet
new file mode 120000
index 00000000..b47e2b1b
--- /dev/null
+++ b/mediagoblin/static/extlib/leaflet
@@ -0,0 +1 @@
+../../../extlib/leaflet/dist/ \ No newline at end of file
diff --git a/mediagoblin/static/extlib/pdf.js b/mediagoblin/static/extlib/pdf.js
new file mode 120000
index 00000000..f829660a
--- /dev/null
+++ b/mediagoblin/static/extlib/pdf.js
@@ -0,0 +1 @@
+../../../extlib/pdf.js \ No newline at end of file
diff --git a/mediagoblin/static/extlib/video-js b/mediagoblin/static/extlib/video-js
new file mode 120000
index 00000000..65652d6e
--- /dev/null
+++ b/mediagoblin/static/extlib/video-js
@@ -0,0 +1 @@
+../../../extlib/video-js/ \ No newline at end of file
diff --git a/mediagoblin/static/fonts/Inconsolata.otf b/mediagoblin/static/fonts/Inconsolata.otf
new file mode 120000
index 00000000..777be657
--- /dev/null
+++ b/mediagoblin/static/fonts/Inconsolata.otf
@@ -0,0 +1 @@
+../../../extlib/inconsolata/Inconsolata.otf \ No newline at end of file
diff --git a/mediagoblin/static/fonts/Lato-Bold.ttf b/mediagoblin/static/fonts/Lato-Bold.ttf
new file mode 120000
index 00000000..8b747690
--- /dev/null
+++ b/mediagoblin/static/fonts/Lato-Bold.ttf
@@ -0,0 +1 @@
+../../../extlib/lato/Lato-Bold.ttf \ No newline at end of file
diff --git a/mediagoblin/static/fonts/Lato-BoldItalic.ttf b/mediagoblin/static/fonts/Lato-BoldItalic.ttf
new file mode 120000
index 00000000..20886f02
--- /dev/null
+++ b/mediagoblin/static/fonts/Lato-BoldItalic.ttf
@@ -0,0 +1 @@
+../../../extlib/lato/Lato-BoldItalic.ttf \ No newline at end of file
diff --git a/mediagoblin/static/fonts/Lato-Italic.ttf b/mediagoblin/static/fonts/Lato-Italic.ttf
new file mode 120000
index 00000000..3e4ee80c
--- /dev/null
+++ b/mediagoblin/static/fonts/Lato-Italic.ttf
@@ -0,0 +1 @@
+../../../extlib/lato/Lato-Italic.ttf \ No newline at end of file
diff --git a/mediagoblin/static/fonts/Lato-Regular.ttf b/mediagoblin/static/fonts/Lato-Regular.ttf
new file mode 120000
index 00000000..ff8e9d2c
--- /dev/null
+++ b/mediagoblin/static/fonts/Lato-Regular.ttf
@@ -0,0 +1 @@
+../../../extlib/lato/Lato-Regular.ttf \ No newline at end of file
diff --git a/mediagoblin/static/images/404.png b/mediagoblin/static/images/404.png
new file mode 100644
index 00000000..78d746ba
--- /dev/null
+++ b/mediagoblin/static/images/404.png
Binary files differ
diff --git a/mediagoblin/static/images/background.png b/mediagoblin/static/images/background.png
new file mode 100644
index 00000000..aa101308
--- /dev/null
+++ b/mediagoblin/static/images/background.png
Binary files differ
diff --git a/mediagoblin/static/images/empty_back.png b/mediagoblin/static/images/empty_back.png
new file mode 100644
index 00000000..3522ddd3
--- /dev/null
+++ b/mediagoblin/static/images/empty_back.png
Binary files differ
diff --git a/mediagoblin/static/images/frontpage_image.png b/mediagoblin/static/images/frontpage_image.png
new file mode 100644
index 00000000..689eb2c2
--- /dev/null
+++ b/mediagoblin/static/images/frontpage_image.png
Binary files differ
diff --git a/mediagoblin/static/images/goblin.ico b/mediagoblin/static/images/goblin.ico
new file mode 100644
index 00000000..ae5a1b12
--- /dev/null
+++ b/mediagoblin/static/images/goblin.ico
Binary files differ
diff --git a/mediagoblin/static/images/goblin.png b/mediagoblin/static/images/goblin.png
new file mode 100644
index 00000000..672ed61a
--- /dev/null
+++ b/mediagoblin/static/images/goblin.png
Binary files differ
diff --git a/mediagoblin/static/images/icon_comment.png b/mediagoblin/static/images/icon_comment.png
new file mode 100644
index 00000000..76860a92
--- /dev/null
+++ b/mediagoblin/static/images/icon_comment.png
Binary files differ
diff --git a/mediagoblin/static/images/icon_feed.png b/mediagoblin/static/images/icon_feed.png
new file mode 100644
index 00000000..81889473
--- /dev/null
+++ b/mediagoblin/static/images/icon_feed.png
Binary files differ
diff --git a/mediagoblin/static/images/logo.png b/mediagoblin/static/images/logo.png
new file mode 100644
index 00000000..b40e58fb
--- /dev/null
+++ b/mediagoblin/static/images/logo.png
Binary files differ
diff --git a/mediagoblin/static/images/logo.svg b/mediagoblin/static/images/logo.svg
new file mode 100644
index 00000000..ad750c97
--- /dev/null
+++ b/mediagoblin/static/images/logo.svg
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="554"
+ height="101.99998"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.1 r9760"
+ sodipodi:docname="logo.svg"
+ inkscape:export-filename="/home/jef/mediagoblin/user_dev/themes/airy/assets/images/logo.png"
+ inkscape:export-xdpi="17.889999"
+ inkscape:export-ydpi="17.889999">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="100.13053"
+ inkscape:cy="65.605728"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ borderlayer="true"
+ inkscape:showpageshadow="false"
+ inkscape:window-width="1680"
+ inkscape:window-height="992"
+ inkscape:window-x="0"
+ inkscape:window-y="26"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-intersection-paths="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-page="true"
+ showguides="false"
+ inkscape:guide-bbox="true"
+ inkscape:snap-global="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ showborder="false">
+ <sodipodi:guide
+ orientation="1,0"
+ position="1062.0155,252.54874"
+ id="guide3002" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4021"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="62.322892,118.88571"
+ id="guide3814" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="154.25003,65.249969"
+ id="guide3816" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="main"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(865.93433,-886.66071)">
+ <g
+ id="g3010"
+ transform="matrix(0.68692139,0,0,0.52224846,-26.609327,197.42947)" />
+ <g
+ id="g3951"
+ transform="matrix(0.75599155,0,0,0.75599155,-269.59547,339.3242)" />
+ <g
+ transform="matrix(0.75599155,0,0,0.75599155,5.8142558,339.3242)"
+ id="g3969" />
+ <path
+ id="path3051"
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#797979;fill-opacity:1;stroke:none;stroke-width:11;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
+ d="m -498.47812,909.8741 c -14.29997,-0.25966 -24.30551,9.34241 -26.50888,22.91161 -2.4313,14.97291 6.8419,28.27685 20.6875,30.5 13.84559,2.22316 27.00531,-7.50109 29.46875,-22.46875 2.41774,-14.68996 -7.14681,-30.64325 -23.64737,-30.94286 z m -1.10263,8.03661 c 11.82552,0.0748 16.8356,12.60864 15.5625,21.78125 -1.4685,10.58046 -9.88714,17.25696 -18.65625,15.6875 -8.7691,-1.56946 -14.66487,-10.77344 -13.125,-21.34375 1.3955,-9.57934 8.39284,-16.17451 16.21875,-16.125 z m 130.09735,-24.04343 c 0,3.62036 -2.93488,6.55525 -6.55524,6.55525 -3.62036,0 -6.55524,-2.93489 -6.55524,-6.55525 0,-3.62036 2.93488,-6.55524 6.55524,-6.55524 3.62036,0 6.55524,2.93488 6.55524,6.55524 z m -11.15801,16.87173 0,51.92717 9.20553,0 0,-51.92717 z m -280.41892,6e-5 0,51.92717 9.20553,0 0,-51.92717 z m 11.15802,-16.87173 c 0,3.62036 -2.93488,6.55525 -6.55524,6.55525 -3.62037,0 -6.55525,-2.93489 -6.55525,-6.55525 0,-3.62036 2.93488,-6.55524 6.55525,-6.55524 3.62036,0 6.55524,2.93488 6.55524,6.55524 z m 91.82766,15.94962 c -17.68301,0 -26.96853,15.66588 -26.96875,26.8125 -3.2e-4,16.07708 10.57093,26.13091 23.6875,26.875 4.79063,0.27178 11.67595,-0.19435 17.96875,-6.34375 0.25,15.44904 -3.39617,22.87085 -13.875,23.25 -7.92125,0.28661 -13.39214,-3.93545 -15.75595,-10.70298 l -7.46875,0 c 1.52404,7.39528 6.81992,18.97887 23.75595,18.64048 16.96269,-0.33892 22.5625,-13.84938 22.5625,-30.6875 l 0,-29.5625 c -0.0225,-6.25597 0.90001,-12.00727 2.71875,-17.34375 l -7.4375,0 c -0.95827,2.12785 -1.63064,4.94081 -2.27681,7.63925 -3.53047,-5.63263 -9.91399,-8.57675 -16.91069,-8.57675 z m 15.09375,27.75 c 0,9.35634 -7.1389,18.15625 -17.21875,18.15625 -10.36378,0 -15.59375,-9.243 -15.59375,-18.28125 0,-7.95083 4.08876,-18.88021 16.15625,-19.65625 7.84943,-0.50479 16.39174,7.25265 16.65625,19.78125 z m -143.1507,-50.84375 0,29.03125 c -3.26199,-4.21342 -10.81819,-6.62398 -16.34375,-6.3125 -17.88151,1.00802 -25.3123,17.2168 -25.3125,27.25 -3.2e-4,16.07708 10.47719,26.7559 23.59375,27.5 5.19493,0.29471 13.81969,-1.57948 19.625,-9.1875 0.60295,2.26134 1.86305,5.37114 3.15625,7.65625 l 7.4375,0 c -2.12044,-5.15404 -2.9375,-10.37795 -2.9375,-18.53125 l 0,-57.40625 z m -16.34375,30.875 c 12.02937,-0.0869 16.24638,9.43483 16.65625,19.59375 0,11.03464 -8.39635,19.0625 -17.21875,19.0625 -9.45765,0 -15.76506,-9.58978 -15.53125,-18.625 0.20843,-8.0541 4.61687,-19.94837 16.09375,-20.03125 z m -54.5723,-7.71875 c -14.69916,0 -25.74033,13.03618 -25.75,27.4375 -0.01,14.78695 10.244,26.125 23.65625,26.125 13.17686,0 17.98713,-4.04194 21.40625,-7.1875 l -4.3125,-5.0625 c -3.0707,2.07296 -7.16015,4.5625 -15.96875,4.5625 -7.45652,0 -14.61337,-5.22342 -15.5625,-14.375 12.17349,2.42928 34.34076,3.97324 38.34375,-8.3125 4.15154,-12.74162 -9.23233,-23.1875 -21.8125,-23.1875 z m -0.1875,7.875 c 7.44103,-0.28295 15.86681,7.08099 13.34375,12.5625 -1.07884,2.34384 -4.36121,3.84759 -11.0625,4.1875 -4.72973,0.2399 -11.67545,0.0602 -18.53125,-1.53125 1.38994,-7.13675 6.98538,-14.86646 16.25,-15.21875 z m 138.94915,-7.84375 c -6.22358,0 -12.35769,2.10385 -17.4375,5.78125 l 3.75,5.65625 c 4.03537,-2.17319 9.28716,-3.79268 14.34375,-3.46875 6.55828,0.42013 14.7598,2.73287 14.7598,14.233 -1.85084,-1.38482 -9.08908,-2.44485 -13.54105,-2.57675 -13.63282,-0.40393 -22.07092,5.70413 -22.86428,15.3125 -1.14325,13.84598 10.26769,18.625 20.33303,18.625 7.39807,5.2e-4 15.35814,-5.72151 17,-8.4375 0.43984,2.49906 2.01961,5.57335 3.125,7.625 l 7.46875,0 c -2.12043,-5.15404 -2.96875,-10.37795 -2.96875,-18.53125 l 0,-9.84375 c 0,-24.39387 -18.81089,-24.375 -23.96875,-24.375 z m 0.78125,27.4375 c 8.05656,-0.0156 14.8125,1.66674 14.8125,4.01961 0,6.72106 -7.55371,13.8524 -16.4375,14.23039 -4.54841,0.19353 -11.57496,-2.09719 -11.48928,-9.59375 0.0808,-7.06964 7.87645,-8.64609 13.11428,-8.65625 z m -223.80458,-27.53125 c -7.78959,0 -12.57316,3.37364 -14.53125,6.625 -0.75834,-1.89688 -1.49654,-3.84633 -2.375,-5.6875 l -7.4375,0 c 2.20887,5.93379 2.73712,12.19296 2.71875,17.3125 l 0,34.59375 9.21875,0 0,-34.78125 c 0,-6.65558 6.44423,-10.26976 12.4375,-10.40625 6.57556,-0.14975 9.05752,3.56709 9.125,8.90625 l 0,36.28125 9.1875,0 0,-34.78125 c 0,-6.65558 6.44424,-10.26976 12.4375,-10.40625 6.57556,-0.14975 9.18251,3.56709 9.25,8.90625 l 0,36.28125 9.21875,0 0,-36.28125 c 0,-11.16352 -8.04836,-16.5625 -18.5,-16.5625 -7.68395,0 -13.41885,3.49982 -15.34375,6.53125 -3.21828,-4.38545 -8.81153,-6.53125 -15.40625,-6.53125 z m 435.58675,-23.11502 0,60.98954 c 0,4.50655 1.82444,11.36243 4.13429,14.95446 l 7.46542,0 c -1.71595,-4.95183 -2.38614,-11.95254 -2.38614,-18.52179 l 0,-57.42221 z m -59.85447,0.0213 0,57.40625 c 0,8.48573 -0.72207,13.98198 -2.5625,18.53125 l 7.46875,0 c 0.49268,-1.18272 1.50645,-4.57275 2.125,-6.375 3.2358,3.63861 9.91843,7.15293 16.6875,7.28125 17.0179,0 26.83165,-13.62855 27.09375,-25.84375 0.32424,-15.11836 -9.23098,-28.09919 -25.40625,-27.96875 -7.10965,0.0573 -12.81683,3.2018 -16.1875,7.1875 l 0,-30.21875 -9.21875,0 z m 25.125,30.96875 c 0.34959,-0.0146 0.70152,-0.008 1.0625,0 6.22899,0.13299 15.65387,5.56214 15.4375,19.65625 -0.16934,11.02718 -8.18402,18.37679 -16.84375,18.34375 -11.39711,-0.0435 -15.84382,-8.59471 -15.84375,-18.8125 0,-7.75719 5.35029,-18.73358 16.1875,-19.1875 z m 107.7562,-7.80926 c -7.78958,0 -13.83625,3.29749 -15.79433,6.54885 -0.67753,-1.63171 -1.5038,-3.80006 -2.38135,-5.68127 l -7.4404,0 c 2.20887,5.93379 2.75814,12.20005 2.73977,17.31959 l 0,34.58661 9.20554,0 0,-34.77561 c 0,-6.65558 7.70716,-9.7658 13.70041,-9.90229 6.57556,-0.14975 10.98867,3.05115 11.05615,8.39031 l 0,36.28759 9.20552,0 0,-36.28759 c 0,-11.16352 -9.83967,-16.48619 -20.29131,-16.48619 z"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/mediagoblin/static/images/media_thumbs/image.png b/mediagoblin/static/images/media_thumbs/image.png
new file mode 100644
index 00000000..8437a298
--- /dev/null
+++ b/mediagoblin/static/images/media_thumbs/image.png
Binary files differ
diff --git a/mediagoblin/static/images/media_thumbs/video.jpg b/mediagoblin/static/images/media_thumbs/video.jpg
new file mode 100644
index 00000000..841dc796
--- /dev/null
+++ b/mediagoblin/static/images/media_thumbs/video.jpg
Binary files differ
diff --git a/mediagoblin/static/images/video-js.png b/mediagoblin/static/images/video-js.png
new file mode 100644
index 00000000..58cd813d
--- /dev/null
+++ b/mediagoblin/static/images/video-js.png
Binary files differ
diff --git a/mediagoblin/static/js/audio.js b/mediagoblin/static/js/audio.js
new file mode 100644
index 00000000..50d58cd9
--- /dev/null
+++ b/mediagoblin/static/js/audio.js
@@ -0,0 +1,229 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var audioPlayer = new Object();
+
+(function (audioPlayer) {
+ audioPlayer.init = function (audioElement) {
+ audioPlayer.audioElement = audioElement;
+
+ console.log(audioElement);
+
+ attachEvents();
+
+ $(audioElement).hide();
+ };
+
+ function attachEvents () {
+ audioPlayer.audioElement.addEventListener(
+ 'durationchange', audioPlayer.durationChange, true);
+ audioPlayer.audioElement.addEventListener(
+ 'timeupdate', audioPlayer.timeUpdate, true);
+ audioPlayer.audioElement.addEventListener(
+ 'progress', audioPlayer.onProgress, true);
+ audioPlayer.audioElement.addEventListener(
+ 'ended', audioPlayer.onEnded, true);
+
+ $(document).ready( function () {
+ $('.audio-spectrogram').delegate(
+ '.seekbar', 'click', audioPlayer.onSeek);
+ $('.audio-spectrogram').delegate(
+ '.audio-control-play-pause', 'click', audioPlayer.playPause);
+ $('.audio-spectrogram').delegate(
+ '.audio-volume', 'change', audioPlayer.onVolumeChange);
+ $('.audio-media').delegate(
+ '.audio-spectrogram', 'attachedControls',
+ audioPlayer.onControlsAttached);
+ });
+ }
+
+ audioPlayer.onVolumeChange = function(e) {
+ console.log('volume change', e);
+ audioPlayer.audioElement.volume = e.target.value;
+ }
+
+ audioPlayer.onControlsAttached = function(e) {
+ console.log('Controls attached', e);
+ $('.audio-spectrogram .audio-volume').val(
+ Math.round(audioPlayer.audioElement.volume, 2));
+ }
+
+ audioPlayer.onProgress = function(e) {
+ /**
+ * Handler for file download progress
+ */
+ console.log(e);
+
+ var buffered = audioPlayer.audioElement.buffered;
+
+ ranges = new Array();
+
+ var indicators = $('.audio-spectrogram .buffered-indicators div');
+
+ for (var i = 0; i < buffered.length; i++) {
+ if (!(i in indicators)) {
+ $('<div style="display: none;"></div>')
+ .appendTo($('.audio-spectrogram .buffered-indicators'))
+ .fadeIn(500);
+ indicators = $('.audio-spectrogram .buffered-indicators div');
+ }
+ var posStart = ((buffered.start(i) / audioPlayer.audioElement.duration)
+ * audioPlayer.imageElement.width());
+ var posStop = ((buffered.end(i) / audioPlayer.audioElement.duration)
+ * audioPlayer.imageElement.width());
+ console.log('indicators', indicators);
+
+ var indicator = $(indicators[i]);
+
+ indicator.css('left', posStart);
+ indicator.css('width', posStop - posStart);
+ }
+
+ /*
+ * Clean up unused indicators
+ */
+ if (indicators.length > buffered.length) {
+ for (var i = buffered.length; i < indicators.length; i++) {
+ $(indicators[i]).fadeOut(500, function () {
+ this.remove();
+ });
+ }
+ }
+ };
+
+ audioPlayer.onSeek = function (e) {
+ /**
+ * Callback handler for seek event, which is a .click() event on the
+ * .seekbar element
+ */
+ console.log('onSeek', e);
+
+ var im = audioPlayer.imageElement;
+ var pos = (e.offsetX || e.originalEvent.layerX) / im.width();
+
+ audioPlayer.audioElement.currentTime = pos * audioPlayer.audioElement.duration;
+ audioPlayer.audioElement.play();
+ audioPlayer.setState(audioPlayer.PLAYING);
+ };
+
+ audioPlayer.onEnded = function (e) {
+ audioPlayer.setState(audioPlayer.PAUSED);
+ }
+
+ audioPlayer.playPause = function (e) {
+ console.log('playPause', e);
+ if (audioPlayer.audioElement.paused) {
+ audioPlayer.audioElement.play();
+ audioPlayer.setState(audioPlayer.PLAYING);
+ } else {
+ audioPlayer.audioElement.pause();
+ audioPlayer.setState(audioPlayer.PAUSED);
+ }
+ };
+
+ audioPlayer.NULL = null;
+ audioPlayer.PLAYING = 2;
+ audioPlayer.PAUSED = 4;
+
+ audioPlayer.state = audioPlayer.NULL;
+
+ audioPlayer.setState = function (state) {
+ if (state == audioPlayer.state) {
+ return;
+ } else {
+ audioPlayer.state = state;
+ }
+
+ switch (state) {
+ case audioPlayer.PLAYING:
+ $('.audio-spectrogram .audio-control-play-pause')
+ .removeClass('paused').addClass('playing')
+ .text('▮▮');
+ break;
+ case audioPlayer.PAUSED:
+ $('.audio-spectrogram .audio-control-play-pause')
+ .removeClass('playing').addClass('paused')
+ .text('▶');
+ break;
+ }
+ };
+
+ audioPlayer.durationChange = function () {
+ // ???
+ };
+
+ audioPlayer.timeUpdate = function () {
+ /**
+ * Callback handler for the timeupdate event, responsible for
+ * updating the playhead
+ */
+ var currentTime = audioPlayer.audioElement.currentTime;
+ var playhead = audioPlayer.imageElement.parent().find('.playhead');
+ playhead.css('width', (currentTime / audioPlayer.audioElement.duration)
+ * audioPlayer.imageElement.width());
+ var time = formatTime(currentTime);
+ var duration = formatTime(audioPlayer.audioElement.duration);
+ audioPlayer.imageElement.parent()
+ .find('.audio-currentTime')
+ .text(time + '/' + duration);
+ };
+
+ function formatTime(seconds) {
+ /**
+ * Format a time duration in (hh:)?mm:ss manner
+ */
+ var h = Math.floor(seconds / (60 * 60));
+ var m = Math.floor((seconds - h * 60 * 60) / 60);
+ var s = Math.round(seconds - h * 60 * 60 - m * 60);
+ return '' + (h ? (h < 10 ? '0' + h : h) + ':' : '') + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
+ }
+
+ audioPlayer.formatTime = formatTime;
+
+ audioPlayer.attachToImage = function (imageElement) {
+ /**
+ * Attach the player to an image element
+ */
+ console.log(imageElement);
+
+ var im = $(imageElement);
+
+ audioPlayer.imageElement = im;
+
+ $('<div class="playhead"></div>').appendTo(im.parent());
+ $('<div class="buffered-indicators"></div>').appendTo(im.parent());
+ $('<div class="seekbar"></div>').appendTo(im.parent());
+ $('<div class="audio-control-play-pause paused">▶</div>').appendTo(im.parent());
+ $('<div class="audio-currentTime">00:00</div>').appendTo(im.parent());
+ $('<input type="range" class="audio-volume"'
+ +'value="1" min="0" max="1" step="0.001" />').appendTo(im.parent());
+ $('.audio-spectrogram').trigger('attachedControls');
+ };
+})(audioPlayer);
+
+$(document).ready(function () {
+ if (!$('.audio-media').length) {
+ return;
+ }
+
+ console.log('Initializing audio player');
+
+ audioElements = $('.audio-media .audio-player');
+ audioPlayer.init(audioElements[0]);
+ audioPlayer.attachToImage($('.audio-spectrogram img')[0]);
+});
diff --git a/mediagoblin/static/js/autofilledin_password.js b/mediagoblin/static/js/autofilledin_password.js
new file mode 100644
index 00000000..45e867fe
--- /dev/null
+++ b/mediagoblin/static/js/autofilledin_password.js
@@ -0,0 +1,25 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$(document).ready(function(){
+ $('#forgot_password').click(function(event){
+ event.preventDefault();
+ window.location.pathname = $(this).attr('href') + '?username=' +
+ $('#username').val();
+ });
+});
diff --git a/mediagoblin/static/js/collection_form_show.js b/mediagoblin/static/js/collection_form_show.js
new file mode 100644
index 00000000..03a4906b
--- /dev/null
+++ b/mediagoblin/static/js/collection_form_show.js
@@ -0,0 +1,26 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$(document).ready(function(){
+ $('#new_collection').hide();
+ $('#button_addcollection').click(function(){
+ $('#new_collection').slideToggle('fast', function(){
+ $('#collection_title').focus();
+ });
+ });
+});
diff --git a/mediagoblin/static/js/comment_show.js b/mediagoblin/static/js/comment_show.js
new file mode 100644
index 00000000..c5ccee66
--- /dev/null
+++ b/mediagoblin/static/js/comment_show.js
@@ -0,0 +1,27 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$(document).ready(function(){
+ $('#form_comment').hide();
+ $('#button_addcomment').click(function(){
+ $(this).fadeOut('fast');
+ $('#form_comment').slideDown(function(){
+ $('#comment_content').focus();
+ });
+ });
+});
diff --git a/mediagoblin/static/js/extlib/html5slider.js b/mediagoblin/static/js/extlib/html5slider.js
new file mode 120000
index 00000000..feae2cb8
--- /dev/null
+++ b/mediagoblin/static/js/extlib/html5slider.js
@@ -0,0 +1 @@
+../../../../extlib/html5slider/html5slider.js \ No newline at end of file
diff --git a/mediagoblin/static/js/extlib/jquery.js b/mediagoblin/static/js/extlib/jquery.js
new file mode 120000
index 00000000..d78f5cc3
--- /dev/null
+++ b/mediagoblin/static/js/extlib/jquery.js
@@ -0,0 +1 @@
+../../../../extlib/jquery/jquery.js \ No newline at end of file
diff --git a/mediagoblin/static/js/extlib/leaflet b/mediagoblin/static/js/extlib/leaflet
new file mode 120000
index 00000000..2fc302d7
--- /dev/null
+++ b/mediagoblin/static/js/extlib/leaflet
@@ -0,0 +1 @@
+../../../../extlib/leaflet/dist/ \ No newline at end of file
diff --git a/mediagoblin/static/js/extlib/thingiview.js b/mediagoblin/static/js/extlib/thingiview.js
new file mode 120000
index 00000000..b7c842ba
--- /dev/null
+++ b/mediagoblin/static/js/extlib/thingiview.js
@@ -0,0 +1 @@
+../../../../extlib/thingiview.js/ \ No newline at end of file
diff --git a/mediagoblin/static/js/geolocation-map.js b/mediagoblin/static/js/geolocation-map.js
new file mode 100644
index 00000000..26d94c5d
--- /dev/null
+++ b/mediagoblin/static/js/geolocation-map.js
@@ -0,0 +1,47 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$(document).ready(function () {
+ if (!$('#tile-map').length) {
+ return;
+ }
+ console.log('Initializing map');
+
+ var longitude = Number(
+ $('#tile-map #gps-longitude').val());
+ var latitude = Number(
+ $('#tile-map #gps-latitude').val());
+
+ // Get a new map instance attached and element with id="tile-map"
+ var map = new L.Map('tile-map');
+
+ var mqtileUrl = 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg';
+ var mqtileAttrib = '<a id="osm_license_link">see map license</a>';
+ var mqtile = new L.TileLayer(
+ mqtileUrl,
+ {maxZoom: 18,
+ attribution: mqtileAttrib,
+ subdomains: '1234'});
+
+ map.attributionControl.setPrefix('');
+ var location = new L.LatLng(latitude, longitude);
+ map.setView(location, 13).addLayer(mqtile);
+
+ var marker = new L.Marker(location);
+ map.addLayer(marker);
+});
diff --git a/mediagoblin/static/js/header_dropdown.js b/mediagoblin/static/js/header_dropdown.js
new file mode 100644
index 00000000..1b2fb00f
--- /dev/null
+++ b/mediagoblin/static/js/header_dropdown.js
@@ -0,0 +1,27 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$(document).ready(function(){
+ $(".header_dropdown").hide();
+ $(".header_dropdown_up").hide();
+ $(".header_dropdown_down,.header_dropdown_up").click(function() {
+ $(".header_dropdown_down").toggle();
+ $(".header_dropdown_up").toggle();
+ $(".header_dropdown").slideToggle();
+ });
+});
diff --git a/mediagoblin/static/js/keyboard_navigation.js b/mediagoblin/static/js/keyboard_navigation.js
new file mode 100644
index 00000000..7401e4d8
--- /dev/null
+++ b/mediagoblin/static/js/keyboard_navigation.js
@@ -0,0 +1,41 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* It must be wrapped into a function and you also cannot use
+ * $(':not(textarea, input)') because of some reason. */
+
+$(document).ready(function(){
+ $('textarea, input').keydown(function(event){
+ event.stopPropagation();
+ });
+});
+
+$(document).keydown(function(event){
+ switch(event.which){
+ case 37:
+ if($('a.navigation_left').length) {
+ window.location = $('a.navigation_left').attr('href');
+ }
+ break;
+ case 39:
+ if($('a.navigation_right').length) {
+ window.location = $('a.navigation_right').attr('href');
+ }
+ break;
+ }
+});
diff --git a/mediagoblin/static/js/show_password.js b/mediagoblin/static/js/show_password.js
new file mode 100644
index 00000000..b3fbc862
--- /dev/null
+++ b/mediagoblin/static/js/show_password.js
@@ -0,0 +1,38 @@
+/**
+ * GNU MediaGoblin -- federated, autonomous media hosting
+ * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$(document).ready(function(){
+ //Create a duplicate password field. We could change the input type dynamically, but this angers the IE gods (not just IE6).
+ $("#password").after('<input type="text" value="" name="password_clear" id="password_clear" /><label><input type="checkbox" id="password_boolean" />Show password</label>');
+ $('#password_clear').hide();
+ $('#password_boolean').click(function(){
+ if($('#password_boolean').prop("checked")) {
+ $('#password_clear').val($('#password').val());
+ $('#password').hide();
+ $('#password_clear').show();
+ } else {
+ $('#password').val($('#password_clear').val());
+ $('#password_clear').hide();
+ $('#password').show();
+ };
+ });
+ $('#password,#password_clear').keyup(function(){
+ $('#password').val($(this).val());
+ $('#password_clear').val($(this).val());
+ });
+});
diff --git a/mediagoblin/storage/__init__.py b/mediagoblin/storage/__init__.py
new file mode 100644
index 00000000..bbe134a7
--- /dev/null
+++ b/mediagoblin/storage/__init__.py
@@ -0,0 +1,264 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import shutil
+import uuid
+
+from werkzeug.utils import secure_filename
+
+from mediagoblin.tools import common
+
+########
+# Errors
+########
+
+
+class Error(Exception):
+ pass
+
+
+class InvalidFilepath(Error):
+ pass
+
+
+class NoWebServing(Error):
+ pass
+
+
+class NotImplementedError(Error):
+ pass
+
+
+###############################################
+# Storage interface & basic file implementation
+###############################################
+
+class StorageInterface(object):
+ """
+ Interface for the storage API.
+
+ This interface doesn't actually provide behavior, but it defines
+ what kind of storage patterns subclasses should provide.
+
+ It is important to note that the storage API idea of a "filepath"
+ is actually like ['dir1', 'dir2', 'file.jpg'], so keep that in
+ mind while reading method documentation.
+
+ You should set up your __init__ method with whatever keyword
+ arguments are appropriate to your storage system, but you should
+ also passively accept all extraneous keyword arguments like:
+
+ def __init__(self, **kwargs):
+ pass
+
+ See BasicFileStorage as a simple implementation of the
+ StorageInterface.
+ """
+
+ # Whether this file store is on the local filesystem.
+ local_storage = False
+
+ def __raise_not_implemented(self):
+ """
+ Raise a warning about some component not implemented by a
+ subclass of this interface.
+ """
+ raise NotImplementedError(
+ "This feature not implemented in this storage API implementation.")
+
+ def file_exists(self, filepath):
+ """
+ Return a boolean asserting whether or not file at filepath
+ exists in our storage system.
+
+ Returns:
+ True / False depending on whether file exists or not.
+ """
+ # Subclasses should override this method.
+ self.__raise_not_implemented()
+
+ def get_file(self, filepath, mode='r'):
+ """
+ Return a file-like object for reading/writing from this filepath.
+
+ Should create directories, buckets, whatever, as necessary.
+ """
+ # Subclasses should override this method.
+ self.__raise_not_implemented()
+
+ def delete_file(self, filepath):
+ """
+ Delete or dereference the file (not directory) at filepath.
+ """
+ # Subclasses should override this method.
+ self.__raise_not_implemented()
+
+ def delete_dir(self, dirpath, recursive=False):
+ """Delete the directory at dirpath
+
+ :param recursive: Usually, a directory must not contain any
+ files for the delete to succeed. If True, containing files
+ and subdirectories within dirpath will be recursively
+ deleted.
+
+ :returns: True in case of success, False otherwise.
+ """
+ # Subclasses should override this method.
+ self.__raise_not_implemented()
+
+ def file_url(self, filepath):
+ """
+ Get the URL for this file. This assumes our storage has been
+ mounted with some kind of URL which makes this possible.
+ """
+ # Subclasses should override this method.
+ self.__raise_not_implemented()
+
+ def get_unique_filepath(self, filepath):
+ """
+ If a filename at filepath already exists, generate a new name.
+
+ Eg, if the filename doesn't exist:
+ >>> storage_handler.get_unique_filename(['dir1', 'dir2', 'fname.jpg'])
+ [u'dir1', u'dir2', u'fname.jpg']
+
+ But if a file does exist, let's get one back with at uuid tacked on:
+ >>> storage_handler.get_unique_filename(['dir1', 'dir2', 'fname.jpg'])
+ [u'dir1', u'dir2', u'd02c3571-dd62-4479-9d62-9e3012dada29-fname.jpg']
+ """
+ # Make sure we have a clean filepath to start with, since
+ # we'll be possibly tacking on stuff to the filename.
+ filepath = clean_listy_filepath(filepath)
+
+ if self.file_exists(filepath):
+ return filepath[:-1] + ["%s-%s" % (uuid.uuid4(), filepath[-1])]
+ else:
+ return filepath
+
+ def get_local_path(self, filepath):
+ """
+ If this is a local_storage implementation, give us a link to
+ the local filesystem reference to this file.
+
+ >>> storage_handler.get_local_path(['foo', 'bar', 'baz.jpg'])
+ u'/path/to/mounting/foo/bar/baz.jpg'
+ """
+ # Subclasses should override this method, if applicable.
+ self.__raise_not_implemented()
+
+ def copy_locally(self, filepath, dest_path):
+ """
+ Copy this file locally.
+
+ A basic working method for this is provided that should
+ function both for local_storage systems and remote storge
+ systems, but if more efficient systems for copying locally
+ apply to your system, override this method with something more
+ appropriate.
+ """
+ if self.local_storage:
+ # Note: this will copy in small chunks
+ shutil.copy(self.get_local_path(filepath), dest_path)
+ else:
+ with self.get_file(filepath, 'rb') as source_file:
+ with file(dest_path, 'wb') as dest_file:
+ # Copy from remote storage in 4M chunks
+ shutil.copyfileobj(source_file, dest_file, length=4*1048576)
+
+ def copy_local_to_storage(self, filename, filepath):
+ """
+ Copy this file from locally to the storage system.
+
+ This is kind of the opposite of copy_locally. It's likely you
+ could override this method with something more appropriate to
+ your storage system.
+ """
+ with self.get_file(filepath, 'wb') as dest_file:
+ with file(filename, 'rb') as source_file:
+ # Copy to storage system in 4M chunks
+ shutil.copyfileobj(source_file, dest_file, length=4*1048576)
+
+
+###########
+# Utilities
+###########
+
+def clean_listy_filepath(listy_filepath):
+ """
+ Take a listy filepath (like ['dir1', 'dir2', 'filename.jpg']) and
+ clean out any nastiness from it.
+
+
+ >>> clean_listy_filepath([u'/dir1/', u'foo/../nasty', u'linooks.jpg'])
+ [u'dir1', u'foo_.._nasty', u'linooks.jpg']
+
+ Args:
+ - listy_filepath: a list of filepath components, mediagoblin
+ storage API style.
+
+ Returns:
+ A cleaned list of unicode objects.
+ """
+ cleaned_filepath = [
+ unicode(secure_filename(filepath))
+ for filepath in listy_filepath]
+
+ if u'' in cleaned_filepath:
+ raise InvalidFilepath(
+ "A filename component could not be resolved into a usable name.")
+
+ return cleaned_filepath
+
+
+def storage_system_from_config(config_section):
+ """
+ Utility for setting up a storage system from a config section.
+
+ Note that a special argument may be passed in to
+ the config_section which is "storage_class" which will provide an
+ import path to a storage system. This defaults to
+ "mediagoblin.storage:BasicFileStorage" if otherwise undefined.
+
+ Arguments:
+ - config_section: dictionary of config parameters
+
+ Returns:
+ An instantiated storage system.
+
+ Example:
+ storage_system_from_config(
+ {'base_url': '/media/',
+ 'base_dir': '/var/whatever/media/'})
+
+ Will return:
+ BasicFileStorage(
+ base_url='/media/',
+ base_dir='/var/whatever/media')
+ """
+ # This construct is needed, because dict(config) does
+ # not replace the variables in the config items.
+ config_params = dict(config_section.iteritems())
+
+ if 'storage_class' in config_params:
+ storage_class = config_params['storage_class']
+ config_params.pop('storage_class')
+ else:
+ storage_class = 'mediagoblin.storage.filestorage:BasicFileStorage'
+
+ storage_class = common.import_component(storage_class)
+ return storage_class(**config_params)
+
+import filestorage
diff --git a/mediagoblin/storage/cloudfiles.py b/mediagoblin/storage/cloudfiles.py
new file mode 100644
index 00000000..250f06d4
--- /dev/null
+++ b/mediagoblin/storage/cloudfiles.py
@@ -0,0 +1,246 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+'''
+Make it so that ``import cloudfiles`` does not pick THIS file, but the
+python-cloudfiles one.
+
+http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports
+'''
+from __future__ import absolute_import
+
+from mediagoblin.storage import StorageInterface, clean_listy_filepath
+
+import cloudfiles
+import mimetypes
+import logging
+
+_log = logging.getLogger(__name__)
+
+
+class CloudFilesStorage(StorageInterface):
+ '''
+ OpenStack/Rackspace Cloud's Swift/CloudFiles support
+ '''
+
+ local_storage = False
+
+ def __init__(self, **kwargs):
+ self.param_container = kwargs.get('cloudfiles_container')
+ self.param_user = kwargs.get('cloudfiles_user')
+ self.param_api_key = kwargs.get('cloudfiles_api_key')
+ self.param_host = kwargs.get('cloudfiles_host')
+ self.param_use_servicenet = kwargs.get('cloudfiles_use_servicenet')
+
+ # the Mime Type webm doesn't exists, let's add it
+ mimetypes.add_type("video/webm", "webm")
+
+ if not self.param_host:
+ _log.info('No CloudFiles host URL specified, '
+ 'defaulting to Rackspace US')
+
+ self.connection = cloudfiles.get_connection(
+ username=self.param_user,
+ api_key=self.param_api_key,
+ servicenet=True if self.param_use_servicenet == 'true' or \
+ self.param_use_servicenet == True else False)
+
+ _log.debug('Connected to {0} (auth: {1})'.format(
+ self.connection.connection.host,
+ self.connection.auth.host))
+
+ if not self.param_container == \
+ self.connection.get_container(self.param_container):
+ self.container = self.connection.create_container(
+ self.param_container)
+ self.container.make_public(
+ ttl=60 * 60 * 2)
+ else:
+ self.container = self.connection.get_container(
+ self.param_container)
+
+ _log.debug('Container: {0}'.format(
+ self.container.name))
+
+ self.container_uri = self.container.public_ssl_uri()
+
+ def _resolve_filepath(self, filepath):
+ return '/'.join(
+ clean_listy_filepath(filepath))
+
+ def file_exists(self, filepath):
+ try:
+ self.container.get_object(self._resolve_filepath(filepath))
+ return True
+ except cloudfiles.errors.NoSuchObject:
+ return False
+
+ def get_file(self, filepath, *args, **kwargs):
+ """
+ - Doesn't care about the "mode" argument.
+ """
+ try:
+ obj = self.container.get_object(
+ self._resolve_filepath(filepath))
+ except cloudfiles.errors.NoSuchObject:
+ obj = self.container.create_object(
+ self._resolve_filepath(filepath))
+
+ # Detect the mimetype ourselves, since some extensions (webm)
+ # may not be universally accepted as video/webm
+ mimetype = mimetypes.guess_type(
+ filepath[-1])
+
+ if mimetype[0]:
+ # Set the mimetype on the CloudFiles object
+ obj.content_type = mimetype[0]
+ obj.metadata = {'mime-type': mimetype[0]}
+ else:
+ obj.content_type = 'application/octet-stream'
+ obj.metadata = {'mime-type': 'application/octet-stream'}
+
+ return CloudFilesStorageObjectWrapper(obj, *args, **kwargs)
+
+ def delete_file(self, filepath):
+ # TODO: Also delete unused directories if empty (safely, with
+ # checks to avoid race conditions).
+ try:
+ self.container.delete_object(
+ self._resolve_filepath(filepath))
+ except cloudfiles.container.ResponseError:
+ pass
+ finally:
+ pass
+
+ def file_url(self, filepath):
+ return '/'.join([
+ self.container_uri,
+ self._resolve_filepath(filepath)])
+
+
+ def copy_locally(self, filepath, dest_path):
+ """
+ Copy this file locally.
+
+ A basic working method for this is provided that should
+ function both for local_storage systems and remote storge
+ systems, but if more efficient systems for copying locally
+ apply to your system, override this method with something more
+ appropriate.
+ """
+ # Override this method, using the "stream" iterator for efficient streaming
+ with self.get_file(filepath, 'rb') as source_file:
+ with file(dest_path, 'wb') as dest_file:
+ for data in source_file:
+ dest_file.write(data)
+
+ def copy_local_to_storage(self, filename, filepath):
+ """
+ Copy this file from locally to the storage system.
+
+ This is kind of the opposite of copy_locally. It's likely you
+ could override this method with something more appropriate to
+ your storage system.
+ """
+ # It seems that (our implementation of) cloudfiles.write() takes
+ # all existing data and appends write(data) to it, sending the
+ # full monty over the wire everytime. This would of course
+ # absolutely kill chunked writes with some O(1^n) performance
+ # and bandwidth usage. So, override this method and use the
+ # Cloudfile's "send" interface instead.
+ # TODO: Fixing write() still seems worthwhile though.
+ _log.debug('Sending {0} to cloudfiles...'.format(filepath))
+ with self.get_file(filepath, 'wb') as dest_file:
+ with file(filename, 'rb') as source_file:
+ # Copy to storage system in 4096 byte chunks
+ dest_file.send(source_file)
+
+class CloudFilesStorageObjectWrapper():
+ """
+ Wrapper for python-cloudfiles's cloudfiles.storage_object.Object
+ used to circumvent the mystic `medium.jpg` corruption issue, where
+ we had both python-cloudfiles and PIL doing buffering on both
+ ends and causing breakage.
+
+ This wrapper currently meets mediagoblin's needs for a public_store
+ file-like object.
+ """
+ def __init__(self, storage_object, *args, **kwargs):
+ self.storage_object = storage_object
+
+ def read(self, *args, **kwargs):
+ _log.debug('Reading {0}'.format(
+ self.storage_object.name))
+ return self.storage_object.read(*args, **kwargs)
+
+ def write(self, data, *args, **kwargs):
+ """
+ write data to the cloudfiles storage object
+
+ The original motivation for this wrapper is to ensure
+ that buffered writing to a cloudfiles storage object does not overwrite
+ any preexisting data.
+
+ Currently this method does not support any write modes except "append".
+ However if we should need it it would be easy implement.
+ """
+ _log.warn(
+ '{0}.write() has bad performance! Use .send instead for now'\
+ .format(self.__class__.__name__))
+
+ if self.storage_object.size and type(data) == str:
+ _log.debug('{0} is > 0 in size, appending data'.format(
+ self.storage_object.name))
+ data = self.read() + data
+
+ _log.debug('Writing {0}'.format(
+ self.storage_object.name))
+ self.storage_object.write(data, *args, **kwargs)
+
+ def send(self, *args, **kw):
+ self.storage_object.send(*args, **kw)
+
+ def close(self):
+ """
+ Not sure we need anything here.
+ """
+ pass
+
+ def __enter__(self):
+ """
+ Context Manager API implementation
+ http://docs.python.org/library/stdtypes.html#context-manager-types
+ """
+ return self
+
+ def __exit__(self, *exc_info):
+ """
+ Context Manger API implementation
+ see self.__enter__()
+ """
+ self.close()
+
+
+ def __iter__(self, **kwargs):
+ """Make CloudFile an iterator, yielding 8192 bytes by default
+
+ This returns a generator object that can be used to getting the
+ object's content in a memory efficient way.
+
+ Warning: The HTTP response is only complete after this generator
+ has raised a StopIteration. No other methods can be called until
+ this has occurred."""
+ return self.storage_object.stream(**kwargs)
diff --git a/mediagoblin/storage/filestorage.py b/mediagoblin/storage/filestorage.py
new file mode 100644
index 00000000..3d6e0753
--- /dev/null
+++ b/mediagoblin/storage/filestorage.py
@@ -0,0 +1,113 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.storage import (
+ StorageInterface,
+ clean_listy_filepath,
+ NoWebServing)
+
+import os
+import shutil
+import urlparse
+
+
+class BasicFileStorage(StorageInterface):
+ """
+ Basic local filesystem implementation of storage API
+ """
+
+ local_storage = True
+
+ def __init__(self, base_dir, base_url=None, **kwargs):
+ """
+ Keyword arguments:
+ - base_dir: Base directory things will be served out of. MUST
+ be an absolute path.
+ - base_url: URL files will be served from
+ """
+ self.base_dir = base_dir
+ self.base_url = base_url
+
+ def _resolve_filepath(self, filepath):
+ """
+ Transform the given filepath into a local filesystem filepath.
+ """
+ return os.path.join(
+ self.base_dir, *clean_listy_filepath(filepath))
+
+ def file_exists(self, filepath):
+ return os.path.exists(self._resolve_filepath(filepath))
+
+ def get_file(self, filepath, mode='r'):
+ # Make directories if necessary
+ if len(filepath) > 1:
+ directory = self._resolve_filepath(filepath[:-1])
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+ # Grab and return the file in the mode specified
+ return open(self._resolve_filepath(filepath), mode)
+
+ def delete_file(self, filepath):
+ """Delete file at filepath
+
+ Raises OSError in case filepath is a directory."""
+ #TODO: log error
+ os.remove(self._resolve_filepath(filepath))
+
+ def delete_dir(self, dirpath, recursive=False):
+ """returns True on succes, False on failure"""
+
+ dirpath = self._resolve_filepath(dirpath)
+
+ # Shortcut the default and simple case of nonempty=F, recursive=F
+ if recursive:
+ try:
+ shutil.rmtree(dirpath)
+ except OSError as e:
+ #TODO: log something here
+ return False
+ else: # recursively delete everything
+ try:
+ os.rmdir(dirpath)
+ except OSError as e:
+ #TODO: log something here
+ return False
+ return True
+
+ def file_url(self, filepath):
+ if not self.base_url:
+ raise NoWebServing(
+ "base_url not set, cannot provide file urls")
+
+ return urlparse.urljoin(
+ self.base_url,
+ '/'.join(clean_listy_filepath(filepath)))
+
+ def get_local_path(self, filepath):
+ return self._resolve_filepath(filepath)
+
+ def copy_local_to_storage(self, filename, filepath):
+ """
+ Copy this file from locally to the storage system.
+ """
+ # Make directories if necessary
+ if len(filepath) > 1:
+ directory = self._resolve_filepath(filepath[:-1])
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+ # This uses chunked copying of 16kb buffers (Py2.7):
+ shutil.copy(filename, self.get_local_path(filepath))
diff --git a/mediagoblin/storage/mountstorage.py b/mediagoblin/storage/mountstorage.py
new file mode 100644
index 00000000..dffc619b
--- /dev/null
+++ b/mediagoblin/storage/mountstorage.py
@@ -0,0 +1,160 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.storage import StorageInterface, clean_listy_filepath
+
+
+class MountError(Exception):
+ pass
+
+
+class MountStorage(StorageInterface):
+ """
+ Experimental "Mount" virtual Storage Interface
+
+ This isn't an interface to some real storage, instead it's a
+ redirecting interface, that redirects requests to other
+ "StorageInterface"s.
+
+ For example, say you have the paths:
+
+ 1. ['user_data', 'cwebber', 'avatar.jpg']
+ 2. ['user_data', 'elrond', 'avatar.jpg']
+ 3. ['media_entries', '34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg']
+
+ You could mount media_entries under CloudFileStorage and user_data
+ under BasicFileStorage. Then 1 would be passed to
+ BasicFileStorage under the path ['cwebber', 'avatar.jpg'] and 3
+ would be passed to CloudFileStorage under
+ ['34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg'].
+
+ In other words, this is kind of like mounting /home/ and /etc/
+ under different filesystems on your operating system... but with
+ mediagoblin filestorages :)
+
+ To set this up, you currently need to call the mount() method with
+ the target path and a backend, that shall be available under that
+ target path. You have to mount things in a sensible order,
+ especially you can't mount ["a", "b"] before ["a"].
+ """
+ def __init__(self, **kwargs):
+ self.mounttab = {}
+
+ def mount(self, dirpath, backend):
+ """
+ Mount a new backend under dirpath
+ """
+ new_ent = clean_listy_filepath(dirpath)
+
+ print "Mounting:", repr(new_ent)
+ already, rem_1, table, rem_2 = self._resolve_to_backend(new_ent, True)
+ print "===", repr(already), repr(rem_1), repr(rem_2), len(table)
+
+ assert (len(rem_2) > 0) or (None not in table), \
+ "That path is already mounted"
+ assert (len(rem_2) > 0) or (len(table) == 0), \
+ "A longer path is already mounted here"
+
+ for part in rem_2:
+ table[part] = {}
+ table = table[part]
+ table[None] = backend
+
+ def _resolve_to_backend(self, filepath, extra_info=False):
+ """
+ extra_info = True is for internal use!
+
+ Normally, returns the backend and the filepath inside that backend.
+
+ With extra_info = True it returns the last directory node and the
+ remaining filepath from there in addition.
+ """
+ table = self.mounttab
+ filepath = filepath[:]
+ res_fp = None
+ while True:
+ new_be = table.get(None)
+ if (new_be is not None) or res_fp is None:
+ res_be = new_be
+ res_fp = filepath[:]
+ res_extra = (table, filepath[:])
+ # print "... New res: %r, %r, %r" % (res_be, res_fp, res_extra)
+ if len(filepath) == 0:
+ break
+ query = filepath.pop(0)
+ entry = table.get(query)
+ if entry is not None:
+ table = entry
+ res_extra = (table, filepath[:])
+ else:
+ break
+ if extra_info:
+ return (res_be, res_fp) + res_extra
+ else:
+ return (res_be, res_fp)
+
+ def resolve_to_backend(self, filepath):
+ backend, filepath = self._resolve_to_backend(filepath)
+ if backend is None:
+ raise MountError("Path not mounted")
+ return backend, filepath
+
+ def __repr__(self, table=None, indent=[]):
+ res = []
+ if table is None:
+ res.append("MountStorage<")
+ table = self.mounttab
+ v = table.get(None)
+ if v:
+ res.append(" " * len(indent) + repr(indent) + ": " + repr(v))
+ for k, v in table.iteritems():
+ if k == None:
+ continue
+ res.append(" " * len(indent) + repr(k) + ":")
+ res += self.__repr__(v, indent + [k])
+ if table is self.mounttab:
+ res.append(">")
+ return "\n".join(res)
+ else:
+ return res
+
+ def file_exists(self, filepath):
+ backend, filepath = self.resolve_to_backend(filepath)
+ return backend.file_exists(filepath)
+
+ def get_file(self, filepath, mode='r'):
+ backend, filepath = self.resolve_to_backend(filepath)
+ return backend.get_file(filepath, mode)
+
+ def delete_file(self, filepath):
+ backend, filepath = self.resolve_to_backend(filepath)
+ return backend.delete_file(filepath)
+
+ def file_url(self, filepath):
+ backend, filepath = self.resolve_to_backend(filepath)
+ return backend.file_url(filepath)
+
+ def get_local_path(self, filepath):
+ backend, filepath = self.resolve_to_backend(filepath)
+ return backend.get_local_path(filepath)
+
+ def copy_locally(self, filepath, dest_path):
+ """
+ Need to override copy_locally, because the local_storage
+ attribute is not correct.
+ """
+ backend, filepath = self.resolve_to_backend(filepath)
+ backend.copy_locally(filepath, dest_path)
diff --git a/mediagoblin/submit/__init__.py b/mediagoblin/submit/__init__.py
new file mode 100644
index 00000000..621845ba
--- /dev/null
+++ b/mediagoblin/submit/__init__.py
@@ -0,0 +1,15 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/submit/forms.py b/mediagoblin/submit/forms.py
new file mode 100644
index 00000000..e9bd93fd
--- /dev/null
+++ b/mediagoblin/submit/forms.py
@@ -0,0 +1,53 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import wtforms
+
+from mediagoblin.tools.text import tag_length_validator
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+from mediagoblin.tools.licenses import licenses_as_choices
+
+
+class SubmitStartForm(wtforms.Form):
+ file = wtforms.FileField(_('File'))
+ title = wtforms.TextField(
+ _('Title'),
+ [wtforms.validators.Length(min=0, max=500)])
+ description = wtforms.TextAreaField(
+ _('Description of this work'),
+ description=_("""You can use
+ <a href="http://daringfireball.net/projects/markdown/basics">
+ Markdown</a> for formatting."""))
+ tags = wtforms.TextField(
+ _('Tags'),
+ [tag_length_validator],
+ description=_(
+ "Separate tags by commas."))
+ license = wtforms.SelectField(
+ _('License'),
+ [wtforms.validators.Optional(),],
+ choices=licenses_as_choices())
+
+class AddCollectionForm(wtforms.Form):
+ title = wtforms.TextField(
+ _('Title'),
+ [wtforms.validators.Length(min=0, max=500), wtforms.validators.Required()])
+ description = wtforms.TextAreaField(
+ _('Description of this collection'),
+ description=_("""You can use
+ <a href="http://daringfireball.net/projects/markdown/basics">
+ Markdown</a> for formatting."""))
diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py
new file mode 100644
index 00000000..7e85696b
--- /dev/null
+++ b/mediagoblin/submit/lib.py
@@ -0,0 +1,102 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import uuid
+from werkzeug.utils import secure_filename
+from werkzeug.datastructures import FileStorage
+
+from mediagoblin.db.models import MediaEntry
+from mediagoblin.processing import mark_entry_failed
+from mediagoblin.processing.task import process_media
+
+
+_log = logging.getLogger(__name__)
+
+
+def check_file_field(request, field_name):
+ """Check if a file field meets minimal criteria"""
+ retval = (field_name in request.files
+ and isinstance(request.files[field_name], FileStorage)
+ and request.files[field_name].stream)
+ if not retval:
+ _log.debug("Form did not contain proper file field %s", field_name)
+ return retval
+
+
+def new_upload_entry(user):
+ """
+ Create a new MediaEntry for uploading
+ """
+ entry = MediaEntry()
+ entry.uploader = user.id
+ entry.license = user.license_preference
+ return entry
+
+
+def prepare_queue_task(app, entry, filename):
+ """
+ Prepare a MediaEntry for the processing queue and get a queue file
+ """
+ # We generate this ourselves so we know what the task id is for
+ # retrieval later.
+
+ # (If we got it off the task's auto-generation, there'd be
+ # a risk of a race condition when we'd save after sending
+ # off the task)
+ task_id = unicode(uuid.uuid4())
+ entry.queued_task_id = task_id
+
+ # Now store generate the queueing related filename
+ queue_filepath = app.queue_store.get_unique_filepath(
+ ['media_entries',
+ task_id,
+ secure_filename(filename)])
+
+ # queue appropriately
+ queue_file = app.queue_store.get_file(
+ queue_filepath, 'wb')
+
+ # Add queued filename to the entry
+ entry.queued_media_file = queue_filepath
+
+ return queue_file
+
+
+def run_process_media(entry, feed_url=None):
+ """Process the media asynchronously
+
+ :param entry: MediaEntry() instance to be processed.
+ :param feed_url: A string indicating the feed_url that the PuSH servers
+ should be notified of. This will be sth like: `request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',qualified=True,
+ user=request.user.username)`"""
+ try:
+ process_media.apply_async(
+ [entry.id, feed_url], {},
+ task_id=entry.queued_task_id)
+ except BaseException as exc:
+ # The purpose of this section is because when running in "lazy"
+ # or always-eager-with-exceptions-propagated celery mode that
+ # the failure handling won't happen on Celery end. Since we
+ # expect a lot of users to run things in this way we have to
+ # capture stuff here.
+ #
+ # ... not completely the diaper pattern because the
+ # exception is re-raised :)
+ mark_entry_failed(entry.id, exc)
+ # re-raise the exception
+ raise
diff --git a/mediagoblin/submit/routing.py b/mediagoblin/submit/routing.py
new file mode 100644
index 00000000..085344fd
--- /dev/null
+++ b/mediagoblin/submit/routing.py
@@ -0,0 +1,21 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.routing import add_route
+
+add_route('mediagoblin.submit.start',
+ '/submit/', 'mediagoblin.submit.views:submit_start')
+add_route('mediagoblin.submit.collection', '/submit/collection', 'mediagoblin.submit.views:add_collection')
diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py
new file mode 100644
index 00000000..a70c89b4
--- /dev/null
+++ b/mediagoblin/submit/views.py
@@ -0,0 +1,153 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin import messages
+import mediagoblin.mg_globals as mg_globals
+from os.path import splitext
+
+import logging
+
+_log = logging.getLogger(__name__)
+
+
+from mediagoblin.tools.text import convert_to_tag_list_of_dicts
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.tools.response import render_to_response, redirect
+from mediagoblin.decorators import require_active_login
+from mediagoblin.submit import forms as submit_forms
+from mediagoblin.messages import add_message, SUCCESS
+from mediagoblin.media_types import sniff_media, \
+ InvalidFileType, FileTypeNotSupported
+from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \
+ run_process_media, new_upload_entry
+
+
+@require_active_login
+def submit_start(request):
+ """
+ First view for submitting a file.
+ """
+ submit_form = submit_forms.SubmitStartForm(request.form,
+ license=request.user.license_preference)
+
+ if request.method == 'POST' and submit_form.validate():
+ if not check_file_field(request, 'file'):
+ submit_form.file.errors.append(
+ _(u'You must provide a file.'))
+ else:
+ try:
+ filename = request.files['file'].filename
+
+ # Sniff the submitted media to determine which
+ # media plugin should handle processing
+ media_type, media_manager = sniff_media(
+ request.files['file'])
+
+ # create entry and save in database
+ entry = new_upload_entry(request.user)
+ entry.media_type = unicode(media_type)
+ entry.title = (
+ unicode(submit_form.title.data)
+ or unicode(splitext(filename)[0]))
+
+ entry.description = unicode(submit_form.description.data)
+
+ entry.license = unicode(submit_form.license.data) or None
+
+ # Process the user's folksonomy "tags"
+ entry.tags = convert_to_tag_list_of_dicts(
+ submit_form.tags.data)
+
+ # Generate a slug from the title
+ entry.generate_slug()
+
+ queue_file = prepare_queue_task(request.app, entry, filename)
+
+ with queue_file:
+ queue_file.write(request.files['file'].stream.read())
+
+ # Save now so we have this data before kicking off processing
+ entry.save()
+
+ # Pass off to processing
+ #
+ # (... don't change entry after this point to avoid race
+ # conditions with changes to the document via processing code)
+ feed_url = request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ qualified=True, user=request.user.username)
+ run_process_media(entry, feed_url)
+ add_message(request, SUCCESS, _('Woohoo! Submitted!'))
+
+ return redirect(request, "mediagoblin.user_pages.user_home",
+ user=request.user.username)
+ except Exception as e:
+ '''
+ This section is intended to catch exceptions raised in
+ mediagoblin.media_types
+ '''
+ if isinstance(e, InvalidFileType) or \
+ isinstance(e, FileTypeNotSupported):
+ submit_form.file.errors.append(
+ e)
+ else:
+ raise
+
+ return render_to_response(
+ request,
+ 'mediagoblin/submit/start.html',
+ {'submit_form': submit_form,
+ 'app_config': mg_globals.app_config})
+
+
+@require_active_login
+def add_collection(request, media=None):
+ """
+ View to create a new collection
+ """
+ submit_form = submit_forms.AddCollectionForm(request.form)
+
+ if request.method == 'POST' and submit_form.validate():
+ collection = request.db.Collection()
+
+ collection.title = unicode(submit_form.title.data)
+ collection.description = unicode(submit_form.description.data)
+ collection.creator = request.user.id
+ collection.generate_slug()
+
+ # Make sure this user isn't duplicating an existing collection
+ existing_collection = request.db.Collection.find_one({
+ 'creator': request.user.id,
+ 'title':collection.title})
+
+ if existing_collection:
+ add_message(request, messages.ERROR,
+ _('You already have a collection called "%s"!') \
+ % collection.title)
+ else:
+ collection.save()
+
+ add_message(request, SUCCESS,
+ _('Collection "%s" added!') % collection.title)
+
+ return redirect(request, "mediagoblin.user_pages.user_home",
+ user=request.user.username)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/submit/collection.html',
+ {'submit_form': submit_form,
+ 'app_config': mg_globals.app_config})
diff --git a/mediagoblin/templates/mediagoblin/admin/panel.html b/mediagoblin/templates/mediagoblin/admin/panel.html
new file mode 100644
index 00000000..1c3c866e
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/admin/panel.html
@@ -0,0 +1,114 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+ {% trans %}Media processing panel{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+<h1>{% trans %}Media processing panel{% endtrans %}</h1>
+
+<p>
+ {% trans %}Here you can track the state of media being processed on this instance.{% endtrans %}
+</p>
+
+<h2>{% trans %}Media in-processing{% endtrans %}</h2>
+
+{% if processing_entries.count() %}
+ <table class="media_panel processing">
+ <tr>
+ <th>ID</th>
+ <th>User</th>
+ <th>Title</th>
+ <th>When submitted</th>
+ <th>Transcoding progress</th>
+ </tr>
+ {% for media_entry in processing_entries %}
+ <tr>
+ <td>{{ media_entry.id }}</td>
+ <td>{{ media_entry.get_uploader.username }}</td>
+ <td>{{ media_entry.title }}</td>
+ <td>{{ media_entry.created.strftime("%F %R") }}</td>
+ {% if media_entry.transcoding_progress %}
+ <td>{{ media_entry.transcoding_progress }}%</td>
+ {% else %}
+ <td>Unknown</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+{% else %}
+ <p><em>{% trans %}No media in-processing{% endtrans %}</em></p>
+{% endif %}
+
+<h2>{% trans %}These uploads failed to process:{% endtrans %}</h2>
+{% if failed_entries.count() %}
+
+ <table class="media_panel failed">
+ <tr>
+ <th>ID</th>
+ <th>User</th>
+ <th>Title</th>
+ <th>When submitted</th>
+ <th>Reason for failure</th>
+ <th>Failure metadata</th>
+ </tr>
+ {% for media_entry in failed_entries %}
+ <tr>
+ <td>{{ media_entry.id }}</td>
+ <td>{{ media_entry.get_uploader.username }}</td>
+ <td>{{ media_entry.title }}</td>
+ <td>{{ media_entry.created.strftime("%F %R") }}</td>
+ {% if media_entry.get_fail_exception() %}
+ <td>{{ media_entry.get_fail_exception().general_message }}</td>
+ <td>{{ media_entry.fail_metadata }}</td>
+ {% else %}
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+{% else %}
+ <p><em>{% trans %}No failed entries!{% endtrans %}</em></p>
+{% endif %}
+<h2>{% trans %}Last 10 successful uploads{% endtrans %}</h2>
+{% if processed_entries.count() %}
+
+ <table class="media_panel processed">
+ <tr>
+ <th>ID</th>
+ <th>User</th>
+ <th>Title</th>
+ <th>Submitted</th>
+ </tr>
+ {% for media_entry in processed_entries %}
+ <tr>
+ <td>{{ media_entry.id }}</td>
+ <td>{{ media_entry.get_uploader.username }}</td>
+ <td><a href="{{ media_entry.url_for_self(request.urlgen) }}">{{ media_entry.title }}</a></td>
+ <td><span title='{{ media_entry.created.strftime("%F %R") }}'>{{ timesince(media_entry.created) }}</span></td>
+ </tr>
+ {% endfor %}
+ </table>
+{% else %}
+ <p><em>{% trans %}No processed entries, yet!{% endtrans %}</em></p>
+{% endif %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/auth/change_fp.html b/mediagoblin/templates/mediagoblin/auth/change_fp.html
new file mode 100644
index 00000000..1f7d9aca
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/change_fp.html
@@ -0,0 +1,44 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_head %}
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/show_password.js') }}"></script>
+{% endblock mediagoblin_head %}
+
+{% block title -%}
+ {% trans %}Set your new password{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.auth.verify_forgot_password') }}"
+ method="POST" enctype="multipart/form-data">
+ {{ csrf_token }}
+ <div class="form_box">
+ <h1>{% trans %}Set your new password{% endtrans %}</h1>
+ {{ wtforms_util.render_divs(cp_form) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Set password{% endtrans %}" class="button_form"/>
+ </div>
+ </div>
+ </form>
+{% endblock %}
+
diff --git a/mediagoblin/templates/mediagoblin/auth/forgot_password.html b/mediagoblin/templates/mediagoblin/auth/forgot_password.html
new file mode 100644
index 00000000..46aeddef
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/forgot_password.html
@@ -0,0 +1,38 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans %}Recover password{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.auth.forgot_password') }}"
+ method="POST" enctype="multipart/form-data">
+ {{ csrf_token }}
+ <div class="form_box">
+ <h1>{% trans %}Recover password{% endtrans %}</h1>
+ {{ wtforms_util.render_divs(fp_form) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Send instructions{% endtrans %}" class="button_form"/>
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt b/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt
new file mode 100644
index 00000000..fb5e1674
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt
@@ -0,0 +1,30 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 Free Software Foundation, Inc
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% trans username=username, verification_url=verification_url|safe -%}
+Hi {{ username }},
+
+to change your GNU MediaGoblin password, open the following URL in
+your web browser:
+
+{{ verification_url }}
+
+If you think this is an error, just ignore this email and continue being
+a happy goblin!
+{%- endtrans %}
+
diff --git a/mediagoblin/templates/mediagoblin/auth/login.html b/mediagoblin/templates/mediagoblin/auth/login.html
new file mode 100644
index 00000000..4a39059d
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/login.html
@@ -0,0 +1,62 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_head %}
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/autofilledin_password.js') }}"></script>
+{% endblock %}
+
+{% block title -%}
+ {% trans %}Log in{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.auth.login') }}"
+ method="POST" enctype="multipart/form-data">
+ {{ csrf_token }}
+ <div class="form_box">
+ <h1>{% trans %}Log in{% endtrans %}</h1>
+ {% if login_failed %}
+ <div class="form_field_error">
+ {% trans %}Logging in failed!{% endtrans %}
+ </div>
+ {% endif %}
+ {% if allow_registration %}
+ <p>
+ {% trans %}Don't have an account yet?{% endtrans %} <a href="{{ request.urlgen('mediagoblin.auth.register') }}">
+ {%- trans %}Create one here!{% endtrans %}</a>
+ </p>
+ {% endif %}
+ {{ wtforms_util.render_divs(login_form) }}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.auth.forgot_password') }}" id="forgot_password">
+ {% trans %}Forgot your password?{% endtrans %}</a>
+ </p>
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Log in{% endtrans %}" class="button_form"/>
+ </div>
+ {% if next %}
+ <input type="hidden" name="next" value="{{ next }}" class="button_form"
+ style="display: none;"/>
+ {% endif %}
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/auth/register.html b/mediagoblin/templates/mediagoblin/auth/register.html
new file mode 100644
index 00000000..6dff0207
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/register.html
@@ -0,0 +1,47 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_head %}
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/show_password.js') }}"></script>
+{% endblock mediagoblin_head %}
+
+{% block title -%}
+ {% trans %}Create an account!{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.auth.register') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>{% trans %}Create an account!{% endtrans %}</h1>
+ {{ wtforms_util.render_divs(register_form) }}
+ {{ csrf_token }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Create{% endtrans %}"
+ class="button_form" />
+ </div>
+ </div>
+ </form>
+<!-- Focus the username field by default -->
+<script>$(document).ready(function(){$("#username").focus();});</script>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/auth/verification_email.txt b/mediagoblin/templates/mediagoblin/auth/verification_email.txt
new file mode 100644
index 00000000..969ef96a
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/auth/verification_email.txt
@@ -0,0 +1,26 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+
+{% trans username=username, verification_url=verification_url|safe -%}
+Hi {{ username }},
+
+to activate your GNU MediaGoblin account, open the following URL in
+your web browser:
+
+{{ verification_url }}
+{%- endtrans %}
diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html
new file mode 100644
index 00000000..6c7c07d0
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/base.html
@@ -0,0 +1,128 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+<!doctype html>
+<html
+{% block mediagoblin_html_tag %}
+{% endblock mediagoblin_html_tag %}
+>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>{% block title %}{{ app_config['html_title'] }}{% endblock %}</title>
+ <link rel="stylesheet" type="text/css"
+ href="{{ request.staticdirect('/css/extlib/reset.css') }}"/>
+ <link rel="stylesheet" type="text/css"
+ href="{{ request.staticdirect('/css/base.css') }}"/>
+ <link rel="shortcut icon"
+ href="{{ request.staticdirect('/images/goblin.ico') }}" />
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/extlib/jquery.js') }}"></script>
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/header_dropdown.js') }}"></script>
+
+ {# For clarification, the difference between the extra_head.html template
+ # and the head template hook is that the former should be used by
+ # themes and the latter should be used by plugins.
+ # The reason is that only one thing can override extra_head.html...
+ # but multiple plugins can hook into the template hook.
+ #}
+ {% include "mediagoblin/extra_head.html" %}
+ {% template_hook("head") %}
+
+ {% block mediagoblin_head %}
+ {% endblock mediagoblin_head %}
+ </head>
+ <body>
+ {% include 'mediagoblin/bits/body_start.html' %}
+ {% block mediagoblin_body %}
+ {% block mediagoblin_header %}
+ <header>
+ {%- include "mediagoblin/bits/logo.html" -%}
+ {% block mediagoblin_header_title %}{% endblock %}
+ <div class="header_right">
+ {%- if request.user %}
+ {% if request.user and request.user.status == 'active' %}
+ <div class="button_action header_dropdown_down">&#9660;</div>
+ <div class="button_action header_dropdown_up">&#9650;</div>
+ {% elif request.user and request.user.status == "needs_email_verification" %}
+ {# the following link should only appear when verification is needed #}
+ <a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
+ user=request.user.username) }}"
+ class="button_action_highlight">
+ {% trans %}Verify your email!{% endtrans %}</a>
+ or <a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}log out{% endtrans %}</a>
+ {% endif %}
+ {%- else %}
+ <a href="{{ request.urlgen('mediagoblin.auth.login') }}?next={{
+ request.base_url|urlencode }}">
+ {%- trans %}Log in{% endtrans -%}
+ </a>
+ {%- endif %}
+ </div>
+ <div class="clear"></div>
+ {% if request.user and request.user.status == 'active' %}
+ <div class="header_dropdown">
+ <p>
+ <span class="dropdown_title">
+ {% trans user_url=request.urlgen('mediagoblin.user_pages.user_home',
+ user=request.user.username),
+ user_name=request.user.username -%}
+ <a href="{{ user_url }}">{{ user_name }}</a>'s account
+ {%- endtrans %}
+ </span>
+ &middot;
+ <a href="{{ request.urlgen('mediagoblin.edit.account') }}">{%- trans %}Change account settings{% endtrans -%}</a>
+ &middot;
+ <a href="{{ request.urlgen('mediagoblin.user_pages.processing_panel',
+ user=request.user.username) }}">
+ {%- trans %}Media processing panel{% endtrans -%}
+ </a>
+ &middot;
+ <a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}Log out{% endtrans %}</a>
+ </p>
+ <a class="button_action" href="{{ request.urlgen('mediagoblin.submit.start') }}">
+ {%- trans %}Add media{% endtrans -%}
+ </a>
+ <a class="button_action" href="{{ request.urlgen('mediagoblin.submit.collection') }}">
+ {%- trans %}Create new collection{% endtrans -%}
+ </a>
+ {% if request.user.is_admin %}
+ <p>
+ <span class="dropdown_title">Admin powers:</span>
+ <a href="{{ request.urlgen('mediagoblin.admin.panel') }}">
+ {%- trans %}Media processing panel{% endtrans -%}
+ </a>
+ </p>
+ {% endif %}
+ </div>
+ {% endif %}
+ </header>
+ {% endblock %}
+ <div class="container">
+ {% include 'mediagoblin/bits/above_content.html' %}
+ <div class="mediagoblin_content">
+ {% include "mediagoblin/utils/messages.html" %}
+ {% block mediagoblin_content %}
+ {% endblock mediagoblin_content %}
+ </div>
+ {%- include "mediagoblin/bits/base_footer.html" %}
+ </div>
+ {%- endblock mediagoblin_body %}
+ {% include 'mediagoblin/bits/body_end.html' %}
+ </body>
+</html>
diff --git a/mediagoblin/templates/mediagoblin/bits/above_content.html b/mediagoblin/templates/mediagoblin/bits/above_content.html
new file mode 100644
index 00000000..bb7b9762
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/bits/above_content.html
@@ -0,0 +1,17 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
diff --git a/mediagoblin/templates/mediagoblin/bits/base_footer.html b/mediagoblin/templates/mediagoblin/bits/base_footer.html
new file mode 100644
index 00000000..80cd41b0
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/bits/base_footer.html
@@ -0,0 +1,28 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011-2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{%- block mediagoblin_footer %}
+ <footer>
+ {% trans -%}
+ Powered by <a href="http://mediagoblin.org/" title='Version {{ version }}'>MediaGoblin</a>, a <a href="http://gnu.org/">GNU</a> project.
+ {%- endtrans %}
+ {% trans source_link=app_config['source_link'] -%}
+ Released under the <a href="http://www.fsf.org/licensing/licenses/agpl-3.0.html">AGPL</a>. <a href="{{ source_link }}">Source code</a> available.
+ {%- endtrans %}
+ </footer>
+{%- endblock mediagoblin_footer -%}
diff --git a/mediagoblin/templates/mediagoblin/bits/body_end.html b/mediagoblin/templates/mediagoblin/bits/body_end.html
new file mode 100644
index 00000000..bb7b9762
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/bits/body_end.html
@@ -0,0 +1,17 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
diff --git a/mediagoblin/templates/mediagoblin/bits/body_start.html b/mediagoblin/templates/mediagoblin/bits/body_start.html
new file mode 100644
index 00000000..bb7b9762
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/bits/body_start.html
@@ -0,0 +1,17 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
diff --git a/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html b/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html
new file mode 100644
index 00000000..544ee146
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html
@@ -0,0 +1,35 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% if request.user %}
+ <h1>{% trans %}Explore{% endtrans %}</h1>
+{% else %}
+ <h1>{% trans %}Hi there, welcome to this MediaGoblin site!{% endtrans %}</h1>
+ <img class="right_align" src="{{ request.staticdirect('/images/frontpage_image.png') }}" />
+ <p>{% trans %}This site is running <a href="http://mediagoblin.org">MediaGoblin</a>, an extraordinarily great piece of media hosting software.{% endtrans %}</p>
+ <p>{% trans %}To add your own media, place comments, and more, you can log in with your MediaGoblin account.{% endtrans %}</p>
+ {% if allow_registration %}
+ <p>{% trans %}Don't have one yet? It's easy!{% endtrans %}</p>
+ {% trans register_url=request.urlgen('mediagoblin.auth.register') -%}
+ <a class="button_action_highlight" href="{{ register_url }}">Create an account at this site</a>
+ or
+ <a class="button_action" href="http://wiki.mediagoblin.org/HackingHowto">Set up MediaGoblin on your own server</a>
+ {%- endtrans %}
+ {% endif %}
+ <div class="clear"></div>
+{% endif %}
diff --git a/mediagoblin/templates/mediagoblin/bits/logo.html b/mediagoblin/templates/mediagoblin/bits/logo.html
new file mode 100644
index 00000000..5bd8edd8
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/bits/logo.html
@@ -0,0 +1,25 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+
+{% block mediagoblin_logo %}
+ <a class="logo"
+ href="{{ request.urlgen('index') }}"
+ ><img src="{{ request.staticdirect('/images/logo.png') }}"
+ alt="{% trans %}MediaGoblin logo{% endtrans %}" />
+ </a>
+{% endblock mediagoblin_logo -%}
diff --git a/mediagoblin/templates/mediagoblin/edit/attachments.html b/mediagoblin/templates/mediagoblin/edit/attachments.html
new file mode 100644
index 00000000..3fbea3be
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/attachments.html
@@ -0,0 +1,69 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{%- extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans media_title=media.title -%}
+ Editing attachments for {{ media_title }}
+ {%- endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.edit.attachments',
+ user= media.get_uploader.username,
+ media_id=media.id) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans media_title=media.title -%}
+ Editing attachments for {{ media_title }}
+ {%- endtrans -%}
+ </h1>
+ <div style="text-align: center;" >
+ <img src="{{ media.thumb_url }}" />
+ </div>
+
+ {% if media.attachment_files|count %}
+ <h2>{% trans %}Attachments{% endtrans %}</h2>
+ <ul>
+ {%- for attachment in media.attachment_files %}
+ <li>
+ <a target="_blank" href="{{ request.app.public_store.file_url(
+ attachment['filepath']) }}">
+ {{- attachment.name -}}
+ </a>
+ </li>
+ {%- endfor %}
+ </ul>
+ {% endif %}
+
+ <h2>{% trans %}Add attachment{% endtrans %}</h2>
+ {{- wtforms_util.render_divs(form) }}
+ <div class="form_submit_buttons">
+ <a href="{{ media.url_for_self(request.urlgen) }}">
+ {%- trans %}Cancel{% endtrans -%}
+ </a>
+ <input type="submit" value="{% trans %}Save changes{% endtrans %}"
+ class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/edit/change_pass.html b/mediagoblin/templates/mediagoblin/edit/change_pass.html
new file mode 100644
index 00000000..ff909b07
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/change_pass.html
@@ -0,0 +1,52 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_head %}
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/show_password.js') }}"></script>
+{% endblock mediagoblin_head %}
+
+{% block title -%}
+ {% trans username=user.username -%}
+ Changing {{ username }}'s password
+ {%- endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.edit.pass') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box edit_box">
+ <h1>
+ {%- trans username=user.username -%}
+ Changing {{ username }}'s password
+ {%- endtrans -%}
+ </h1>
+ {{ wtforms_util.render_divs(form) }}
+ {{ csrf_token }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Save{% endtrans %}"
+ class="button_form" />
+ </div>
+ </div>
+ </form>
+{% endblock %}
+
+
diff --git a/mediagoblin/templates/mediagoblin/edit/delete_account.html b/mediagoblin/templates/mediagoblin/edit/delete_account.html
new file mode 100644
index 00000000..84d0b580
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/delete_account.html
@@ -0,0 +1,48 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.edit.delete_account') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans user_name=user.username -%}
+ Really delete user '{{ user_name }}' and all related media/comments?
+ {%- endtrans -%}
+ </h1>
+ <p class="delete_checkbox_box">
+ <input type="checkbox" name="confirmed"/>
+ <label for="confirmed">
+ {%- trans %}Yes, really delete my account{% endtrans -%}
+ </label>
+ </p>
+
+ <div class="form_submit_buttons">
+ <a class="button_action" href="{{ request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username) }}">{% trans %}Cancel{% endtrans %}</a>
+ {{ csrf_token }}
+ <input type="submit" value="{% trans %}Delete permanently{% endtrans %}" class="button_form" />
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html
new file mode 100644
index 00000000..9a040095
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/edit.html
@@ -0,0 +1,48 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans media_title=media.title -%}
+ Editing {{ media_title }}
+ {%- endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.edit.edit_media',
+ user= media.get_uploader.username,
+ media_id=media.id) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box_xl edit_box">
+ <h1>{% trans media_title=media.title %}Editing {{ media_title }}{% endtrans %}</h1>
+ <div style="text-align: center;" >
+ <img src="{{ media.thumb_url }}" />
+ </div>
+ {{ wtforms_util.render_divs(form) }}
+ <div class="form_submit_buttons">
+ <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a>
+ <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/edit/edit_account.html b/mediagoblin/templates/mediagoblin/edit/edit_account.html
new file mode 100644
index 00000000..4c4aaf95
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/edit_account.html
@@ -0,0 +1,65 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{%- extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_head %}
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/show_password.js') }}"></script>
+{% endblock mediagoblin_head %}
+
+{% block title -%}
+ {% trans username=user.username -%}
+ Changing {{ username }}'s account settings
+ {%- endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.edit.account') }}?username={{
+ user.username }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box edit_box">
+ <h1>
+ {%- trans username=user.username -%}
+ Changing {{ username }}'s account settings
+ {%- endtrans -%}
+ </h1>
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.edit.pass') }}">
+ {% trans %}Change your password.{% endtrans %}
+ </a>
+ </p>
+ <div class="form_field_input">
+ <p>{{ form.wants_comment_notification }}
+ {{ wtforms_util.render_label(form.wants_comment_notification) }}</p>
+ </div>
+ {{- wtforms_util.render_field_div(form.license_preference) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+ <div class="delete">
+ <a href="{{ request.urlgen('mediagoblin.edit.delete_account') }}">
+ {%- trans %}Delete my account{% endtrans -%}
+ </a>
+ </div>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/edit/edit_collection.html b/mediagoblin/templates/mediagoblin/edit/edit_collection.html
new file mode 100644
index 00000000..5cf5bae8
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/edit_collection.html
@@ -0,0 +1,39 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.edit.edit_collection',
+ user= collection.get_creator.username,
+ collection= collection.slug) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box_xl edit_box">
+ <h1>{% trans collection_title=collection.title %}Editing {{ collection_title }}{% endtrans %}</h1>
+ {{ wtforms_util.render_divs(form) }}
+ <div class="form_submit_buttons">
+ <a class="button_action" href="{{ collection.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a>
+ <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/edit/edit_profile.html b/mediagoblin/templates/mediagoblin/edit/edit_profile.html
new file mode 100644
index 00000000..163fe186
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/edit/edit_profile.html
@@ -0,0 +1,45 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans username=user.username -%}
+ Editing {{ username }}'s profile
+ {%- endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.edit.profile',
+ user=user.username) }}" method="POST" enctype="multipart/form-data">
+ <div class="form_box edit_box">
+ <h1>
+ {%- trans username=user.username -%}
+ Editing {{ username }}'s profile
+ {%- endtrans %}
+ </h1>
+ {{ wtforms_util.render_divs(form) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/error.html b/mediagoblin/templates/mediagoblin/error.html
new file mode 100644
index 00000000..c16b650f
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/error.html
@@ -0,0 +1,28 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title %}{{err_code}} &mdash; {{ super() }}{% endblock %}
+
+{% block mediagoblin_content %}
+ <img class="right_align" src="{{ request.staticdirect('/images/404.png') }}"
+ alt="{% trans %}Image of goblin stressing out{% endtrans %}" />
+ <h1>{{ title }}</h1>
+ <p>{{ err_msg|safe }}</p>
+ <div class="clear"></div>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/extra_head.html b/mediagoblin/templates/mediagoblin/extra_head.html
new file mode 100644
index 00000000..973e2b48
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/extra_head.html
@@ -0,0 +1,19 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+
+{# Add extra head declarations here for your theme, if appropriate #}
diff --git a/mediagoblin/templates/mediagoblin/listings/collection.html b/mediagoblin/templates/mediagoblin/listings/collection.html
new file mode 100644
index 00000000..4d502201
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/listings/collection.html
@@ -0,0 +1,43 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% from "mediagoblin/utils/object_gallery.html" import object_gallery %}
+
+{% block mediagoblin_head %}
+ <link rel="alternate" type="application/atom+xml"
+ href="{{ request.urlgen(
+ 'mediagoblin.listings.tag_atom_feed',
+ tag=tag_slug) }}">
+{% endblock mediagoblin_head %}
+
+{% block title %}
+ {% trans %}Media tagged with: {{ tag_name }}{% endtrans %} &mdash; {{ super() }}
+{% endblock %}
+
+{% block mediagoblin_content -%}
+ <h1>
+ {% trans %}Media tagged with: {{ tag_name }}{% endtrans %}
+ </h1>
+
+ {{ object_gallery(request, media_entries, pagination) }}
+
+ {% set feed_url = request.urlgen('mediagoblin.listings.tag_atom_feed',
+ tag=tag_slug) %}
+ {% include "mediagoblin/utils/feed_link.html" %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/listings/tag.html b/mediagoblin/templates/mediagoblin/listings/tag.html
new file mode 100644
index 00000000..4d502201
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/listings/tag.html
@@ -0,0 +1,43 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% from "mediagoblin/utils/object_gallery.html" import object_gallery %}
+
+{% block mediagoblin_head %}
+ <link rel="alternate" type="application/atom+xml"
+ href="{{ request.urlgen(
+ 'mediagoblin.listings.tag_atom_feed',
+ tag=tag_slug) }}">
+{% endblock mediagoblin_head %}
+
+{% block title %}
+ {% trans %}Media tagged with: {{ tag_name }}{% endtrans %} &mdash; {{ super() }}
+{% endblock %}
+
+{% block mediagoblin_content -%}
+ <h1>
+ {% trans %}Media tagged with: {{ tag_name }}{% endtrans %}
+ </h1>
+
+ {{ object_gallery(request, media_entries, pagination) }}
+
+ {% set feed_url = request.urlgen('mediagoblin.listings.tag_atom_feed',
+ tag=tag_slug) %}
+ {% include "mediagoblin/utils/feed_link.html" %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/media_displays/ascii.html b/mediagoblin/templates/mediagoblin/media_displays/ascii.html
new file mode 100644
index 00000000..3cc5e0ab
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/media_displays/ascii.html
@@ -0,0 +1,44 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% extends 'mediagoblin/user_pages/media.html' %}
+
+{% block mediagoblin_media %}
+ <div class="ascii-wrapper">
+ <pre>
+ {%- autoescape False -%}
+ {{- request.app.public_store.get_file(
+ media.media_files['unicode']).read()|string -}}
+ {%- endautoescape -%}
+ </pre>
+ </div>
+{% endblock %}
+
+{% block mediagoblin_sidebar %}
+ {% if 'original' in media.media_files %}
+ <h3>{% trans %}Download{% endtrans %}</h3>
+ <p>
+ <a href="{{ request.app.public_store.file_url(
+ media.media_files['original']) }}">
+ {%- trans -%}
+ Original
+ {%- endtrans -%}
+ </a>
+ </p>
+ {% endif %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/media_displays/audio.html b/mediagoblin/templates/mediagoblin/media_displays/audio.html
new file mode 100644
index 00000000..95bc6e88
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/media_displays/audio.html
@@ -0,0 +1,65 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% extends 'mediagoblin/user_pages/media.html' %}
+
+{% block mediagoblin_head %}
+ {{ super() }}
+ <link rel="stylesheet" type="text/css" href="{{ request.staticdirect('/css/audio.css') }}" />
+ <script type="text/javascript" src="{{ request.staticdirect(
+ '/js/extlib/html5slider.js') }}"></script>
+ <script type="text/javascript" src="{{ request.staticdirect(
+ '/js/audio.js') }}"></script>
+{% endblock %}
+
+{% block mediagoblin_media %}
+ <div class="audio-media">
+ {% if 'spectrogram' in media.media_files %}
+ <div class="audio-spectrogram">
+ <img src="{{ request.app.public_store.file_url(
+ media.media_files.spectrogram) }}"
+ alt="Spectrogram" />
+ </div>
+ {% endif %}
+ <audio class="audio-player" controls="controls"
+ preload="metadata">
+ <source src="{{ request.app.public_store.file_url(
+ media.media_files.webm_audio) }}" type="audio/webm; codecs=vorbis" />
+ <div class="no_html5">
+ {%- trans -%}Sorry, this audio will not work because
+ your web browser does not support HTML5
+ audio.{%- endtrans -%}<br/>
+ {%- trans -%}You can get a modern web browser that
+ can play the audio at <a href="http://getfirefox.com">
+ http://getfirefox.com</a>!{%- endtrans -%}
+ </div>
+ </audio>
+ </div>
+{% endblock %}
+
+{% block mediagoblin_sidebar %}
+ <h3>{% trans %}Download{% endtrans %}</h3>
+ <ul>
+ {% if 'original' in media.media_files %}
+ <li><a href="{{ request.app.public_store.file_url(
+ media.media_files.original) }}">{% trans %}Original file{% endtrans %}</a>
+ {% endif %}
+ <li><a href="{{ request.app.public_store.file_url(
+ media.media_files.webm_audio) }}">{% trans %}WebM file (Vorbis codec){% endtrans %}</a>
+ </ul>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/media_displays/image.html b/mediagoblin/templates/mediagoblin/media_displays/image.html
new file mode 100644
index 00000000..d0050f50
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/media_displays/image.html
@@ -0,0 +1,46 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% extends 'mediagoblin/user_pages/media.html' %}
+
+{% block mediagoblin_head %}
+ {{ super() }}
+ {% template_hook("image_head") %}
+{% endblock mediagoblin_head %}
+
+{% block mediagoblin_sidebar %}
+ {{ super() }}
+ {% template_hook("image_sideinfo") %}
+{% endblock %}
+
+{% block mediagoblin_after_added_sidebar %}
+ {% if app_config['original_date_visible'] %}
+ {% set original_date = media.media_manager.get_original_date() %}
+
+ {% if original_date %}
+ <h3>{% trans %}Created{% endtrans %}</h3>
+
+ <p><span title="{{ original_date.strftime("%I:%M%p %Y-%m-%d") }}">
+ {%- trans formatted_time=timesince(original_date) -%}
+ {{ formatted_time }} ago
+ {%- endtrans -%}
+ </span></p>
+ {%- endif %}
+ {% endif %}
+{% endblock %}
+
diff --git a/mediagoblin/templates/mediagoblin/media_displays/pdf.html b/mediagoblin/templates/mediagoblin/media_displays/pdf.html
new file mode 100644
index 00000000..9319e87c
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/media_displays/pdf.html
@@ -0,0 +1,86 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% extends 'mediagoblin/user_pages/media.html' %}
+
+{% set medium_view = request.app.public_store.file_url(
+ media.media_files['medium']) %}
+
+{% if 'pdf' in media.media_files %}
+ {% set pdf_view = request.app.public_store.file_url(
+ media.media_files['pdf']) %}
+{% else %}
+ {% set pdf_view = request.app.public_store.file_url(
+ media.media_files['original']) %}
+{% endif %}
+
+{% set pdf_js = global_config.get('media_type:mediagoblin.media_types.pdf', {}).get('pdf_js', False) %}
+
+{% if pdf_js %}
+ {% block mediagoblin_html_tag %}
+ dir="ltr" mozdisallowselectionprint moznomarginboxes
+ {% endblock mediagoblin_html_tag %}
+{% endif %}
+
+{% block mediagoblin_head -%}
+ {{ super() }}
+
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+
+{%- endblock %}
+
+{% block mediagoblin_media %}
+ {% if pdf_js %}
+ <iframe width="640px" height="480px"
+ src="{{ request.staticdirect('/extlib/pdf.js/web/viewer.html') }}?file={{ pdf_view }} ">
+ </iframe>
+ {% else %}
+ <a href="{{ pdf_view }}">
+ <img id="medium"
+ class="media_image"
+ src="{{ medium_view }}"
+ alt="
+ {%- trans media_title=media.title -%}
+ Image for {{ media_title}}
+ {%- endtrans %}"/>
+ </a>
+ {% endif %}
+{% endblock %}
+
+{% block mediagoblin_sidebar %}
+ <h3>{% trans %}Download{% endtrans %}</h3>
+ <ul>
+ {% if 'original' in media.media_files %}
+ <li>
+ <a href="{{ request.app.public_store.file_url(
+ media.media_files.original) }}">
+ {%- trans %}Original file{% endtrans -%}
+ </a>
+ </li>
+ {% endif %}
+ {% if 'pdf' in media.media_files %}
+ <li>
+ <a href="{{ request.app.public_store.file_url(
+ media.media_files.pdf) }}">
+ {%- trans %}PDF file{% endtrans -%}
+ </a>
+ </li>
+ {% endif %}
+ </ul>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/media_displays/stl.html b/mediagoblin/templates/mediagoblin/media_displays/stl.html
new file mode 100644
index 00000000..a89e0b4f
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/media_displays/stl.html
@@ -0,0 +1,150 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% extends 'mediagoblin/user_pages/media.html' %}
+
+
+{% block mediagoblin_media %}
+
+
+{% set model_download = request.app.public_store.file_url(
+ media.media_files['original']) %}
+{% set perspective_view = request.app.public_store.file_url(
+ media.media_files['perspective']) %}
+{% set top_view = request.app.public_store.file_url(
+ media.media_files['top']) %}
+{% set side_view = request.app.public_store.file_url(
+ media.media_files['side']) %}
+{% set front_view = request.app.public_store.file_url(
+ media.media_files['front']) %}
+
+<style type="text/css">
+#top_view, #side_view, #front_view, #thingy_view {
+ display: none;
+}
+.media_image {
+ cursor: inherit!important;
+}
+
+</style>
+
+{% if media.media_data.file_type == "stl" %}
+ <script src="{{ request.staticdirect('/js/extlib/thingiview.js/Three.js') }}"></script>
+ <script src="{{ request.staticdirect('/js/extlib/thingiview.js/plane.js') }}"></script>
+ <script src="{{ request.staticdirect('/js/extlib/thingiview.js/thingiview.js') }}"></script>
+{% endif %}
+
+
+<script type="text/javascript">
+window.show = function (view_id) {
+ ids = [
+ "perspective",
+ "top_view",
+ "side_view",
+ "front_view",
+ "thingy_view",
+ ];
+ for (var i=0; i<ids.length; i+=1) {
+ id = ids[i];
+ var view = document.getElementById(id);
+ view.style.display = id===view_id ? "block" : "none";
+ }
+};
+
+window.show_things = function () {
+ document.getElementById("webgl_button").onclick = function () {
+ show('thingy_view');
+ };
+ window.show("thingy_view");
+ thingiurlbase = "{{ request.staticdirect('/js/extlib/thingiview.js') }}";
+ thingiview = new Thingiview("thingy_view");
+ thingiview.setObjectColor('#821543');
+ thingiview.initScene();
+ thingiview.loadSTL("{{ model_download }}");
+ thingiview.setRotation(false);
+};
+</script>
+
+<img
+ id="perspective"
+ class="media_image"
+ src="{{ perspective_view }}"
+ alt="{% trans media_title=media.title -%}
+ Image for {{ media_title }}{% endtrans %}" />
+<img
+ id="top_view"
+ class="media_image"
+ src="{{ top_view }}"
+ alt="{% trans media_title=media.title -%}
+ Image for {{ media_title }}{% endtrans %}" />
+<img
+ id="side_view"
+ class="media_image"
+ src="{{ side_view }}"
+ alt="{% trans media_title=media.title -%}
+ Image for {{ media_title }}{% endtrans %}" />
+<img
+ id="front_view"
+ class="media_image"
+ src="{{ front_view }}"
+ alt="{% trans media_title=media.title -%}
+ Image for {{ media_title }}{% endtrans %}" />
+<div id="thingy_view" style="width:640px;height:640px;"></div>
+
+
+<div style="padding: 4px;">
+ <a class="button_action" onclick="show('perspective');"
+ title="{%- trans %}Toggle Rotate{% endtrans -%}">
+ {%- trans %}Perspective{% endtrans -%}
+ </a>
+ <a class="button_action" onclick="show('front_view');"
+ title="{%- trans %}Front{% endtrans -%}">
+ {%- trans %}Front{% endtrans -%}
+ </a>
+ <a class="button_action" onclick="show('top_view');"
+ title="{%- trans %}Top{% endtrans -%}">
+ {%- trans %}Top{% endtrans -%}
+ </a>
+ <a class="button_action" onclick="show('side_view');"
+ title="{%- trans %}Side{% endtrans -%}">
+ {%- trans %}Side{% endtrans -%}
+ </a>
+{% if media.media_data.file_type == "stl" %}
+ <a id="webgl_button" class="button_action"
+ onclick="show_things();"
+ title="{%- trans %}WebGL{% endtrans -%}">
+ {%- trans %}WebGL{% endtrans -%}
+ </a>
+{% endif %}
+
+ <a class="button_action" href="{{ model_download }}"
+ title="{%- trans %}Download{% endtrans -%}"
+ style="float:right;">
+ {%- trans %}Download model{% endtrans -%}
+ </a>
+</div>
+
+
+{% endblock %}
+
+{% block mediagoblin_sidebar %}
+<h3>{% trans %}File Format{% endtrans %}</h3>
+<p>{{ media.media_data.file_type }}</p>
+<h3>{% trans %}Object Height{% endtrans %}</h3>
+<p>~{{ media.media_data.height|int }} mm</p>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/media_displays/video.html b/mediagoblin/templates/mediagoblin/media_displays/video.html
new file mode 100644
index 00000000..b0854c9f
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/media_displays/video.html
@@ -0,0 +1,74 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% extends 'mediagoblin/user_pages/media.html' %}
+
+{% block mediagoblin_head -%}
+ {{ super() }}
+ <script type="text/javascript" src="{{
+ request.staticdirect('/extlib/video-js/video.min.js') }}"></script>
+ <link href="{{ request.staticdirect('/css/vjs-mg-skin.css') }}"
+ rel="stylesheet">
+{%- endblock %}
+
+{% block mediagoblin_media %}
+ {% set display_type, display_path = media.get_display_media() %}
+
+ <video controls
+ {% if global_config['media_type:mediagoblin.media_types.video']['auto_play'] %}autoplay{% endif %}
+ preload="auto" class="video-js vjs-mg-skin"
+ data-setup='{"height": {{ media.media_data.height }},
+ "width": {{ media.media_data.width }} }'>
+ <source src="{{ request.app.public_store.file_url(display_path) }}"
+ {% if media.media_data %}
+ type="{{ media.media_data.source_type() }}"
+ {% else %}
+ type="{{ media.media_manager['default_webm_type'] }}"
+ {% endif %} />
+ <div class="no_html5">
+ {%- trans -%}Sorry, this video will not work because
+ your web browser does not support HTML5
+ video.{%- endtrans -%}<br/>
+ {%- trans -%}You can get a modern web browser that
+ can play this video at <a href="http://getfirefox.com">
+ http://getfirefox.com</a>!{%- endtrans -%}
+ </div>
+ </video>
+{% endblock %}
+
+{% block mediagoblin_sidebar %}
+ <h3>{% trans %}Download{% endtrans %}</h3>
+ <ul>
+ {% if 'original' in media.media_files %}
+ <li>
+ <a href="{{ request.app.public_store.file_url(
+ media.media_files.original) }}">
+ {%- trans %}Original file{% endtrans -%}
+ </a>
+ </li>
+ {% endif %}
+ {% if 'webm_640' in media.media_files %}
+ <li>
+ <a href="{{ request.app.public_store.file_url(
+ media.media_files.webm_640) }}">
+ {%- trans %}WebM file (640p; VP8/Vorbis){% endtrans -%}
+ </a>
+ </li>
+ {% endif %}
+ </ul>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/root.html b/mediagoblin/templates/mediagoblin/root.html
new file mode 100644
index 00000000..15d53af1
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/root.html
@@ -0,0 +1,38 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% from "mediagoblin/utils/object_gallery.html" import object_gallery %}
+
+{% set feed_url = request.urlgen('mediagoblin.listings.atom_feed') %}
+
+{% block mediagoblin_head -%}
+ {% set feed_url = request.urlgen('mediagoblin.listings.atom_feed') -%}
+ <link rel="alternate" type="application/atom+xml" href="{{ feed_url }}">
+{%- endblock mediagoblin_head %}
+
+{% block mediagoblin_content %}
+ {% include "mediagoblin/bits/frontpage_welcome.html" %}
+
+ <h2>{% trans %}Most recent media{% endtrans %}</h2>
+ {{ object_gallery(request, media_entries, pagination) }}
+
+ {#- Need to set feed_url within this block so template can use it. -#}
+ {%- set feed_url = feed_url -%}
+ {%- include "mediagoblin/utils/feed_link.html" -%}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/submit/collection.html b/mediagoblin/templates/mediagoblin/submit/collection.html
new file mode 100644
index 00000000..4e2bc17d
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/submit/collection.html
@@ -0,0 +1,34 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.submit.collection') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box_xl">
+ <h1>{% trans %}Add a collection{% endtrans %}</h1>
+ {{ wtforms_util.render_divs(submit_form) }}
+ <div class="form_submit_buttons">
+ {{ csrf_token }}
+ <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form" />
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/submit/start.html b/mediagoblin/templates/mediagoblin/submit/start.html
new file mode 100644
index 00000000..aa390f56
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/submit/start.html
@@ -0,0 +1,38 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block title -%}
+ {% trans %}Add your media{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.submit.start') }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box_xl">
+ <h1>{% trans %}Add your media{% endtrans %}</h1>
+ {{ wtforms_util.render_divs(submit_form) }}
+ <div class="form_submit_buttons">
+ {{ csrf_token }}
+ <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form" />
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/test_submit.html b/mediagoblin/templates/mediagoblin/test_submit.html
new file mode 100644
index 00000000..0771a0c7
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/test_submit.html
@@ -0,0 +1,34 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+<html>
+ <body>
+ <form action="{{ request.urlgen('test_submit') }}" method="POST"
+ enctype="multipart/form-data">
+ <table>
+ {{ wtforms_util.render_table(image_form) }}
+ <tr>
+ <td></td>
+ <td><input type="submit" value="submit" class="button_form" /></td>
+ {{ csrf_token }}
+ </tr>
+ </table>
+ </form>
+ </body>
+</html>
diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection.html b/mediagoblin/templates/mediagoblin/user_pages/collection.html
new file mode 100644
index 00000000..5a7baadd
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/collection.html
@@ -0,0 +1,72 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% from "mediagoblin/utils/collection_gallery.html" import collection_gallery %}
+
+{% block mediagoblin_head %}
+ <link rel="alternate" type="application/atom+xml"
+ href="{{ request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ user=user.username) }}">
+{% endblock mediagoblin_head %}
+
+{% block title %}
+ {%- trans username=user.username,
+ collection_title=collection.title
+ -%}
+ {{ collection_title }} ({{ username }}'s collection)
+ {%- endtrans %} &mdash; {{ super() }}
+{% endblock %}
+
+{% block mediagoblin_content -%}
+ <h1>
+ {%- trans username=user.username,
+ user_url=request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username),
+ collection_title=collection.title -%}
+ {{ collection_title }} by <a href="{{ user_url }}">{{ username }}</a>
+ {%- endtrans %}
+ </h1>
+ {% if request.user and (collection.creator == request.user.id or
+ request.user.is_admin) %}
+ {% set edit_url = request.urlgen('mediagoblin.edit.edit_collection',
+ user=collection.get_creator.username,
+ collection=collection.slug) %}
+ <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a>
+ {% set delete_url = request.urlgen('mediagoblin.user_pages.collection_confirm_delete',
+ user=collection.get_creator.username,
+ collection=collection.slug) %}
+ <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
+ {% endif %}
+
+ <p>
+ {% autoescape False %}
+ {{ collection.description_html }}
+ {% endautoescape %}
+ </p>
+
+ {{ collection_gallery(request, collection_items, pagination) }}
+
+ {% set feed_url = request.urlgen('mediagoblin.user_pages.collection_atom_feed',
+ user=user.username,
+ collection=collection.slug ) %}
+ {% include "mediagoblin/utils/feed_link.html" %}
+
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html
new file mode 100644
index 00000000..694eb979
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html
@@ -0,0 +1,53 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.user_pages.collection_confirm_delete',
+ user=collection.get_creator.username,
+ collection=collection.slug) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans title=collection.title -%}
+ Really delete {{ title }}?
+ {%- endtrans %}
+ </h1>
+
+ <br />
+
+ <p class="delete_checkbox_box">
+ {{ form.confirm }}
+ {{ wtforms_util.render_label(form.confirm) }}
+ </p>
+
+ <div class="form_submit_buttons">
+ {# TODO: This isn't a button really... might do unexpected things :) #}
+ <a class="button_action" href="
+ {{- collection.url_for_self(request.urlgen) }}">
+ {%- trans %}Cancel{% endtrans -%}
+ </a>
+ <input type="submit" value="{% trans %}Delete permanently{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html
new file mode 100644
index 00000000..dc31d90f
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html
@@ -0,0 +1,59 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.user_pages.collection_item_confirm_remove',
+ user=collection_item.in_collection.get_creator.username,
+ collection=collection_item.in_collection.slug,
+ collection_item=collection_item.id) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans media_title=collection_item.get_media_entry.title,
+ collection_title=collection_item.in_collection.title -%}
+ Really remove {{ media_title }} from {{ collection_title }}?
+ {%- endtrans %}
+ </h1>
+
+ <div style="text-align: center;" >
+ <img src="{{ collection_item.get_media_entry.thumb_url }}" />
+ </div>
+
+ <br />
+
+ <p class="delete_checkbox_box">
+ {{ form.confirm }}
+ {{ wtforms_util.render_label(form.confirm) }}
+ </p>
+
+ <div class="form_submit_buttons">
+ {# TODO: This isn't a button really... might do unexpected things :) #}
+ <a class="button_action" href="
+ {{- collection_item.in_collection.url_for_self(request.urlgen) }}">
+ {%- trans %}Cancel{% endtrans -%}
+ </a>
+ <input type="submit" value="{% trans %}Remove{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_list.html b/mediagoblin/templates/mediagoblin/user_pages/collection_list.html
new file mode 100644
index 00000000..8ac0b988
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/collection_list.html
@@ -0,0 +1,56 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{%- extends "mediagoblin/base.html" %}
+
+{% block title %}
+ {%- trans username=user.username -%}
+ {{ username }}'s collections
+ {%- endtrans %} &mdash; {{ super() }}
+{% endblock %}
+
+{% block mediagoblin_content -%}
+ <h1>
+ {%- trans username=user.username,
+ user_url=request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username) -%}
+ <a href="{{ user_url }}">{{ username }}</a>'s collections
+ {%- endtrans %}
+ </h1>
+
+ {% if request.user %}
+ {% if request.user.status == 'active' %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.submit.collection',
+ user=user.username) }}">
+ {%- trans %}Create new collection{% endtrans -%}
+ </a>
+ </p>
+ {% endif %}
+ {% endif %}
+
+ <ul>
+ {% for coll in collections %}
+ {%- set coll_url = coll.url_for_self(request.urlgen) %}
+ <li>
+ <a href="{{ coll_url }}">{{ coll.title }}</a>
+ </li>
+ {% endfor %}
+ </ul>
+
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/comment_email.txt b/mediagoblin/templates/mediagoblin/user_pages/comment_email.txt
new file mode 100644
index 00000000..1155ac1e
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/comment_email.txt
@@ -0,0 +1,26 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+
+{% trans username=username, comment_author=comment_author, instance_name=app_config.html_title -%}
+
+Hi {{ username }},
+{{ comment_author }} commented on your post ({{ comment_url }}) at {{ instance_name }}
+{% endtrans %}
+{{ comment_content }}
+
+{{ app_config.html_title }}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/gallery.html b/mediagoblin/templates/mediagoblin/user_pages/gallery.html
new file mode 100644
index 00000000..f23bb156
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/gallery.html
@@ -0,0 +1,63 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% from "mediagoblin/utils/object_gallery.html" import object_gallery %}
+
+{% block mediagoblin_head %}
+ <link rel="alternate" type="application/atom+xml"
+ href="{{ request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ user=user.username) }}">
+{% endblock mediagoblin_head %}
+
+{% block title %}
+ {%- trans username=user.username -%}
+ {{ username }}'s media
+ {%- endtrans %} &mdash; {{ super() }}
+{% endblock %}
+
+{% block mediagoblin_content -%}
+ <h1>
+ {% if tag %}
+ {%- trans username=user.username,
+ user_url=request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username),
+ tag_url=request.urlgen(
+ 'mediagoblin.listings.tags_listing',
+ tag=tag) -%}
+ <a href="{{ user_url }}">{{ username }}</a>'s media with tag <a href="{{ tag_url }}">{{ tag }}</a>
+ {%- endtrans %}
+ {% else %}
+ {%- trans username=user.username,
+ user_url=request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username) -%}
+ <a href="{{ user_url }}">{{ username }}</a>'s media
+ {%- endtrans %}
+ {% endif %}
+ </h1>
+
+ {{ object_gallery(request, media_entries, pagination) }}
+
+ {% set feed_url = request.urlgen('mediagoblin.user_pages.atom_feed',
+ user=user.username) %}
+ {% include "mediagoblin/utils/feed_link.html" %}
+
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html
new file mode 100644
index 00000000..fb892fd7
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/media.html
@@ -0,0 +1,205 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{%- extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+{% from "mediagoblin/utils/pagination.html" import render_pagination %}
+
+{% block title %}{{ media.title }} &mdash; {{ super() }}{% endblock %}
+
+{% block mediagoblin_head %}
+<!--[if lte IE 8]><link rel="stylesheet"
+ href="{{ request.staticdirect('/extlib/leaflet/leaflet.ie.css') }}" /><![endif]-->
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/comment_show.js') }}"></script>
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/keyboard_navigation.js') }}"></script>
+
+ {% template_hook("media_head") %}
+{% endblock mediagoblin_head %}
+
+{% block mediagoblin_content %}
+ <p class="context">
+ {%- trans user_url=request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=media.get_uploader.username),
+ username=media.get_uploader.username -%}
+ ❖ Browsing media by <a href="{{user_url}}">{{username}}</a>
+ {%- endtrans -%}
+ </p>
+ {% include "mediagoblin/utils/prev_next.html" %}
+ <div class="media_pane">
+ <div class="media_image_container">
+ {% block mediagoblin_media %}
+ {% set display_media = request.app.public_store.file_url(
+ media.get_display_media()[1]) %}
+ {# if there's a medium file size, that means the medium size
+ # isn't the original... so link to the original!
+ #}
+ {% if media.media_files.has_key('medium') %}
+ <a href="{{ request.app.public_store.file_url(
+ media.media_files['original']) }}">
+ <img class="media_image"
+ src="{{ display_media }}"
+ alt="{% trans media_title=media.title -%}
+ Image for {{ media_title }}{% endtrans %}" />
+ </a>
+ {% else %}
+ <img class="media_image"
+ src="{{ display_media }}"
+ alt="{% trans media_title=media.title -%}
+ Image for {{ media_title }}{% endtrans %}" />
+ {% endif %}
+ {% endblock %}
+ </div>
+ <h2 class="media_title">
+ {{ media.title }}
+ </h2>
+ {% if request.user and
+ (media.uploader == request.user.id or
+ request.user.is_admin) %}
+ {% set edit_url = request.urlgen('mediagoblin.edit.edit_media',
+ user= media.get_uploader.username,
+ media_id=media.id) %}
+ <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a>
+ {% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete',
+ user= media.get_uploader.username,
+ media_id=media.id) %}
+ <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
+ {% endif %}
+ {% autoescape False %}
+ <p>{{ media.description_html }}</p>
+ {% endautoescape %}
+ {% if comments %}
+ {% if app_config['allow_comments'] %}
+ <a
+ {% if not request.user %}
+ href="{{ request.urlgen('mediagoblin.auth.login') }}"
+ {% endif %}
+ class="button_action" id="button_addcomment" title="Add a comment">
+ {% trans %}Add a comment{% endtrans %}
+ </a>
+ {% endif %}
+ {% if request.user %}
+ <form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment',
+ user= media.get_uploader.username,
+ media_id=media.id) }}" method="POST" id="form_comment">
+ {{ wtforms_util.render_divs(comment_form) }}
+ <div class="form_submit_buttons">
+ <input type="submit" value="{% trans %}Add this comment{% endtrans %}" class="button_action" />
+ {{ csrf_token }}
+ </div>
+ </form>
+ {% endif %}
+ <ul style="list-style:none">
+ {% for comment in comments %}
+ {% set comment_author = comment.get_author %}
+ <li id="comment-{{ comment.id }}"
+ {%- if pagination.active_id == comment.id %}
+ class="comment_wrapper comment_active">
+ <a name="comment" id="comment"></a>
+ {%- else %}
+ class="comment_wrapper">
+ {%- endif %}
+ <div class="comment_author">
+ <img src="{{ request.staticdirect('/images/icon_comment.png') }}" />
+ <a href="{{ request.urlgen('mediagoblin.user_pages.user_home',
+ user=comment_author.username) }}"
+ class="comment_authorlink">
+ {{- comment_author.username -}}
+ </a>
+ <a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment',
+ comment=comment.id,
+ user=media.get_uploader.username,
+ media=media.slug_or_id) }}#comment"
+ class="comment_whenlink">
+ <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'>
+ {%- trans formatted_time=timesince(comment.created) -%}
+ {{ formatted_time }} ago
+ {%- endtrans -%}
+ </span></a>:
+ </div>
+ <div class="comment_content">
+ {% autoescape False -%}
+ {{ comment.content_html }}
+ {%- endautoescape %}
+ </div>
+ </li>
+ {% endfor %}
+ </ul>
+ {{ render_pagination(request, pagination,
+ media.url_for_self(request.urlgen)) }}
+ {% endif %}
+ </div>
+ <div class="media_sidebar">
+ <h3>{% trans %}Added{% endtrans %}</h3>
+ <p><span title="{{ media.created.strftime("%I:%M%p %Y-%m-%d") }}">
+ {%- trans formatted_time=timesince(media.created) -%}
+ {{ formatted_time }} ago
+ {%- endtrans -%}
+ </span></p>
+
+ {% block mediagoblin_after_added_sidebar %}
+ {% endblock %}
+
+ {% if media.tags %}
+ {% include "mediagoblin/utils/tags.html" %}
+ {% endif %}
+
+ {% include "mediagoblin/utils/collections.html" %}
+
+ {% include "mediagoblin/utils/license.html" %}
+
+ {% include "mediagoblin/utils/exif.html" %}
+
+ {%- if media.attachment_files|count %}
+ <h3>{% trans %}Attachments{% endtrans %}</h3>
+ <ul>
+ {%- for attachment in media.attachment_files %}
+ <li>
+ <a href="{{ request.app.public_store.file_url(attachment.filepath) }}">
+ {{- attachment.name -}}
+ </a>
+ </li>
+ {%- endfor %}
+ </ul>
+ {%- endif %}
+ {%- if app_config['allow_attachments']
+ and request.user
+ and (media.uploader == request.user.id
+ or request.user.is_admin) %}
+ {%- if not media.attachment_files|count %}
+ <h3>{% trans %}Attachments{% endtrans %}</h3>
+ {%- endif %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.edit.attachments',
+ user=media.get_uploader.username,
+ media_id=media.id) }}">
+ {%- trans %}Add attachment{% endtrans -%}
+ </a>
+ </p>
+ {%- endif %}
+
+ {% template_hook("media_sideinfo") %}
+
+ {% block mediagoblin_sidebar %}
+ {% endblock %}
+
+ </div>
+ <div class="clear"></div>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_collect.html b/mediagoblin/templates/mediagoblin/user_pages/media_collect.html
new file mode 100644
index 00000000..b4c9671c
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/media_collect.html
@@ -0,0 +1,73 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_head %}
+ <script type="text/javascript"
+ src="{{ request.staticdirect('/js/collection_form_show.js') }}"></script>
+{% endblock %}
+
+{% block title -%}
+ {% trans media_title=media.title -%}
+ Add “{{ media_title }}” to a collection
+ {%- endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+ <form action="{{ request.urlgen('mediagoblin.user_pages.media_collect',
+ user=media.get_uploader.username,
+ media_id=media.id) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans media_title=media.title -%}
+ Add “{{ media_title }}” to a collection
+ {%- endtrans -%}
+ </h1>
+
+ <div style="text-align: center;" >
+ <img src="{{ media.thumb_url }}" />
+ </div>
+
+ <br />
+
+ {{- wtforms_util.render_label_p(form.collection) }}
+ <div class="form_field_input">
+ {{ form.collection }}
+ <a class="button_action" id="button_addcollection">{% trans %}+{% endtrans %}</a>
+ </div>
+
+ <div id="new_collection" class="subform">
+ <h3>{% trans %}Add a new collection{% endtrans %}</h3>
+
+ {{- wtforms_util.render_field_div(form.collection_title) }}
+ {{- wtforms_util.render_field_div(form.collection_description) }}
+ </div>
+ {{- wtforms_util.render_field_div(form.note) }}
+
+ <div class="form_submit_buttons">
+ {# TODO: This isn't a button really... might do unexpected things :) #}
+ <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a>
+ <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html
new file mode 100644
index 00000000..1d7dcc17
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html
@@ -0,0 +1,54 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %}
+
+{% block mediagoblin_content %}
+
+ <form action="{{ request.urlgen('mediagoblin.user_pages.media_confirm_delete',
+ user=media.get_uploader.username,
+ media_id=media.id) }}"
+ method="POST" enctype="multipart/form-data">
+ <div class="form_box">
+ <h1>
+ {%- trans title=media.title -%}
+ Really delete {{ title }}?
+ {%- endtrans %}
+ </h1>
+
+ <div style="text-align: center;" >
+ <img src="{{ media.thumb_url }}" />
+ </div>
+
+ <br />
+
+ <p class="delete_checkbox_box">
+ {{ form.confirm }}
+ {{ wtforms_util.render_label(form.confirm) }}
+ </p>
+
+ <div class="form_submit_buttons">
+ {# TODO: This isn't a button really... might do unexpected things :) #}
+ <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a>
+ <input type="submit" value="{% trans %}Delete permanently{% endtrans %}" class="button_form" />
+ {{ csrf_token }}
+ </div>
+ </div>
+ </form>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html
new file mode 100644
index 00000000..2a449d45
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html
@@ -0,0 +1,109 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% block title -%}
+ {% trans %}Media processing panel{% endtrans %} &mdash; {{ super() }}
+{%- endblock %}
+
+{% block mediagoblin_content %}
+
+<h1>{% trans %}Media processing panel{% endtrans %}</h1>
+
+<p>
+ {% trans %}You can track the state of media being processed for your gallery here.{% endtrans %}
+</p>
+
+<h2>{% trans %}Media in-processing{% endtrans %}</h2>
+
+{% if processing_entries.count() %}
+ <table class="media_panel processing">
+ <tr>
+ <th>ID</th>
+ <th>Title</th>
+ <th>When submitted</th>
+ <th>Transcoding progress</th>
+ </tr>
+ {% for media_entry in processing_entries %}
+ <tr>
+ <td>{{ media_entry.id }}</td>
+ <td>{{ media_entry.title }}</td>
+ <td>{{ media_entry.created.strftime("%F %R") }}</td>
+ {% if media_entry.transcoding_progress %}
+ <td>{{ media_entry.transcoding_progress }}%</td>
+ {% else %}
+ <td>Unknown</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+{% else %}
+ <p><em>{% trans %}No media in-processing{% endtrans %}</em></p>
+{% endif %}
+
+<h2>{% trans %}These uploads failed to process:{% endtrans %}</h2>
+{% if failed_entries.count() %}
+
+ <table class="media_panel failed">
+ <tr>
+ <th>ID</th>
+ <th>Title</th>
+ <th>When submitted</th>
+ <th>Reason for failure</th>
+ <th>Failure metadata</th>
+ </tr>
+ {% for media_entry in failed_entries %}
+ <tr>
+ <td>{{ media_entry.id }}</td>
+ <td>{{ media_entry.title }}</td>
+ <td>{{ media_entry.created.strftime("%F %R") }}</td>
+ {% if media_entry.get_fail_exception() %}
+ <td>{{ media_entry.get_fail_exception().general_message }}</td>
+ <td>{{ media_entry.fail_metadata }}</td>
+ {% else %}
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+{% else %}
+ <p><em>{% trans %}No failed entries!{% endtrans %}</em></p>
+{% endif %}
+
+<h2>{% trans %}Your last 10 successful uploads{% endtrans %}</h2>
+{% if processed_entries.count() %}
+
+ <table class="media_panel processed">
+ <tr>
+ <th>ID</th>
+ <th>Title</th>
+ <th>Submitted</th>
+ </tr>
+ {% for entry in processed_entries %}
+ <tr>
+ <td>{{ entry.id }}</td>
+ <td><a href="{{ entry.url_for_self(request.urlgen) }}">{{ entry.title }}</a></td>
+ <td>{{ entry.created.strftime("%F %R") }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+{% else %}
+ <p><em>{% trans %}No processed entries, yet!{% endtrans %}</em></p>
+{% endif %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/user_pages/user.html b/mediagoblin/templates/mediagoblin/user_pages/user.html
new file mode 100644
index 00000000..71acd66c
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/user_pages/user.html
@@ -0,0 +1,171 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+{% extends "mediagoblin/base.html" %}
+
+{% from "mediagoblin/utils/object_gallery.html" import object_gallery %}
+
+{% block mediagoblin_head %}
+ <link rel="alternate" type="application/atom+xml"
+ href="{{ request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ user=user.username) }}">
+{% endblock mediagoblin_head %}
+
+{% block title %}
+ {%- if user -%}
+ {%- trans username=user.username -%}
+ {{ username }}'s profile
+ {%- endtrans %} &mdash; {{ super() }}
+ {%- else -%}
+ {{ super() }}
+ {%- endif -%}
+{% endblock %}
+
+
+{% block mediagoblin_content -%}
+ {# If no user... #}
+ {% if not user %}
+ <p>{% trans %}Sorry, no such user found.{% endtrans %}</p>
+
+ {# User exists, but needs verification #}
+ {% elif user.status == "needs_email_verification" %}
+ {% if user == request.user %}
+ {# this should only be visible when you are this user #}
+ <div class="form_box">
+ <h1>{% trans %}Email verification needed{% endtrans %}</h1>
+
+ <p>
+ {% trans -%}
+ Almost done! Your account still needs to be activated.
+ {%- endtrans %}
+ </p>
+ <p>
+ {% trans -%}
+ An email should arrive in a few moments with instructions on how to do so.
+ {%- endtrans %}
+ </p>
+ <p>{% trans %}In case it doesn't:{% endtrans %}</p>
+
+ <a href="{{ request.urlgen('mediagoblin.auth.resend_verification') }}"
+ class="button_action_highlight">{% trans %}Resend verification email{% endtrans %}</a>
+ </div>
+ {% else %}
+ {# if the user is not you, but still needs to verify their email #}
+ <div class="form_box">
+ <h1>{% trans %}Email verification needed{% endtrans %}</h1>
+
+ <p>
+ {% trans -%}
+ Someone has registered an account with this username, but it still has to be activated.
+ {%- endtrans %}
+ </p>
+
+ <p>
+ {% trans login_url=request.urlgen('mediagoblin.auth.login') -%}
+ If you are that person but you've lost your verification email, you can <a href="{{ login_url }}">log in</a> and resend it.
+ {%- endtrans %}
+ </p>
+ </div>
+ {% endif %}
+
+ {# Active(?) (or at least verified at some point) user, horray! #}
+ {% else %}
+ <h1>
+ {%- trans username=user.username %}{{ username }}'s profile{% endtrans -%}
+ </h1>
+
+ {% if not user.url and not user.bio %}
+ {% if request.user and (request.user.id == user.id) %}
+ <div class="profile_sidebar empty_space">
+ <p>
+ {% trans %}Here's a spot to tell others about yourself.{% endtrans %}
+ </p>
+ <a href="{{ request.urlgen('mediagoblin.edit.profile',
+ user=user.username) }}" class="button_action">
+ {%- trans %}Edit profile{% endtrans -%}
+ </a>
+ {% else %}
+ <div class="profile_sidebar empty_space">
+ <p>
+ {% trans -%}
+ This user hasn't filled in their profile (yet).
+ {%- endtrans %}
+ </p>
+ {% endif %}
+ {% else %}
+ <div class="profile_sidebar">
+ {% include "mediagoblin/utils/profile.html" %}
+ {% if request.user and
+ (request.user.id == user.id or request.user.is_admin) %}
+ <a href="{{ request.urlgen('mediagoblin.edit.profile',
+ user=user.username) }}">
+ {%- trans %}Edit profile{% endtrans -%}
+ </a>
+ {% endif %}
+ {% endif %}
+ <p>
+ <a href="{{ request.urlgen('mediagoblin.user_pages.collection_list',
+ user=user.username) }}">
+ {%- trans %}Browse collections{% endtrans -%}
+ </a>
+ </p>
+ </div>
+
+ {% if media_entries.count() %}
+ <div class="profile_showcase">
+ {{ object_gallery(request, media_entries, pagination,
+ pagination_base_url=user_gallery_url, col_number=3) }}
+ {% include "mediagoblin/utils/object_gallery.html" %}
+ <div class="clear"></div>
+ <p>
+ <a href="{{ user_gallery_url }}">
+ {% trans username=user.username -%}
+ View all of {{ username }}'s media{% endtrans -%}
+ </a>
+ </p>
+ {% set feed_url = request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ user=user.username) %}
+ {% include "mediagoblin/utils/feed_link.html" %}
+ </div>
+ {% else %}
+ {% if request.user and (request.user.id == user.id) %}
+ <div class="profile_showcase empty_space">
+ <p>
+ {% trans -%}
+ This is where your media will appear, but you don't seem to have added anything yet.
+ {%- endtrans %}
+ </p>
+ <a class="button_action"
+ href="{{ request.urlgen('mediagoblin.submit.start') }}">
+ {%- trans %}Add media{% endtrans -%}
+ </a>
+ </div>
+ {% else %}
+ <div class="profile_showcase empty_space">
+ <p>
+ {% trans -%}
+ There doesn't seem to be any media here yet...
+ {%- endtrans %}
+ </p>
+ </div>
+ {% endif %}
+ {% endif %}
+ <div class="clear"></div>
+ {% endif %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html
new file mode 100644
index 00000000..dcc59244
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html
@@ -0,0 +1,90 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% from "mediagoblin/utils/pagination.html" import render_pagination %}
+
+{% macro media_grid(request, collection_items, col_number=5) %}
+ <table class="thumb_gallery">
+ {% for row in collection_items|batch(col_number) %}
+ <tr class="thumb_row
+ {%- if loop.first %} thumb_row_first
+ {%- elif loop.last %} thumb_row_last{% endif %}">
+ {% for item in row %}
+ {% set media_entry = item.get_media_entry %}
+ {% set entry_url = media_entry.url_for_self(request.urlgen) %}
+ <td class="media_thumbnail thumb_entry
+ {%- if loop.first %} thumb_entry_first
+ {%- elif loop.last %} thumb_entry_last{% endif %}">
+ <a href="{{ entry_url }}">
+ <img src="{{ media_entry.thumb_url }}" />
+ </a>
+
+ {% if item.note %}
+ <a href="{{ entry_url }}">{{ item.note }}</a>
+ {% endif %}
+ {% if request.user and
+ (item.in_collection.creator == request.user.id or
+ request.user.is_admin) %}
+ {%- set remove_url=request.urlgen(
+ 'mediagoblin.user_pages.collection_item_confirm_remove',
+ user=item.in_collection.get_creator.username,
+ collection=item.in_collection.slug,
+ collection_item=item.id) -%}
+ <a href="{{ remove_url }}" class="remove">
+ {%- trans %}(remove){% endtrans -%}
+ </a>
+ {% endif %}
+ </td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </table>
+{%- endmacro %}
+
+{#
+ Render a media gallery with pagination.
+
+ Args:
+ - request: Request
+ - collection_items: cursor of collection items
+ - pagination: Paginator object
+ - pagination_base_url: If you want the pagination to point to a
+ different URL, point it here
+ - col_number: How many columns per row (default 5)
+#}
+{% macro collection_gallery(request, collection_items, pagination,
+ pagination_base_url=None, col_number=5) %}
+ {% if collection_items and collection_items.count() %}
+ {{ media_grid(request, collection_items, col_number=col_number) }}
+ <div class="clear"></div>
+ {% if pagination_base_url %}
+ {# different url, so set that and don't keep the get params #}
+ {{ render_pagination(request, pagination, pagination_base_url, False) }}
+ {% else %}
+ {{ render_pagination(request, pagination) }}
+ {% endif %}
+ {% else %}
+ <p>
+ <i>
+ {%- trans -%}
+ There doesn't seem to be any media here yet...
+ {%- endtrans -%}
+ </i>
+ </p>
+ {% endif %}
+{% endmacro %}
diff --git a/mediagoblin/templates/mediagoblin/utils/collections.html b/mediagoblin/templates/mediagoblin/utils/collections.html
new file mode 100644
index 00000000..69738e26
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/collections.html
@@ -0,0 +1,44 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block collections_content -%}
+ {% if media.collections %}
+ <h3>{% trans %}Collected in{% endtrans %}</h3>
+ <p>
+ {%- for collection in media.collections %}
+ {%- if not loop.first %}
+ &middot;
+ {%- endif %}
+ <a href="{{ collection.url_for_self(request.urlgen) }}">
+ {{- collection.title }} (
+ {{- collection.get_creator.username -}}
+ )</a>
+ {%- endfor %}
+ </p>
+ {%- endif %}
+ {%- if request.user %}
+ <p>
+ <a type="submit" href="{{ request.urlgen('mediagoblin.user_pages.media_collect',
+ user=media.get_uploader.username,
+ media_id=media.id) }}"
+ class="button_action">
+ {% trans %}Add to a collection{% endtrans %}
+ </a>
+ </p>
+ {%- endif %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/utils/exif.html b/mediagoblin/templates/mediagoblin/utils/exif.html
new file mode 100644
index 00000000..b62208e1
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/exif.html
@@ -0,0 +1,67 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block exif_content %}
+<noscript>
+ <style type="text/css">
+ #exif_additional_info {
+ display: block;
+ }
+ </style>
+</noscript>
+<div id="exif_content">
+ {% if app_config['exif_visible']
+ and media.media_data
+ and media.media_data.exif_all is defined
+ and media.media_data.exif_all %}
+ <h3>Camera Information</h3>
+ <table id="exif_camera_information">
+ <tbody>
+ {% for label, value in media.exif_display_data_short().iteritems() %}
+ <tr>
+ <td class="col1">{{ label }}</td>
+ <td>{{ value }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ <h3 id="exif_additional_info_button" class="button_action">
+ Additional Information
+ </h3>
+ <div id="exif_additional_info">
+ <table class="exif_info">
+ {% for key, tag in media.exif_display_iter() %}
+ <tr>
+ <td class="col1">{{ key }}</td>
+ <td>{{ tag.printable }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ {% endif %}
+<script type="text/javascript">
+$(document).ready(function(){
+
+$("#exif_additional_info_button").click(function(){
+ $("#exif_additional_info").slideToggle("slow");
+});
+
+});
+</script>
+</div> <!-- end exif_content div -->
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/utils/feed_link.html b/mediagoblin/templates/mediagoblin/utils/feed_link.html
new file mode 100644
index 00000000..6a41cef5
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/feed_link.html
@@ -0,0 +1,23 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+<a href="{{ feed_url }}">
+ <img src="{{ request.staticdirect('/images/icon_feed.png') }}"
+ class="media_icon" alt="{% trans %}feed icon{% endtrans %}" />
+</a>
+<a href="{{ feed_url }}">{%- trans %}Atom feed{% endtrans -%}</a>
diff --git a/mediagoblin/templates/mediagoblin/utils/license.html b/mediagoblin/templates/mediagoblin/utils/license.html
new file mode 100644
index 00000000..9dad7419
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/license.html
@@ -0,0 +1,28 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block license_content -%}
+ <h3>{% trans %}License{% endtrans %}</h3>
+ <p>
+ {% if media.license %}
+ <a href="{{ media.license }}">{{ media.get_license_data().abbreviation }}</a>
+ {% else %}
+ {% trans %}All rights reserved{% endtrans %}
+ {% endif %}
+ </p>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/utils/messages.html b/mediagoblin/templates/mediagoblin/utils/messages.html
new file mode 100644
index 00000000..cb45f59a
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/messages.html
@@ -0,0 +1,28 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{# Display any queued messages #}
+{% set messages = fetch_messages(request) %}
+{% if messages %}
+ <ul class="mediagoblin_messages">
+ {% for msg in messages %}
+ <li class="message_{{ msg.level }}">{{ msg.text }}</li>
+ {% endfor %}
+ </ul>
+{% endif %}
+
diff --git a/mediagoblin/templates/mediagoblin/utils/object_gallery.html b/mediagoblin/templates/mediagoblin/utils/object_gallery.html
new file mode 100644
index 00000000..d328b552
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/object_gallery.html
@@ -0,0 +1,76 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% from "mediagoblin/utils/pagination.html" import render_pagination %}
+
+{% macro media_grid(request, media_entries, col_number=5) %}
+ <table class="thumb_gallery">
+ {% for row in media_entries|batch(col_number) %}
+ <tr class="thumb_row
+ {%- if loop.first %} thumb_row_first
+ {%- elif loop.last %} thumb_row_last{% endif %}">
+ {% for entry in row %}
+ {% set entry_url = entry.url_for_self(request.urlgen) %}
+ <td class="media_thumbnail thumb_entry
+ {%- if loop.first %} thumb_entry_first
+ {%- elif loop.last %} thumb_entry_last{% endif %}">
+ <a href="{{ entry_url }}">
+ <img src="{{ entry.thumb_url }}" />
+ </a>
+ {% if entry.title %}
+ <a class="thumb_entry_title" href="{{ entry_url }}">{{ entry.title }}</a>
+ {% endif %}
+ </td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </table>
+{%- endmacro %}
+
+{#
+ Render a media gallery with pagination.
+
+ Args:
+ - request: Request
+ - media_entries: db cursor of media entries
+ - pagination: Paginator object
+ - pagination_base_url: If you want the pagination to point to a
+ different URL, point it here
+ - col_number: How many columns per row (default 5)
+#}
+{% macro object_gallery(request, media_entries, pagination,
+ pagination_base_url=None, col_number=5) %}
+ {% if media_entries and media_entries.count() %}
+ {{ media_grid(request, media_entries, col_number=col_number) }}
+ <div class="clear"></div>
+ {% if pagination_base_url %}
+ {# different url, so set that and don't keep the get params #}
+ {{ render_pagination(request, pagination, pagination_base_url, False) }}
+ {% else %}
+ {{ render_pagination(request, pagination) }}
+ {% endif %}
+ {% else %}
+ <p>
+ <i>
+ {%- trans -%}
+ There doesn't seem to be any media here yet...
+ {%- endtrans -%}
+ </i>
+ </p>
+ {% endif %}
+{% endmacro %}
diff --git a/mediagoblin/templates/mediagoblin/utils/pagination.html b/mediagoblin/templates/mediagoblin/utils/pagination.html
new file mode 100644
index 00000000..2ac990ae
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/pagination.html
@@ -0,0 +1,65 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% macro render_pagination(request, pagination,
+ base_url=None, preserve_get_params=True) %}
+ {# only display if {{pagination}} is defined #}
+ {% if pagination and pagination.pages > 1 %}
+ {% if not base_url %}
+ {% set base_url = request.full_path %}
+ {% endif %}
+
+ {% if preserve_get_params %}
+ {% set get_params = request.GET %}
+ {% else %}
+ {% set get_params = {} %}
+ {% endif %}
+
+ <div class="pagination">
+ <p>
+ {% if pagination.has_prev %}
+ {% set prev_url = pagination.get_page_url_explicit(
+ base_url, get_params,
+ pagination.page - 1) %}
+ <a href="{{ prev_url }}">{% trans %}← Newer{% endtrans %}</a>
+ {% endif %}
+ {% if pagination.has_next %}
+ {% set next_url = pagination.get_page_url_explicit(
+ base_url, get_params,
+ pagination.page + 1) %}
+ <a href="{{ next_url }}">{% trans %}Older →{% endtrans %}</a>
+ {% endif %}
+ <br />
+ {% trans %}Go to page:{% endtrans %}
+ {%- for page in pagination.iter_pages() %}
+ {% if page %}
+ {% if page != pagination.page %}
+ <a href="{{ pagination.get_page_url_explicit(
+ base_url, get_params,
+ page) }}">{{ page }}</a>
+ {% else %}
+ {{ page }}
+ {% endif %}
+ {% else %}
+ <span class="ellipsis">…</span>
+ {% endif %}
+ {%- endfor %}
+ </p>
+ </div>
+ {% endif %}
+{% endmacro %}
diff --git a/mediagoblin/templates/mediagoblin/utils/prev_next.html b/mediagoblin/templates/mediagoblin/utils/prev_next.html
new file mode 100644
index 00000000..9e262ed9
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/prev_next.html
@@ -0,0 +1,48 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{# Provide navigation links to neighboring media entries, if possible #}
+{% set prev_entry_url = media.url_to_prev(request.urlgen) %}
+{% set next_entry_url = media.url_to_next(request.urlgen) %}
+
+{% if prev_entry_url or next_entry_url %}
+ <div class="navigation">
+ {# There are no previous entries for the very first media entry #}
+ {% if prev_entry_url %}
+ <a class="navigation_button navigation_left" href="{{ prev_entry_url }}">
+ &larr; {% trans %}newer{% endtrans %}
+ </a>
+ {% else %}
+ {# This is the first entry. display greyed-out 'previous' image #}
+ <p class="navigation_button navigation_left">
+ &larr; {% trans %}newer{% endtrans %}
+ </p>
+ {% endif %}
+ {# Likewise, this could be the very last media entry #}
+ {% if next_entry_url %}
+ <a class="navigation_button navigation_right" href="{{ next_entry_url }}">
+ {% trans %}older{% endtrans %} &rarr;
+ </a>
+ {% else %}
+ {# This is the last entry. display greyed-out 'next' image #}
+ <p class="navigation_button navigation_right">
+ {% trans %}older{% endtrans %} &rarr;
+ </p>
+ {% endif %}
+ </div>
+{% endif %}
diff --git a/mediagoblin/templates/mediagoblin/utils/profile.html b/mediagoblin/templates/mediagoblin/utils/profile.html
new file mode 100644
index 00000000..7a3af01c
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/profile.html
@@ -0,0 +1,30 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block profile_content -%}
+ {% if user.bio %}
+ {% autoescape False %}
+ {{ user.bio_html }}
+ {% endautoescape %}
+ {% endif %}
+ {% if user.url %}
+ <p>
+ <a href="{{ user.url }}">{{ user.url }}</a>
+ </p>
+ {% endif %}
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/utils/tags.html b/mediagoblin/templates/mediagoblin/utils/tags.html
new file mode 100644
index 00000000..bb4bd1a5
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/tags.html
@@ -0,0 +1,46 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{% block tags_content -%}
+ <h3>{% trans %}Tagged with{% endtrans %}</h3>
+ <p>
+ {% for tag in media.tags %}
+ {% if loop.last %}
+ {# the 'and' should only appear if there is more than one tag #}
+ {% if media.tags|length > 1 %}
+ &middot;
+ {% endif %}
+ <a href="{{ request.urlgen(
+ 'mediagoblin.user_pages.user_tag_gallery',
+ tag=tag['slug'],
+ user=media.get_uploader.username) }}">{{ tag['name'] }}</a>
+ {% elif loop.revindex == 2 %}
+ <a href="{{ request.urlgen(
+ 'mediagoblin.user_pages.user_tag_gallery',
+ tag=tag['slug'],
+ user=media.get_uploader.username) }}">{{ tag['name'] }}</a>
+ {% else %}
+ <a href="{{ request.urlgen(
+ 'mediagoblin.user_pages.user_tag_gallery',
+ tag=tag['slug'],
+ user=media.get_uploader.username) }}">{{ tag['name'] }}</a>
+ &middot;
+ {% endif %}
+ {% endfor %}
+ </p>
+{% endblock %}
diff --git a/mediagoblin/templates/mediagoblin/utils/wtforms.html b/mediagoblin/templates/mediagoblin/utils/wtforms.html
new file mode 100644
index 00000000..be6976c2
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/utils/wtforms.html
@@ -0,0 +1,76 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+{# Render the label for a field #}
+{% macro render_label(field) %}
+ {%- if field.label.text -%}
+ <label for="{{ field.label.field_id }}">{{ field.label.text }}</label>
+ {%- endif -%}
+{%- endmacro %}
+
+{# Render the label in a <p> for a field #}
+{% macro render_label_p(field) %}
+ {%- if field.label.text %}
+ <p class="form_field_label">
+ {{- render_label(field) -}}
+ </p>
+ {%- endif %}
+{%- endmacro %}
+
+{# Generically render a field #}
+{% macro render_field_div(field) %}
+ {{- render_label_p(field) }}
+ <div class="form_field_input">
+ {{ field }}
+ {%- if field.errors -%}
+ {% for error in field.errors %}
+ <p class="form_field_error">{{ error }}</p>
+ {% endfor %}
+ {%- endif %}
+ {%- if field.description %}
+ <p class="form_field_description">{{ field.description|safe }}</p>
+ {%- endif %}
+ </div>
+{%- endmacro %}
+
+{# Auto-render a form as a series of divs #}
+{% macro render_divs(form) -%}
+ {% for field in form %}
+ {{ render_field_div(field) }}
+ {% endfor %}
+{%- endmacro %}
+
+{# Auto-render a form as a table #}
+{% macro render_table(form) -%}
+ {% for field in form %}
+ <tr>
+ <th>{{ field.label.text }}</th>
+ <td>
+ {{field}}
+ {% if field.errors %}
+ <br />
+ <ul class="errors">
+ {% for error in field.errors %}
+ <li>{{error}}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </td>
+ </tr>
+ {% endfor %}
+{%- endmacro %}
diff --git a/mediagoblin/templates/mediagoblin/webfinger/host-meta.xml b/mediagoblin/templates/mediagoblin/webfinger/host-meta.xml
new file mode 100644
index 00000000..0f5fa7a3
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/webfinger/host-meta.xml
@@ -0,0 +1,27 @@
+{# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"
+ xmlns:hm="http://host-meta.net/xrd/1.0">
+
+ <hm:Host>{{ request.host }}</hm:Host>
+
+ <Link rel="lrdd"
+ template="{{ lrdd_template|replace(placeholder, '{uri}') }}">
+ <Title>{{ lrdd_title }}</Title>
+ </Link>
+</XRD>
diff --git a/mediagoblin/templates/mediagoblin/webfinger/xrd.xml b/mediagoblin/templates/mediagoblin/webfinger/xrd.xml
new file mode 100644
index 00000000..bb2c5905
--- /dev/null
+++ b/mediagoblin/templates/mediagoblin/webfinger/xrd.xml
@@ -0,0 +1,27 @@
+{# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
+
+ <Subject>{{ subject }}</Subject>
+ <Alias>{{ alias }}</Alias>
+ {% for link in links %}
+ <Link
+ {%- for attr, value in link.attrs.items() %} {{ attr }}="{{ value}}"
+ {%- endfor %} />
+ {%- endfor %}
+</XRD>
diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py
new file mode 100644
index 00000000..cf200791
--- /dev/null
+++ b/mediagoblin/tests/__init__.py
@@ -0,0 +1,22 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+def setup_package():
+
+ import warnings
+ from sqlalchemy.exc import SAWarning
+ warnings.simplefilter("error", SAWarning)
diff --git a/mediagoblin/tests/appconfig_context_modified.ini b/mediagoblin/tests/appconfig_context_modified.ini
new file mode 100644
index 00000000..80ca69b1
--- /dev/null
+++ b/mediagoblin/tests/appconfig_context_modified.ini
@@ -0,0 +1,26 @@
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+# TODO: Switch to using an in-memory database
+sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
+[[mediagoblin.tests.testplugins.modify_context]]
diff --git a/mediagoblin/tests/appconfig_plugin_specs.ini b/mediagoblin/tests/appconfig_plugin_specs.ini
new file mode 100644
index 00000000..5511cd97
--- /dev/null
+++ b/mediagoblin/tests/appconfig_plugin_specs.ini
@@ -0,0 +1,21 @@
+[mediagoblin]
+direct_remote_path = /mgoblin_static/
+email_sender_address = "notice@mediagoblin.example.org"
+
+## Uncomment and change to your DB's appropiate setting.
+## Default is a local sqlite db "mediagoblin.db".
+# sql_engine = postgresql:///gmg
+
+# set to false to enable sending notices
+email_debug_mode = true
+
+# Set to false to disable registrations
+allow_registration = true
+
+[plugins]
+[[mediagoblin.tests.testplugins.pluginspec]]
+some_string = "not blork"
+some_int = "not an int"
+
+# this one shouldn't have its own config
+[[mediagoblin.tests.testplugins.callables1]]
diff --git a/mediagoblin/tests/appconfig_static_plugin.ini b/mediagoblin/tests/appconfig_static_plugin.ini
new file mode 100644
index 00000000..dc251171
--- /dev/null
+++ b/mediagoblin/tests/appconfig_static_plugin.ini
@@ -0,0 +1,26 @@
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+# TODO: Switch to using an in-memory database
+sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+
+# Celery shouldn't be set up by the application as it's setup via
+# mediagoblin.init.celery.from_celery
+celery_setup_elsewhere = true
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
+[[mediagoblin.tests.testplugins.staticstuff]]
diff --git a/mediagoblin/tests/conftest.py b/mediagoblin/tests/conftest.py
new file mode 100644
index 00000000..dbb0aa0a
--- /dev/null
+++ b/mediagoblin/tests/conftest.py
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import pytest
+
+from mediagoblin.tests import tools
+from mediagoblin.tools.testing import _activate_testing
+
+
+@pytest.fixture()
+def test_app(request):
+ """
+ py.test fixture to pass sandboxed mediagoblin applications into tests that
+ want them.
+
+ You could make a local version of this method for your own tests
+ to override the paste and config files being used by passing them
+ in differently to get_app.
+ """
+ return tools.get_app(request)
+
+
+@pytest.fixture()
+def pt_fixture_enable_testing():
+ """
+ py.test fixture to enable testing mode in tools.
+ """
+ _activate_testing()
diff --git a/mediagoblin/tests/fake_carrot_conf_bad.ini b/mediagoblin/tests/fake_carrot_conf_bad.ini
new file mode 100644
index 00000000..9d8cf518
--- /dev/null
+++ b/mediagoblin/tests/fake_carrot_conf_bad.ini
@@ -0,0 +1,14 @@
+[carrotapp]
+# Whether or not our carrots are going to be turned into cake.
+## These should throw errors
+carrotcake = slobber
+num_carrots = GROSS
+
+# A message encouraging our users to eat their carrots.
+encouragement_phrase = 586956856856 # shouldn't throw error
+
+# Something extra!
+blah_blah = "blah!" # shouldn't throw error either
+
+[celery]
+EAT_CELERY_WITH_CARROTS = pants # yeah that's def an error right there.
diff --git a/mediagoblin/tests/fake_carrot_conf_empty.ini b/mediagoblin/tests/fake_carrot_conf_empty.ini
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediagoblin/tests/fake_carrot_conf_empty.ini
diff --git a/mediagoblin/tests/fake_carrot_conf_good.ini b/mediagoblin/tests/fake_carrot_conf_good.ini
new file mode 100644
index 00000000..1377907b
--- /dev/null
+++ b/mediagoblin/tests/fake_carrot_conf_good.ini
@@ -0,0 +1,13 @@
+[carrotapp]
+# Whether or not our carrots are going to be turned into cake.
+carrotcake = true
+num_carrots = 88
+
+# A message encouraging our users to eat their carrots.
+encouragement_phrase = "I'd love it if you eat your carrots!"
+
+# Something extra!
+blah_blah = "blah!"
+
+[celery]
+EAT_CELERY_WITH_CARROTS = False
diff --git a/mediagoblin/tests/fake_celery_conf.ini b/mediagoblin/tests/fake_celery_conf.ini
new file mode 100644
index 00000000..67b0cba6
--- /dev/null
+++ b/mediagoblin/tests/fake_celery_conf.ini
@@ -0,0 +1,9 @@
+[mediagoblin]
+# I got nothin' in this file!
+
+[celery]
+SOME_VARIABLE = floop
+MAIL_PORT = 2000
+CELERYD_ETA_SCHEDULER_PRECISION = 1.3
+CELERY_RESULT_PERSISTENT = true
+CELERY_IMPORTS = foo.bar.baz, this.is.an.import
diff --git a/mediagoblin/tests/fake_celery_module.py b/mediagoblin/tests/fake_celery_module.py
new file mode 100644
index 00000000..621845ba
--- /dev/null
+++ b/mediagoblin/tests/fake_celery_module.py
@@ -0,0 +1,15 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/tests/fake_config_spec.ini b/mediagoblin/tests/fake_config_spec.ini
new file mode 100644
index 00000000..43f2e236
--- /dev/null
+++ b/mediagoblin/tests/fake_config_spec.ini
@@ -0,0 +1,10 @@
+[carrotapp]
+# Whether or not our carrots are going to be turned into cake.
+carrotcake = boolean(default=False)
+num_carrots = integer(default=1)
+
+# A message encouraging our users to eat their carrots.
+encouragement_phrase = string()
+
+[celery]
+EAT_CELERY_WITH_CARROTS = boolean(default=True) \ No newline at end of file
diff --git a/mediagoblin/tests/pytest.ini b/mediagoblin/tests/pytest.ini
new file mode 100644
index 00000000..e561c074
--- /dev/null
+++ b/mediagoblin/tests/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+usefixtures = tmpdir pt_fixture_enable_testing
diff --git a/mediagoblin/tests/resources.py b/mediagoblin/tests/resources.py
new file mode 100644
index 00000000..f7b3037d
--- /dev/null
+++ b/mediagoblin/tests/resources.py
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+from pkg_resources import resource_filename
+
+
+def resource(filename):
+ return resource_filename('mediagoblin.tests', 'test_submission/' + filename)
+
+
+GOOD_JPG = resource('good.jpg')
+GOOD_PNG = resource('good.png')
+EVIL_FILE = resource('evil')
+EVIL_JPG = resource('evil.jpg')
+EVIL_PNG = resource('evil.png')
+BIG_BLUE = resource('bigblue.png')
+GOOD_PDF = resource('good.pdf')
+
+
+def resource_exif(f):
+ return resource_filename('mediagoblin.tests', 'test_exif/' + f)
+
+
+GOOD_JPG = resource_exif('good.jpg')
+EMPTY_JPG = resource_exif('empty.jpg')
+BAD_JPG = resource_exif('bad.jpg')
+GPS_JPG = resource_exif('has-gps.jpg')
diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py
new file mode 100644
index 00000000..89cf1026
--- /dev/null
+++ b/mediagoblin/tests/test_api.py
@@ -0,0 +1,92 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import logging
+import base64
+
+import pytest
+
+from mediagoblin import mg_globals
+from mediagoblin.tools import template, pluginapi
+from mediagoblin.tests.tools import fixture_add_user
+from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
+ BIG_BLUE
+
+
+_log = logging.getLogger(__name__)
+
+
+class TestAPI(object):
+ def setup(self):
+ self.db = mg_globals.database
+
+ self.user_password = u'4cc355_70k3N'
+ self.user = fixture_add_user(u'joapi', self.user_password)
+
+ def login(self, test_app):
+ test_app.post(
+ '/auth/login/', {
+ 'username': self.user.username,
+ 'password': self.user_password})
+
+ def get_context(self, template_name):
+ return template.TEMPLATE_TEST_CONTEXT[template_name]
+
+ def http_auth_headers(self):
+ return {'Authorization': 'Basic {0}'.format(
+ base64.b64encode(':'.join([
+ self.user.username,
+ self.user_password])))}
+
+ def do_post(self, data, test_app, **kwargs):
+ url = kwargs.pop('url', '/api/submit')
+ do_follow = kwargs.pop('do_follow', False)
+
+ if not 'headers' in kwargs.keys():
+ kwargs['headers'] = self.http_auth_headers()
+
+ response = test_app.post(url, data, **kwargs)
+
+ if do_follow:
+ response.follow()
+
+ return response
+
+ def upload_data(self, filename):
+ return {'upload_files': [('file', filename)]}
+
+ def test_1_test_test_view(self, test_app):
+ self.login(test_app)
+
+ response = test_app.get(
+ '/api/test',
+ headers=self.http_auth_headers())
+
+ assert response.body == \
+ '{"username": "joapi", "email": "joapi@example.com"}'
+
+ def test_2_test_submission(self, test_app):
+ self.login(test_app)
+
+ response = self.do_post(
+ {'title': 'Great JPG!'},
+ test_app,
+ **self.upload_data(GOOD_JPG))
+
+ assert response.status_int == 200
+
+ assert self.db.MediaEntry.query.filter_by(title=u'Great JPG!').first()
diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py
new file mode 100644
index 00000000..755727f9
--- /dev/null
+++ b/mediagoblin/tests/test_auth.py
@@ -0,0 +1,396 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import urlparse
+import datetime
+
+from mediagoblin import mg_globals
+from mediagoblin.auth import lib as auth_lib
+from mediagoblin.db.models import User
+from mediagoblin.tests.tools import fixture_add_user
+from mediagoblin.tools import template, mail
+
+
+########################
+# Test bcrypt auth funcs
+########################
+
+def test_bcrypt_check_password():
+ # Check known 'lollerskates' password against check function
+ assert auth_lib.bcrypt_check_password(
+ 'lollerskates',
+ '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
+
+ assert not auth_lib.bcrypt_check_password(
+ 'notthepassword',
+ '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO')
+
+ # Same thing, but with extra fake salt.
+ assert not auth_lib.bcrypt_check_password(
+ 'notthepassword',
+ '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6',
+ '3><7R45417')
+
+
+def test_bcrypt_gen_password_hash():
+ pw = 'youwillneverguessthis'
+
+ # Normal password hash generation, and check on that hash
+ hashed_pw = auth_lib.bcrypt_gen_password_hash(pw)
+ assert auth_lib.bcrypt_check_password(
+ pw, hashed_pw)
+ assert not auth_lib.bcrypt_check_password(
+ 'notthepassword', hashed_pw)
+
+ # Same thing, extra salt.
+ hashed_pw = auth_lib.bcrypt_gen_password_hash(pw, '3><7R45417')
+ assert auth_lib.bcrypt_check_password(
+ pw, hashed_pw, '3><7R45417')
+ assert not auth_lib.bcrypt_check_password(
+ 'notthepassword', hashed_pw, '3><7R45417')
+
+
+def test_register_views(test_app):
+ """
+ Massive test function that all our registration-related views all work.
+ """
+ # Test doing a simple GET on the page
+ # -----------------------------------
+
+ test_app.get('/auth/register/')
+ # Make sure it rendered with the appropriate template
+ assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Try to register without providing anything, should error
+ # --------------------------------------------------------
+
+ template.clear_test_template_context()
+ test_app.post(
+ '/auth/register/', {})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ form = context['register_form']
+ assert form.username.errors == [u'This field is required.']
+ assert form.password.errors == [u'This field is required.']
+ assert form.email.errors == [u'This field is required.']
+
+ # Try to register with fields that are known to be invalid
+ # --------------------------------------------------------
+
+ ## too short
+ template.clear_test_template_context()
+ test_app.post(
+ '/auth/register/', {
+ 'username': 'l',
+ 'password': 'o',
+ 'email': 'l'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ form = context['register_form']
+
+ assert form.username.errors == [u'Field must be between 3 and 30 characters long.']
+ assert form.password.errors == [u'Field must be between 5 and 1024 characters long.']
+
+ ## bad form
+ template.clear_test_template_context()
+ test_app.post(
+ '/auth/register/', {
+ 'username': '@_@',
+ 'email': 'lollerskates'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html']
+ form = context['register_form']
+
+ assert form.username.errors == [u'This field does not take email addresses.']
+ assert form.email.errors == [u'This field requires an email address.']
+
+ ## At this point there should be no users in the database ;)
+ assert User.query.count() == 0
+
+ # Successful register
+ # -------------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/register/', {
+ 'username': u'happygirl',
+ 'password': 'iamsohappy',
+ 'email': 'happygrrl@example.org'})
+ response.follow()
+
+ ## Did we redirect to the proper page? Use the right template?
+ assert urlparse.urlsplit(response.location)[2] == '/u/happygirl/'
+ assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT
+
+ ## Make sure user is in place
+ new_user = mg_globals.database.User.find_one(
+ {'username': u'happygirl'})
+ assert new_user
+ assert new_user.status == u'needs_email_verification'
+ assert new_user.email_verified == False
+
+ ## Make sure user is logged in
+ request = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/user_pages/user.html']['request']
+ assert request.session['user_id'] == unicode(new_user.id)
+
+ ## Make sure we get email confirmation, and try verifying
+ assert len(mail.EMAIL_TEST_INBOX) == 1
+ message = mail.EMAIL_TEST_INBOX.pop()
+ assert message['To'] == 'happygrrl@example.org'
+ email_context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/auth/verification_email.txt']
+ assert email_context['verification_url'] in message.get_payload(decode=True)
+
+ path = urlparse.urlsplit(email_context['verification_url'])[2]
+ get_params = urlparse.urlsplit(email_context['verification_url'])[3]
+ assert path == u'/auth/verify_email/'
+ parsed_get_params = urlparse.parse_qs(get_params)
+
+ ### user should have these same parameters
+ assert parsed_get_params['userid'] == [
+ unicode(new_user.id)]
+ assert parsed_get_params['token'] == [
+ new_user.verification_key]
+
+ ## Try verifying with bs verification key, shouldn't work
+ template.clear_test_template_context()
+ response = test_app.get(
+ "/auth/verify_email/?userid=%s&token=total_bs" % unicode(
+ new_user.id))
+ response.follow()
+ context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/user_pages/user.html']
+ # assert context['verification_successful'] == True
+ # TODO: Would be good to test messages here when we can do so...
+ new_user = mg_globals.database.User.find_one(
+ {'username': u'happygirl'})
+ assert new_user
+ assert new_user.status == u'needs_email_verification'
+ assert new_user.email_verified == False
+
+ ## Verify the email activation works
+ template.clear_test_template_context()
+ response = test_app.get("%s?%s" % (path, get_params))
+ response.follow()
+ context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/user_pages/user.html']
+ # assert context['verification_successful'] == True
+ # TODO: Would be good to test messages here when we can do so...
+ new_user = mg_globals.database.User.find_one(
+ {'username': u'happygirl'})
+ assert new_user
+ assert new_user.status == u'active'
+ assert new_user.email_verified == True
+
+ # Uniqueness checks
+ # -----------------
+ ## We shouldn't be able to register with that user twice
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/register/', {
+ 'username': u'happygirl',
+ 'password': 'iamsohappy2',
+ 'email': 'happygrrl2@example.org'})
+
+ context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/auth/register.html']
+ form = context['register_form']
+ assert form.username.errors == [
+ u'Sorry, a user with that name already exists.']
+
+ ## TODO: Also check for double instances of an email address?
+
+ ### Oops, forgot the password
+ # -------------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/forgot_password/',
+ {'username': u'happygirl'})
+ response.follow()
+
+ ## Did we redirect to the proper page? Use the right template?
+ assert urlparse.urlsplit(response.location)[2] == '/auth/login/'
+ assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
+
+ ## Make sure link to change password is sent by email
+ assert len(mail.EMAIL_TEST_INBOX) == 1
+ message = mail.EMAIL_TEST_INBOX.pop()
+ assert message['To'] == 'happygrrl@example.org'
+ email_context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/auth/fp_verification_email.txt']
+ #TODO - change the name of verification_url to something forgot-password-ish
+ assert email_context['verification_url'] in message.get_payload(decode=True)
+
+ path = urlparse.urlsplit(email_context['verification_url'])[2]
+ get_params = urlparse.urlsplit(email_context['verification_url'])[3]
+ assert path == u'/auth/forgot_password/verify/'
+ parsed_get_params = urlparse.parse_qs(get_params)
+
+ # user should have matching parameters
+ new_user = mg_globals.database.User.find_one({'username': u'happygirl'})
+ assert parsed_get_params['userid'] == [unicode(new_user.id)]
+ assert parsed_get_params['token'] == [new_user.fp_verification_key]
+
+ ### The forgotten password token should be set to expire in ~ 10 days
+ # A few ticks have expired so there are only 9 full days left...
+ assert (new_user.fp_token_expire - datetime.datetime.now()).days == 9
+
+ ## Try using a bs password-changing verification key, shouldn't work
+ template.clear_test_template_context()
+ response = test_app.get(
+ "/auth/forgot_password/verify/?userid=%s&token=total_bs" % unicode(
+ new_user.id), status=404)
+ assert response.status.split()[0] == u'404' # status="404 NOT FOUND"
+
+ ## Try using an expired token to change password, shouldn't work
+ template.clear_test_template_context()
+ new_user = mg_globals.database.User.find_one({'username': u'happygirl'})
+ real_token_expiration = new_user.fp_token_expire
+ new_user.fp_token_expire = datetime.datetime.now()
+ new_user.save()
+ response = test_app.get("%s?%s" % (path, get_params), status=404)
+ assert response.status.split()[0] == u'404' # status="404 NOT FOUND"
+ new_user.fp_token_expire = real_token_expiration
+ new_user.save()
+
+ ## Verify step 1 of password-change works -- can see form to change password
+ template.clear_test_template_context()
+ response = test_app.get("%s?%s" % (path, get_params))
+ assert 'mediagoblin/auth/change_fp.html' in template.TEMPLATE_TEST_CONTEXT
+
+ ## Verify step 2.1 of password-change works -- report success to user
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/forgot_password/verify/', {
+ 'userid': parsed_get_params['userid'],
+ 'password': 'iamveryveryhappy',
+ 'token': parsed_get_params['token']})
+ response.follow()
+ assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
+
+ ## Verify step 2.2 of password-change works -- login w/ new password success
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'happygirl',
+ 'password': 'iamveryveryhappy'})
+
+ # User should be redirected
+ response.follow()
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+
+def test_authentication_views(test_app):
+ """
+ Test logging in and logging out
+ """
+ # Make a new user
+ test_user = fixture_add_user(active_user=False)
+
+ # Get login
+ # ---------
+ test_app.get('/auth/login/')
+ assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Failed login - blank form
+ # -------------------------
+ template.clear_test_template_context()
+ response = test_app.post('/auth/login/')
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
+ form = context['login_form']
+ assert form.username.errors == [u'This field is required.']
+ assert form.password.errors == [u'This field is required.']
+
+ # Failed login - blank user
+ # -------------------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'password': u'toast'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
+ form = context['login_form']
+ assert form.username.errors == [u'This field is required.']
+
+ # Failed login - blank password
+ # -----------------------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'chris'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
+ form = context['login_form']
+ assert form.password.errors == [u'This field is required.']
+
+ # Failed login - bad user
+ # -----------------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'steve',
+ 'password': 'toast'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
+ assert context['login_failed']
+
+ # Failed login - bad password
+ # ---------------------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'chris',
+ 'password': 'jam_and_ham'})
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html']
+ assert context['login_failed']
+
+ # Successful login
+ # ----------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'chris',
+ 'password': 'toast'})
+
+ # User should be redirected
+ response.follow()
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Make sure user is in the session
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ session = context['request'].session
+ assert session['user_id'] == unicode(test_user.id)
+
+ # Successful logout
+ # -----------------
+ template.clear_test_template_context()
+ response = test_app.get('/auth/logout/')
+
+ # Should be redirected to index page
+ response.follow()
+ assert urlparse.urlsplit(response.location)[2] == '/'
+ assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT
+
+ # Make sure the user is not in the session
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ session = context['request'].session
+ assert 'user_id' not in session
+
+ # User is redirected to custom URL if POST['next'] is set
+ # -------------------------------------------------------
+ template.clear_test_template_context()
+ response = test_app.post(
+ '/auth/login/', {
+ 'username': u'chris',
+ 'password': 'toast',
+ 'next' : '/u/chris/'})
+ assert urlparse.urlsplit(response.location)[2] == '/u/chris/'
diff --git a/mediagoblin/tests/test_celery_setup.py b/mediagoblin/tests/test_celery_setup.py
new file mode 100644
index 00000000..5530c6f2
--- /dev/null
+++ b/mediagoblin/tests/test_celery_setup.py
@@ -0,0 +1,60 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import pkg_resources
+
+from mediagoblin.init import celery as celery_setup
+from mediagoblin.init.config import read_mediagoblin_config
+
+
+TEST_CELERY_CONF_NOSPECIALDB = pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'fake_celery_conf.ini')
+
+
+def test_setup_celery_from_config():
+ def _wipe_testmodule_clean(module):
+ vars_to_wipe = [
+ var for var in dir(module)
+ if not var.startswith('__') and not var.endswith('__')]
+ for var in vars_to_wipe:
+ delattr(module, var)
+
+ global_config, validation_result = read_mediagoblin_config(
+ TEST_CELERY_CONF_NOSPECIALDB)
+ app_config = global_config['mediagoblin']
+
+ celery_setup.setup_celery_from_config(
+ app_config, global_config,
+ 'mediagoblin.tests.fake_celery_module', set_environ=False)
+
+ from mediagoblin.tests import fake_celery_module
+ assert fake_celery_module.SOME_VARIABLE == 'floop'
+ assert fake_celery_module.MAIL_PORT == 2000
+ assert isinstance(fake_celery_module.MAIL_PORT, int)
+ assert fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION == 1.3
+ assert isinstance(fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION, float)
+ assert fake_celery_module.CELERY_RESULT_PERSISTENT is True
+ assert fake_celery_module.CELERY_IMPORTS == [
+ 'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing.task']
+ assert fake_celery_module.CELERY_RESULT_BACKEND == 'database'
+ assert fake_celery_module.CELERY_RESULT_DBURI == (
+ 'sqlite:///' +
+ pkg_resources.resource_filename('mediagoblin.tests', 'celery.db'))
+
+ assert fake_celery_module.BROKER_TRANSPORT == 'sqlalchemy'
+ assert fake_celery_module.BROKER_HOST == (
+ 'sqlite:///' +
+ pkg_resources.resource_filename('mediagoblin.tests', 'kombu.db'))
diff --git a/mediagoblin/tests/test_collections.py b/mediagoblin/tests/test_collections.py
new file mode 100644
index 00000000..87782f30
--- /dev/null
+++ b/mediagoblin/tests/test_collections.py
@@ -0,0 +1,32 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tests.tools import fixture_add_collection, fixture_add_user
+from mediagoblin.db.models import Collection, User
+
+
+def test_user_deletes_collection(test_app):
+ # Setup db.
+ user = fixture_add_user()
+ coll = fixture_add_collection(user=user)
+ # Reload into session:
+ user = User.query.get(user.id)
+
+ cnt1 = Collection.query.count()
+ user.delete()
+ cnt2 = Collection.query.count()
+
+ assert cnt1 == cnt2 + 1
diff --git a/mediagoblin/tests/test_config.py b/mediagoblin/tests/test_config.py
new file mode 100644
index 00000000..b13adae6
--- /dev/null
+++ b/mediagoblin/tests/test_config.py
@@ -0,0 +1,97 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import pkg_resources
+
+from mediagoblin.init import config
+
+
+CARROT_CONF_GOOD = pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'fake_carrot_conf_good.ini')
+CARROT_CONF_EMPTY = pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'fake_carrot_conf_empty.ini')
+CARROT_CONF_BAD = pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'fake_carrot_conf_bad.ini')
+FAKE_CONFIG_SPEC = pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'fake_config_spec.ini')
+
+
+def test_read_mediagoblin_config():
+ # An empty file
+ this_conf, validation_results = config.read_mediagoblin_config(
+ CARROT_CONF_EMPTY, FAKE_CONFIG_SPEC)
+
+ assert this_conf['carrotapp']['carrotcake'] == False
+ assert this_conf['carrotapp']['num_carrots'] == 1
+ assert 'encouragement_phrase' not in this_conf['carrotapp']
+ assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == True
+
+ # A good file
+ this_conf, validation_results = config.read_mediagoblin_config(
+ CARROT_CONF_GOOD, FAKE_CONFIG_SPEC)
+
+ assert this_conf['carrotapp']['carrotcake'] == True
+ assert this_conf['carrotapp']['num_carrots'] == 88
+ assert this_conf['carrotapp']['encouragement_phrase'] == \
+ "I'd love it if you eat your carrots!"
+ assert this_conf['carrotapp']['blah_blah'] == "blah!"
+ assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == False
+
+ # A bad file
+ this_conf, validation_results = config.read_mediagoblin_config(
+ CARROT_CONF_BAD, FAKE_CONFIG_SPEC)
+
+ # These should still open but will have errors that we'll test for
+ # in test_generate_validation_report()
+ assert this_conf['carrotapp']['carrotcake'] == 'slobber'
+ assert this_conf['carrotapp']['num_carrots'] == 'GROSS'
+ assert this_conf['carrotapp']['encouragement_phrase'] == \
+ "586956856856"
+ assert this_conf['carrotapp']['blah_blah'] == "blah!"
+ assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == "pants"
+
+
+def test_generate_validation_report():
+ # Empty
+ this_conf, validation_results = config.read_mediagoblin_config(
+ CARROT_CONF_EMPTY, FAKE_CONFIG_SPEC)
+ report = config.generate_validation_report(this_conf, validation_results)
+ assert report is None
+
+ # Good
+ this_conf, validation_results = config.read_mediagoblin_config(
+ CARROT_CONF_GOOD, FAKE_CONFIG_SPEC)
+ report = config.generate_validation_report(this_conf, validation_results)
+ assert report is None
+
+ # Bad
+ this_conf, validation_results = config.read_mediagoblin_config(
+ CARROT_CONF_BAD, FAKE_CONFIG_SPEC)
+ report = config.generate_validation_report(this_conf, validation_results)
+
+ assert report.startswith("""\
+There were validation problems loading this config file:
+--------------------------------------------------------""")
+
+ expected_warnings = [
+ 'carrotapp:carrotcake = the value "slobber" is of the wrong type.',
+ 'carrotapp:num_carrots = the value "GROSS" is of the wrong type.',
+ 'celery:EAT_CELERY_WITH_CARROTS = the value "pants" is of the wrong type.']
+ warnings = report.splitlines()[2:]
+
+ assert len(warnings) == 3
+ for warning in expected_warnings:
+ assert warning in warnings
diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py
new file mode 100644
index 00000000..a272caf6
--- /dev/null
+++ b/mediagoblin/tests/test_csrf_middleware.py
@@ -0,0 +1,86 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin import mg_globals
+
+
+def test_csrf_cookie_set(test_app):
+ cookie_name = mg_globals.app_config['csrf_cookie_name']
+
+ # get login page
+ response = test_app.get('/auth/login/')
+
+ # assert that the mediagoblin nonce cookie has been set
+ assert 'Set-Cookie' in response.headers
+ assert cookie_name in response.cookies_set
+
+ # assert that we're also sending a vary header
+ assert response.headers.get('Vary', False) == 'Cookie'
+
+
+# We need a fresh app for this test on webtest < 1.3.6.
+# We do not understand why, but it fixes the tests.
+# If we require webtest >= 1.3.6, we can switch to a non fresh app here.
+#
+# ... this comment might be irrelevant post-pytest-fixtures, but I'm not
+# removing it yet in case we move to module-level tests :)
+# -- cwebber
+def test_csrf_token_must_match(test_app):
+
+ # construct a request with no cookie or form token
+ assert test_app.post('/auth/login/',
+ extra_environ={'gmg.verify_csrf': True},
+ expect_errors=True).status_int == 403
+
+ # construct a request with a cookie, but no form token
+ assert test_app.post('/auth/login/',
+ headers={'Cookie': str('%s=foo' %
+ mg_globals.app_config['csrf_cookie_name'])},
+ extra_environ={'gmg.verify_csrf': True},
+ expect_errors=True).status_int == 403
+
+ # if both the cookie and form token are provided, they must match
+ assert test_app.post('/auth/login/',
+ {'csrf_token': 'blarf'},
+ headers={'Cookie': str('%s=foo' %
+ mg_globals.app_config['csrf_cookie_name'])},
+ extra_environ={'gmg.verify_csrf': True},
+ expect_errors=True).\
+ status_int == 403
+
+ assert test_app.post('/auth/login/',
+ {'csrf_token': 'foo'},
+ headers={'Cookie': str('%s=foo' %
+ mg_globals.app_config['csrf_cookie_name'])},
+ extra_environ={'gmg.verify_csrf': True}).\
+ status_int == 200
+
+def test_csrf_exempt(test_app):
+ # monkey with the views to decorate a known endpoint
+ import mediagoblin.auth.views
+ from mediagoblin.meddleware.csrf import csrf_exempt
+
+ mediagoblin.auth.views.login = csrf_exempt(
+ mediagoblin.auth.views.login
+ )
+
+ # construct a request with no cookie or form token
+ assert test_app.post('/auth/login/',
+ extra_environ={'gmg.verify_csrf': True},
+ expect_errors=False).status_int == 200
+
+ # restore the CSRF protection in case other tests expect it
+ mediagoblin.auth.views.login.csrf_enabled = True
diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py
new file mode 100644
index 00000000..08b4f8cf
--- /dev/null
+++ b/mediagoblin/tests/test_edit.py
@@ -0,0 +1,144 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import urlparse
+import pytest
+
+from mediagoblin import mg_globals
+from mediagoblin.db.models import User
+from mediagoblin.tests.tools import fixture_add_user
+from mediagoblin.tools import template
+from mediagoblin.auth.lib import bcrypt_check_password
+
+class TestUserEdit(object):
+ def setup(self):
+ # set up new user
+ self.user_password = u'toast'
+ self.user = fixture_add_user(password = self.user_password)
+
+ def login(self, test_app):
+ test_app.post(
+ '/auth/login/', {
+ 'username': self.user.username,
+ 'password': self.user_password})
+
+
+ def test_user_deletion(self, test_app):
+ """Delete user via web interface"""
+ self.login(test_app)
+
+ # Make sure user exists
+ assert User.query.filter_by(username=u'chris').first()
+
+ res = test_app.post('/edit/account/delete/', {'confirmed': 'y'})
+
+ # Make sure user has been deleted
+ assert User.query.filter_by(username=u'chris').first() == None
+
+ #TODO: make sure all corresponding items comments etc have been
+ # deleted too. Perhaps in submission test?
+
+ #Restore user at end of test
+ self.user = fixture_add_user(password = self.user_password)
+ self.login(test_app)
+
+
+ def test_change_password(self, test_app):
+ """Test changing password correctly and incorrectly"""
+ self.login(test_app)
+
+ # test that the password can be changed
+ template.clear_test_template_context()
+ res = test_app.post(
+ '/edit/password/', {
+ 'old_password': 'toast',
+ 'new_password': '123456',
+ })
+ res.follow()
+
+ # Did we redirect to the correct page?
+ assert urlparse.urlsplit(res.location)[2] == '/edit/account/'
+
+ # test_user has to be fetched again in order to have the current values
+ test_user = User.query.filter_by(username=u'chris').first()
+ assert bcrypt_check_password('123456', test_user.pw_hash)
+ # Update current user passwd
+ self.user_password = '123456'
+
+ # test that the password cannot be changed if the given
+ # old_password is wrong
+ template.clear_test_template_context()
+ test_app.post(
+ '/edit/password/', {
+ 'old_password': 'toast',
+ 'new_password': '098765',
+ })
+
+ test_user = User.query.filter_by(username=u'chris').first()
+ assert not bcrypt_check_password('098765', test_user.pw_hash)
+
+
+ def test_change_bio_url(self, test_app):
+ """Test changing bio and URL"""
+ self.login(test_app)
+
+ # Test if legacy profile editing URL redirects correctly
+ res = test_app.post(
+ '/edit/profile/', {
+ 'bio': u'I love toast!',
+ 'url': u'http://dustycloud.org/'}, expect_errors=True)
+
+ # Should redirect to /u/chris/edit/
+ assert res.status_int == 302
+ assert res.headers['Location'].endswith("/u/chris/edit/")
+
+ res = test_app.post(
+ '/u/chris/edit/', {
+ 'bio': u'I love toast!',
+ 'url': u'http://dustycloud.org/'})
+
+ test_user = User.query.filter_by(username=u'chris').first()
+ assert test_user.bio == u'I love toast!'
+ assert test_user.url == u'http://dustycloud.org/'
+
+ # change a different user than the logged in (should fail with 403)
+ fixture_add_user(username=u"foo")
+ res = test_app.post(
+ '/u/foo/edit/', {
+ 'bio': u'I love toast!',
+ 'url': u'http://dustycloud.org/'}, expect_errors=True)
+ assert res.status_int == 403
+
+ # test changing the bio and the URL inproperly
+ too_long_bio = 150 * 'T' + 150 * 'o' + 150 * 'a' + 150 * 's' + 150* 't'
+
+ test_app.post(
+ '/u/chris/edit/', {
+ # more than 500 characters
+ 'bio': too_long_bio,
+ 'url': 'this-is-no-url'})
+
+ # Check form errors
+ context = template.TEMPLATE_TEST_CONTEXT[
+ 'mediagoblin/edit/edit_profile.html']
+ form = context['form']
+
+ assert form.bio.errors == [
+ u'Field must be between 0 and 500 characters long.']
+ assert form.url.errors == [
+ u'This address contains errors']
+
+# test changing the url inproperly
diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py
new file mode 100644
index 00000000..c07e24ae
--- /dev/null
+++ b/mediagoblin/tests/test_exif.py
@@ -0,0 +1,431 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from mediagoblin.tools.exif import exif_fix_image_orientation, \
+ extract_exif, clean_exif, get_gps_data, get_useful
+from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG
+
+
+def assert_in(a, b):
+ assert a in b, "%r not in %r" % (a, b)
+
+
+def test_exif_extraction():
+ '''
+ Test EXIF extraction from a good image
+ '''
+ result = extract_exif(GOOD_JPG)
+ clean = clean_exif(result)
+ useful = get_useful(clean)
+ gps = get_gps_data(result)
+
+ # Do we have the result?
+ assert len(result) == 56
+
+ # Do we have clean data?
+ assert len(clean) == 53
+
+ # GPS data?
+ assert gps == {}
+
+ # Do we have the "useful" tags?
+ assert useful == {'EXIF CVAPattern': {'field_length': 8,
+ 'field_offset': 26224,
+ 'field_type': 7,
+ 'printable': u'[0, 2, 0, 2, 1, 2, 0, 1]',
+ 'tag': 41730,
+ 'values': [0, 2, 0, 2, 1, 2, 0, 1]},
+ 'EXIF ColorSpace': {'field_length': 2,
+ 'field_offset': 476,
+ 'field_type': 3,
+ 'printable': u'sRGB',
+ 'tag': 40961,
+ 'values': [1]},
+ 'EXIF ComponentsConfiguration': {'field_length': 4,
+ 'field_offset': 308,
+ 'field_type': 7,
+ 'printable': u'YCbCr',
+ 'tag': 37121,
+ 'values': [1, 2, 3, 0]},
+ 'EXIF CompressedBitsPerPixel': {'field_length': 8,
+ 'field_offset': 756,
+ 'field_type': 5,
+ 'printable': u'4',
+ 'tag': 37122,
+ 'values': [[4, 1]]},
+ 'EXIF Contrast': {'field_length': 2,
+ 'field_offset': 656,
+ 'field_type': 3,
+ 'printable': u'Soft',
+ 'tag': 41992,
+ 'values': [1]},
+ 'EXIF CustomRendered': {'field_length': 2,
+ 'field_offset': 572,
+ 'field_type': 3,
+ 'printable': u'Normal',
+ 'tag': 41985,
+ 'values': [0]},
+ 'EXIF DateTimeDigitized': {'field_length': 20,
+ 'field_offset': 736,
+ 'field_type': 2,
+ 'printable': u'2011:06:22 12:20:33',
+ 'tag': 36868,
+ 'values': u'2011:06:22 12:20:33'},
+ 'EXIF DateTimeOriginal': {'field_length': 20,
+ 'field_offset': 716,
+ 'field_type': 2,
+ 'printable': u'2011:06:22 12:20:33',
+ 'tag': 36867,
+ 'values': u'2011:06:22 12:20:33'},
+ 'EXIF DigitalZoomRatio': {'field_length': 8,
+ 'field_offset': 26232,
+ 'field_type': 5,
+ 'printable': u'1',
+ 'tag': 41988,
+ 'values': [[1, 1]]},
+ 'EXIF ExifImageLength': {'field_length': 2,
+ 'field_offset': 500,
+ 'field_type': 3,
+ 'printable': u'2592',
+ 'tag': 40963,
+ 'values': [2592]},
+ 'EXIF ExifImageWidth': {'field_length': 2,
+ 'field_offset': 488,
+ 'field_type': 3,
+ 'printable': u'3872',
+ 'tag': 40962,
+ 'values': [3872]},
+ 'EXIF ExifVersion': {'field_length': 4,
+ 'field_offset': 272,
+ 'field_type': 7,
+ 'printable': u'0221',
+ 'tag': 36864,
+ 'values': [48, 50, 50, 49]},
+ 'EXIF ExposureBiasValue': {'field_length': 8,
+ 'field_offset': 764,
+ 'field_type': 10,
+ 'printable': u'0',
+ 'tag': 37380,
+ 'values': [[0, 1]]},
+ 'EXIF ExposureMode': {'field_length': 2,
+ 'field_offset': 584,
+ 'field_type': 3,
+ 'printable': u'Manual Exposure',
+ 'tag': 41986,
+ 'values': [1]},
+ 'EXIF ExposureProgram': {'field_length': 2,
+ 'field_offset': 248,
+ 'field_type': 3,
+ 'printable': u'Manual',
+ 'tag': 34850,
+ 'values': [1]},
+ 'EXIF ExposureTime': {'field_length': 8,
+ 'field_offset': 700,
+ 'field_type': 5,
+ 'printable': u'1/125',
+ 'tag': 33434,
+ 'values': [[1, 125]]},
+ 'EXIF FNumber': {'field_length': 8,
+ 'field_offset': 708,
+ 'field_type': 5,
+ 'printable': u'10',
+ 'tag': 33437,
+ 'values': [[10, 1]]},
+ 'EXIF FileSource': {'field_length': 1,
+ 'field_offset': 536,
+ 'field_type': 7,
+ 'printable': u'Digital Camera',
+ 'tag': 41728,
+ 'values': [3]},
+ 'EXIF Flash': {'field_length': 2,
+ 'field_offset': 380,
+ 'field_type': 3,
+ 'printable': u'Flash did not fire',
+ 'tag': 37385,
+ 'values': [0]},
+ 'EXIF FlashPixVersion': {'field_length': 4,
+ 'field_offset': 464,
+ 'field_type': 7,
+ 'printable': u'0100',
+ 'tag': 40960,
+ 'values': [48, 49, 48, 48]},
+ 'EXIF FocalLength': {'field_length': 8,
+ 'field_offset': 780,
+ 'field_type': 5,
+ 'printable': u'18',
+ 'tag': 37386,
+ 'values': [[18, 1]]},
+ 'EXIF FocalLengthIn35mmFilm': {'field_length': 2,
+ 'field_offset': 620,
+ 'field_type': 3,
+ 'printable': u'27',
+ 'tag': 41989,
+ 'values': [27]},
+ 'EXIF GainControl': {'field_length': 2,
+ 'field_offset': 644,
+ 'field_type': 3,
+ 'printable': u'None',
+ 'tag': 41991,
+ 'values': [0]},
+ 'EXIF ISOSpeedRatings': {'field_length': 2,
+ 'field_offset': 260,
+ 'field_type': 3,
+ 'printable': u'100',
+ 'tag': 34855,
+ 'values': [100]},
+ 'EXIF InteroperabilityOffset': {'field_length': 4,
+ 'field_offset': 512,
+ 'field_type': 4,
+ 'printable': u'26240',
+ 'tag': 40965,
+ 'values': [26240]},
+ 'EXIF LightSource': {'field_length': 2,
+ 'field_offset': 368,
+ 'field_type': 3,
+ 'printable': u'Unknown',
+ 'tag': 37384,
+ 'values': [0]},
+ 'EXIF MaxApertureValue': {'field_length': 8,
+ 'field_offset': 772,
+ 'field_type': 5,
+ 'printable': u'18/5',
+ 'tag': 37381,
+ 'values': [[18, 5]]},
+ 'EXIF MeteringMode': {'field_length': 2,
+ 'field_offset': 356,
+ 'field_type': 3,
+ 'printable': u'Pattern',
+ 'tag': 37383,
+ 'values': [5]},
+ 'EXIF Saturation': {'field_length': 2,
+ 'field_offset': 668,
+ 'field_type': 3,
+ 'printable': u'Normal',
+ 'tag': 41993,
+ 'values': [0]},
+ 'EXIF SceneCaptureType': {'field_length': 2,
+ 'field_offset': 632,
+ 'field_type': 3,
+ 'printable': u'Standard',
+ 'tag': 41990,
+ 'values': [0]},
+ 'EXIF SceneType': {'field_length': 1,
+ 'field_offset': 548,
+ 'field_type': 7,
+ 'printable': u'Directly Photographed',
+ 'tag': 41729,
+ 'values': [1]},
+ 'EXIF SensingMethod': {'field_length': 2,
+ 'field_offset': 524,
+ 'field_type': 3,
+ 'printable': u'One-chip color area',
+ 'tag': 41495,
+ 'values': [2]},
+ 'EXIF Sharpness': {'field_length': 2,
+ 'field_offset': 680,
+ 'field_type': 3,
+ 'printable': u'Normal',
+ 'tag': 41994,
+ 'values': [0]},
+ 'EXIF SubSecTime': {'field_length': 3,
+ 'field_offset': 428,
+ 'field_type': 2,
+ 'printable': u'10',
+ 'tag': 37520,
+ 'values': u'10'},
+ 'EXIF SubSecTimeDigitized': {'field_length': 3,
+ 'field_offset': 452,
+ 'field_type': 2,
+ 'printable': u'10',
+ 'tag': 37522,
+ 'values': u'10'},
+ 'EXIF SubSecTimeOriginal': {'field_length': 3,
+ 'field_offset': 440,
+ 'field_type': 2,
+ 'printable': u'10',
+ 'tag': 37521,
+ 'values': u'10'},
+ 'EXIF SubjectDistanceRange': {'field_length': 2,
+ 'field_offset': 692,
+ 'field_type': 3,
+ 'printable': u'0',
+ 'tag': 41996,
+ 'values': [0]},
+ 'EXIF WhiteBalance': {'field_length': 2,
+ 'field_offset': 596,
+ 'field_type': 3,
+ 'printable': u'Auto',
+ 'tag': 41987,
+ 'values': [0]},
+ 'Image DateTime': {'field_length': 20,
+ 'field_offset': 194,
+ 'field_type': 2,
+ 'printable': u'2011:06:22 12:20:33',
+ 'tag': 306,
+ 'values': u'2011:06:22 12:20:33'},
+ 'Image ExifOffset': {'field_length': 4,
+ 'field_offset': 126,
+ 'field_type': 4,
+ 'printable': u'214',
+ 'tag': 34665,
+ 'values': [214]},
+ 'Image Make': {'field_length': 18,
+ 'field_offset': 134,
+ 'field_type': 2,
+ 'printable': u'NIKON CORPORATION',
+ 'tag': 271,
+ 'values': u'NIKON CORPORATION'},
+ 'Image Model': {'field_length': 10,
+ 'field_offset': 152,
+ 'field_type': 2,
+ 'printable': u'NIKON D80',
+ 'tag': 272,
+ 'values': u'NIKON D80'},
+ 'Image Orientation': {'field_length': 2,
+ 'field_offset': 42,
+ 'field_type': 3,
+ 'printable': u'Rotated 90 CCW',
+ 'tag': 274,
+ 'values': [6]},
+ 'Image ResolutionUnit': {'field_length': 2,
+ 'field_offset': 78,
+ 'field_type': 3,
+ 'printable': u'Pixels/Inch',
+ 'tag': 296,
+ 'values': [2]},
+ 'Image Software': {'field_length': 15,
+ 'field_offset': 178,
+ 'field_type': 2,
+ 'printable': u'Shotwell 0.9.3',
+ 'tag': 305,
+ 'values': u'Shotwell 0.9.3'},
+ 'Image XResolution': {'field_length': 8,
+ 'field_offset': 162,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 282,
+ 'values': [[300, 1]]},
+ 'Image YCbCrPositioning': {'field_length': 2,
+ 'field_offset': 114,
+ 'field_type': 3,
+ 'printable': u'Co-sited',
+ 'tag': 531,
+ 'values': [2]},
+ 'Image YResolution': {'field_length': 8,
+ 'field_offset': 170,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 283,
+ 'values': [[300, 1]]},
+ 'Thumbnail Compression': {'field_length': 2,
+ 'field_offset': 26280,
+ 'field_type': 3,
+ 'printable': u'JPEG (old-style)',
+ 'tag': 259,
+ 'values': [6]},
+ 'Thumbnail ResolutionUnit': {'field_length': 2,
+ 'field_offset': 26316,
+ 'field_type': 3,
+ 'printable': u'Pixels/Inch',
+ 'tag': 296,
+ 'values': [2]},
+ 'Thumbnail XResolution': {'field_length': 8,
+ 'field_offset': 26360,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 282,
+ 'values': [[300, 1]]},
+ 'Thumbnail YCbCrPositioning': {'field_length': 2,
+ 'field_offset': 26352,
+ 'field_type': 3,
+ 'printable': u'Co-sited',
+ 'tag': 531,
+ 'values': [2]},
+ 'Thumbnail YResolution': {'field_length': 8,
+ 'field_offset': 26368,
+ 'field_type': 5,
+ 'printable': u'300',
+ 'tag': 283,
+ 'values': [[300, 1]]}}
+
+
+def test_exif_image_orientation():
+ '''
+ Test image reorientation based on EXIF data
+ '''
+ result = extract_exif(GOOD_JPG)
+
+ image = exif_fix_image_orientation(
+ Image.open(GOOD_JPG),
+ result)
+
+ # Are the dimensions correct?
+ assert image.size == (428, 640)
+
+ # If this pixel looks right, the rest of the image probably will too.
+ assert_in(image.getdata()[10000],
+ ((41, 28, 11), (43, 27, 11))
+ )
+
+
+def test_exif_no_exif():
+ '''
+ Test an image without exif
+ '''
+ result = extract_exif(EMPTY_JPG)
+ clean = clean_exif(result)
+ useful = get_useful(clean)
+ gps = get_gps_data(result)
+
+ assert result == {}
+ assert clean == {}
+ assert gps == {}
+ assert useful == {}
+
+
+def test_exif_bad_image():
+ '''
+ Test EXIF extraction from a faithful, but bad image
+ '''
+ result = extract_exif(BAD_JPG)
+ clean = clean_exif(result)
+ useful = get_useful(clean)
+ gps = get_gps_data(result)
+
+ assert result == {}
+ assert clean == {}
+ assert gps == {}
+ assert useful == {}
+
+
+def test_exif_gps_data():
+ '''
+ Test extractiion of GPS data
+ '''
+ result = extract_exif(GPS_JPG)
+ gps = get_gps_data(result)
+
+ assert gps == {
+ 'latitude': 59.336666666666666,
+ 'direction': 25.674046740467404,
+ 'altitude': 37.64365671641791,
+ 'longitude': 18.016166666666667}
diff --git a/mediagoblin/tests/test_exif/bad.jpg b/mediagoblin/tests/test_exif/bad.jpg
new file mode 100644
index 00000000..4cde23cd
--- /dev/null
+++ b/mediagoblin/tests/test_exif/bad.jpg
@@ -0,0 +1,18 @@
+V2UncmUgbm8gc3RyYW5nZXJzIHRvIGxvdmUKWW91IGtub3cgdGhlIHJ1bGVzIGFuZCBzbyBkbyBJ
+CkEgZnVsbCBjb21taXRtZW50J3Mgd2hhdCBJJ20gdGhpbmtpbicgb2YKWW91IHdvdWxkbid0IGdl
+dCB0aGlzIGZyb20gYW55IG90aGVyIGd1eQpJIGp1c3Qgd2FubmEgdGVsbCB5b3UgaG93IEknbSBm
+ZWVsaW4nCkdvdHRhIG1ha2UgeW91IHVuZGVyc3RhbmQKCihDaG9ydXMpCk5ldmVyIGdvbm5hIGdp
+dmUgeW91IHVwCk5ldmVyIGdvbm5hIGxldCB5b3UgZG93bgpOZXZlciBnb25uYSBydW4gYXJvdW5k
+IGFuZCBkZXNlcnQgeW91Ck5ldmVyIGdvbm5hIG1ha2UgeW91IGNyeQpOZXZlciBnb25uYSBzYXkg
+Z29vZGJ5ZQpOZXZlciBnb25uYSB0ZWxsIGEgbGllIGFuZCBodXJ0IHlvdQoKV2UndmUga25vdyBl
+YWNoIG90aGVyIGZvciBzbyBsb25nCllvdXIgaGVhcnQncyBiZWVuIGFjaGluJyBidXQgeW91J3Jl
+IHRvbyBzaHkgdG8gc2F5IGl0Ckluc2lkZSB3ZSBib3RoIGtub3cgd2hhdCdzIGJlZW4gZ29pbmcg
+b24KV2Uga25vdyB0aGUgZ2FtZSBhbmQgd2UncmUgZ29ubmEgcGxheSBpdApBbmQgaWYgeW91IGFz
+ayBtZSBob3cgSSdtIGZlZWxpbicKRG9uJ3QgdGVsbCBtZSB5b3UncmUgdG9vIGJsaW5kIHRvIHNl
+ZQoKKENob3J1cyB4MikKCihHaXZlIHlvdSB1cCwgZ2l2ZSB5b3UgdXApCk5ldmVyIGdvbm5hIGdp
+dmUsIG5ldmVyIGdvbm5hIGdpdmUKKEdpdmUgeW91IHVwKQpOZXZlciBnb25uYSBnaXZlLCBuZXZl
+ciBnb25uYSBnaXZlCihHaXZlIHlvdSB1cCkKCldlJ3ZlIGtub3cgZWFjaCBvdGhlciBmb3Igc28g
+bG9uZwpZb3VyIGhlYXJ0J3MgYmVlbiBhY2hpbicgYnV0IHlvdSdyZSB0b28gc2h5IHRvIHNheSBp
+dApJbnNpZGUgd2UgYm90aCBrbm93IHdoYXQncyBiZWVuIGdvaW5nIG9uCldlIGtub3cgdGhlIGdh
+bWUgYW5kIHdlJ3JlIGdvbm5hIHBsYXkgaXQKSSBqdXN0IHdhbm5hIHRlbGwgeW91IGhvdyBJJ20g
+ZmVlbGluJwpHb3R0YSBtYWtlIHlvdSB1bmRlcnN0YW5kCgooQ2hvcnVzIHgzKQo=
diff --git a/mediagoblin/tests/test_exif/empty.jpg b/mediagoblin/tests/test_exif/empty.jpg
new file mode 100644
index 00000000..37533af5
--- /dev/null
+++ b/mediagoblin/tests/test_exif/empty.jpg
Binary files differ
diff --git a/mediagoblin/tests/test_exif/good.jpg b/mediagoblin/tests/test_exif/good.jpg
new file mode 100644
index 00000000..0ee956fe
--- /dev/null
+++ b/mediagoblin/tests/test_exif/good.jpg
Binary files differ
diff --git a/mediagoblin/tests/test_exif/has-gps.jpg b/mediagoblin/tests/test_exif/has-gps.jpg
new file mode 100644
index 00000000..f6f39d86
--- /dev/null
+++ b/mediagoblin/tests/test_exif/has-gps.jpg
Binary files differ
diff --git a/mediagoblin/tests/test_globals.py b/mediagoblin/tests/test_globals.py
new file mode 100644
index 00000000..fe3088f8
--- /dev/null
+++ b/mediagoblin/tests/test_globals.py
@@ -0,0 +1,42 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import pytest
+
+from mediagoblin import mg_globals
+
+
+class TestGlobals(object):
+ def setup(self):
+ self.old_database = mg_globals.database
+
+ def teardown(self):
+ mg_globals.database = self.old_database
+
+ def test_setup_globals(self):
+ mg_globals.setup_globals(
+ database='my favorite database!',
+ public_store='my favorite public_store!',
+ queue_store='my favorite queue_store!')
+
+ assert mg_globals.database == 'my favorite database!'
+ assert mg_globals.public_store == 'my favorite public_store!'
+ assert mg_globals.queue_store == 'my favorite queue_store!'
+
+ pytest.raises(
+ AssertionError,
+ mg_globals.setup_globals,
+ no_such_global_foo="Dummy")
diff --git a/mediagoblin/tests/test_http_callback.py b/mediagoblin/tests/test_http_callback.py
new file mode 100644
index 00000000..a0511af7
--- /dev/null
+++ b/mediagoblin/tests/test_http_callback.py
@@ -0,0 +1,83 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+
+import pytest
+from urlparse import urlparse, parse_qs
+
+from mediagoblin import mg_globals
+from mediagoblin.tools import processing
+from mediagoblin.tests.tools import fixture_add_user
+from mediagoblin.tests.test_submission import GOOD_PNG
+from mediagoblin.tests import test_oauth as oauth
+
+
+class TestHTTPCallback(object):
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
+ self.test_app = test_app
+
+ self.db = mg_globals.database
+
+ self.user_password = u'secret'
+ self.user = fixture_add_user(u'call_back', self.user_password)
+
+ self.login()
+
+ def login(self):
+ self.test_app.post('/auth/login/', {
+ 'username': self.user.username,
+ 'password': self.user_password})
+
+ def get_access_token(self, client_id, client_secret, code):
+ response = self.test_app.get('/oauth/access_token', {
+ 'code': code,
+ 'client_id': client_id,
+ 'client_secret': client_secret})
+
+ response_data = json.loads(response.body)
+
+ return response_data['access_token']
+
+ def test_callback(self):
+ ''' Test processing HTTP callback '''
+ self.oauth = oauth.TestOAuth()
+ self.oauth.setup(self.test_app)
+
+ redirect, client_id = self.oauth.test_4_authorize_confidential_client()
+
+ code = parse_qs(urlparse(redirect.location).query)['code'][0]
+
+ client = self.db.OAuthClient.query.filter(
+ self.db.OAuthClient.identifier == unicode(client_id)).first()
+
+ client_secret = client.secret
+
+ access_token = self.get_access_token(client_id, client_secret, code)
+
+ callback_url = 'https://foo.example?secrettestmediagoblinparam'
+
+ self.test_app.post('/api/submit?client_id={0}&access_token={1}\
+&client_secret={2}'.format(
+ client_id,
+ access_token,
+ client_secret), {
+ 'title': 'Test',
+ 'callback_url': callback_url},
+ upload_files=[('file', GOOD_PNG)])
+
+ assert processing.TESTS_CALLBACKS[callback_url]['state'] == u'processed'
diff --git a/mediagoblin/tests/test_messages.py b/mediagoblin/tests/test_messages.py
new file mode 100644
index 00000000..22f9e800
--- /dev/null
+++ b/mediagoblin/tests/test_messages.py
@@ -0,0 +1,50 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin import messages
+from mediagoblin.tools import template
+
+
+def test_messages(test_app):
+ """
+ Added messages should show up in the request.session,
+ fetched messages should be the same as the added ones,
+ and fetching should clear the message list.
+ """
+ # Aquire a request object
+ test_app.get('/')
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html']
+ request = context['request']
+
+ # The message queue should be empty
+ assert request.session.get('messages', []) == []
+
+ # First of all, we should clear the messages queue
+ messages.clear_add_message()
+ # Adding a message should modify the session accordingly
+ messages.add_message(request, 'herp_derp', 'First!')
+ test_msg_queue = [{'text': 'First!', 'level': 'herp_derp'}]
+
+ # Alternative tests to the following, test divided in two steps:
+ # assert request.session['messages'] == test_msg_queue
+ # 1. Tests if add_message worked
+ assert messages.ADD_MESSAGE_TEST[-1] == test_msg_queue
+ # 2. Tests if add_message updated session information
+ assert messages.ADD_MESSAGE_TEST[-1] == request.session['messages']
+
+ # fetch_messages should return and empty the queue
+ assert messages.fetch_messages(request) == test_msg_queue
+ assert request.session.get('messages') == []
diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini
new file mode 100644
index 00000000..0466b53b
--- /dev/null
+++ b/mediagoblin/tests/test_mgoblin_app.ini
@@ -0,0 +1,33 @@
+[mediagoblin]
+direct_remote_path = /test_static/
+email_sender_address = "notice@mediagoblin.example.org"
+email_debug_mode = true
+
+# TODO: Switch to using an in-memory database
+sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db"
+
+# tag parsing
+tags_max_length = 50
+
+# So we can start to test attachments:
+allow_attachments = True
+
+media_types = mediagoblin.media_types.image, mediagoblin.media_types.pdf
+
+[storage:publicstore]
+base_dir = %(here)s/user_dev/media/public
+base_url = /mgoblin_media/
+
+[storage:queuestore]
+base_dir = %(here)s/user_dev/media/queue
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db"
+BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db"
+
+[plugins]
+[[mediagoblin.plugins.api]]
+[[mediagoblin.plugins.oauth]]
+[[mediagoblin.plugins.httpapiauth]]
+[[mediagoblin.plugins.piwigo]]
diff --git a/mediagoblin/tests/test_misc.py b/mediagoblin/tests/test_misc.py
new file mode 100644
index 00000000..755d863f
--- /dev/null
+++ b/mediagoblin/tests/test_misc.py
@@ -0,0 +1,91 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.db.base import Session
+from mediagoblin.db.models import User, MediaEntry, MediaComment
+from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry
+
+
+def test_404_for_non_existent(test_app):
+ res = test_app.get('/does-not-exist/', expect_errors=True)
+ assert res.status_int == 404
+
+
+def test_user_deletes_other_comments(test_app):
+ user_a = fixture_add_user(u"chris_a")
+ user_b = fixture_add_user(u"chris_b")
+
+ media_a = fixture_media_entry(uploader=user_a.id, save=False)
+ media_b = fixture_media_entry(uploader=user_b.id, save=False)
+ Session.add(media_a)
+ Session.add(media_b)
+ Session.flush()
+
+ # Create all 4 possible comments:
+ for u_id in (user_a.id, user_b.id):
+ for m_id in (media_a.id, media_b.id):
+ cmt = MediaComment()
+ cmt.media_entry = m_id
+ cmt.author = u_id
+ cmt.content = u"Some Comment"
+ Session.add(cmt)
+
+ Session.flush()
+
+ usr_cnt1 = User.query.count()
+ med_cnt1 = MediaEntry.query.count()
+ cmt_cnt1 = MediaComment.query.count()
+
+ User.query.get(user_a.id).delete(commit=False)
+
+ usr_cnt2 = User.query.count()
+ med_cnt2 = MediaEntry.query.count()
+ cmt_cnt2 = MediaComment.query.count()
+
+ # One user deleted
+ assert usr_cnt2 == usr_cnt1 - 1
+ # One media gone
+ assert med_cnt2 == med_cnt1 - 1
+ # Three of four comments gone.
+ assert cmt_cnt2 == cmt_cnt1 - 3
+
+ User.query.get(user_b.id).delete()
+
+ usr_cnt2 = User.query.count()
+ med_cnt2 = MediaEntry.query.count()
+ cmt_cnt2 = MediaComment.query.count()
+
+ # All users gone
+ assert usr_cnt2 == usr_cnt1 - 2
+ # All media gone
+ assert med_cnt2 == med_cnt1 - 2
+ # All comments gone
+ assert cmt_cnt2 == cmt_cnt1 - 4
+
+
+def test_media_deletes_broken_attachment(test_app):
+ user_a = fixture_add_user(u"chris_a")
+
+ media = fixture_media_entry(uploader=user_a.id, save=False)
+ media.attachment_files.append(dict(
+ name=u"some name",
+ filepath=[u"does", u"not", u"exist"],
+ ))
+ Session.add(media)
+ Session.flush()
+
+ MediaEntry.query.get(media.id).delete()
+ User.query.get(user_a.id).delete()
diff --git a/mediagoblin/tests/test_modelmethods.py b/mediagoblin/tests/test_modelmethods.py
new file mode 100644
index 00000000..427aa47c
--- /dev/null
+++ b/mediagoblin/tests/test_modelmethods.py
@@ -0,0 +1,167 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Maybe not every model needs a test, but some models have special
+# methods, and so it makes sense to test them here.
+
+from mediagoblin.db.base import Session
+from mediagoblin.db.models import MediaEntry
+
+from mediagoblin.tests.tools import fixture_add_user
+
+import mock
+
+
+class FakeUUID(object):
+ hex = 'testtest-test-test-test-testtesttest'
+
+UUID_MOCK = mock.Mock(return_value=FakeUUID())
+
+
+class TestMediaEntrySlugs(object):
+ def _setup(self):
+ self.chris_user = fixture_add_user(u'chris')
+ self.emily_user = fixture_add_user(u'emily')
+ self.existing_entry = self._insert_media_entry_fixture(
+ title=u"Beware, I exist!",
+ slug=u"beware-i-exist")
+
+ def _insert_media_entry_fixture(self, title=None, slug=None, this_id=None,
+ uploader=None, save=True):
+ entry = MediaEntry()
+ entry.title = title or u"Some title"
+ entry.slug = slug
+ entry.id = this_id
+ entry.uploader = uploader or self.chris_user.id
+ entry.media_type = u'image'
+
+ if save:
+ entry.save()
+
+ return entry
+
+ def test_unique_slug_from_title(self, test_app):
+ self._setup()
+
+ entry = self._insert_media_entry_fixture(u"Totally unique slug!", save=False)
+ entry.generate_slug()
+ assert entry.slug == u'totally-unique-slug'
+
+ def test_old_good_unique_slug(self, test_app):
+ self._setup()
+
+ entry = self._insert_media_entry_fixture(
+ u"A title here", u"a-different-slug-there", save=False)
+ entry.generate_slug()
+ assert entry.slug == u"a-different-slug-there"
+
+ def test_old_weird_slug(self, test_app):
+ self._setup()
+
+ entry = self._insert_media_entry_fixture(
+ slug=u"wowee!!!!!", save=False)
+ entry.generate_slug()
+ assert entry.slug == u"wowee"
+
+ def test_existing_slug_use_id(self, test_app):
+ self._setup()
+
+ entry = self._insert_media_entry_fixture(
+ u"Beware, I exist!!", this_id=9000, save=False)
+ entry.generate_slug()
+ assert entry.slug == u"beware-i-exist-9000"
+
+ def test_existing_slug_cant_use_id(self, test_app):
+ self._setup()
+
+ # Getting tired of dealing with test_app and this mock.patch
+ # thing conflicting, getting lazy.
+ @mock.patch('uuid.uuid4', UUID_MOCK)
+ def _real_test():
+ # This one grabs the nine thousand slug
+ self._insert_media_entry_fixture(
+ slug=u"beware-i-exist-9000")
+
+ entry = self._insert_media_entry_fixture(
+ u"Beware, I exist!!", this_id=9000, save=False)
+ entry.generate_slug()
+ assert entry.slug == u"beware-i-exist-test"
+
+ _real_test()
+
+ def test_existing_slug_cant_use_id_extra_junk(self, test_app):
+ self._setup()
+
+ # Getting tired of dealing with test_app and this mock.patch
+ # thing conflicting, getting lazy.
+ @mock.patch('uuid.uuid4', UUID_MOCK)
+ def _real_test():
+ # This one grabs the nine thousand slug
+ self._insert_media_entry_fixture(
+ slug=u"beware-i-exist-9000")
+
+ # This one grabs makes sure the annoyance doesn't stop
+ self._insert_media_entry_fixture(
+ slug=u"beware-i-exist-test")
+
+ entry = self._insert_media_entry_fixture(
+ u"Beware, I exist!!", this_id=9000, save=False)
+ entry.generate_slug()
+ assert entry.slug == u"beware-i-exist-testtest"
+
+ _real_test()
+
+ def test_garbage_slug(self, test_app):
+ """
+ Titles that sound totally like Q*Bert shouldn't have slugs at
+ all. We'll just reference them by id.
+
+ ,
+ / \ (@!#?@!)
+ |\,/| ,-, /
+ | |#| ( ")~
+ / \|/ \ L L
+ |\,/|\,/|
+ | |#, |#|
+ / \|/ \|/ \
+ |\,/|\,/|\,/|
+ | |#| |#| |#|
+ / \|/ \|/ \|/ \
+ |\,/|\,/|\,/|\,/|
+ | |#| |#| |#| |#|
+ \|/ \|/ \|/ \|/
+ """
+ self._setup()
+
+ qbert_entry = self._insert_media_entry_fixture(
+ u"@!#?@!", save=False)
+ qbert_entry.generate_slug()
+ assert qbert_entry.slug is None
+
+
+def test_media_data_init(test_app):
+ Session.rollback()
+ Session.remove()
+ media = MediaEntry()
+ media.media_type = u"mediagoblin.media_types.image"
+ assert media.media_data is None
+ media.media_data_init()
+ assert media.media_data is not None
+ obj_in_session = 0
+ for obj in Session():
+ obj_in_session += 1
+ print repr(obj)
+ assert obj_in_session == 0
diff --git a/mediagoblin/tests/test_oauth.py b/mediagoblin/tests/test_oauth.py
new file mode 100644
index 00000000..ea3bd798
--- /dev/null
+++ b/mediagoblin/tests/test_oauth.py
@@ -0,0 +1,222 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import logging
+
+import pytest
+from urlparse import parse_qs, urlparse
+
+from mediagoblin import mg_globals
+from mediagoblin.tools import template, pluginapi
+from mediagoblin.tests.tools import fixture_add_user
+
+
+_log = logging.getLogger(__name__)
+
+
+class TestOAuth(object):
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
+ self.test_app = test_app
+
+ self.db = mg_globals.database
+
+ self.pman = pluginapi.PluginManager()
+
+ self.user_password = u'4cc355_70k3N'
+ self.user = fixture_add_user(u'joauth', self.user_password)
+
+ self.login()
+
+ def login(self):
+ self.test_app.post(
+ '/auth/login/', {
+ 'username': self.user.username,
+ 'password': self.user_password})
+
+ def register_client(self, name, client_type, description=None,
+ redirect_uri=''):
+ return self.test_app.post(
+ '/oauth/client/register', {
+ 'name': name,
+ 'description': description,
+ 'type': client_type,
+ 'redirect_uri': redirect_uri})
+
+ def get_context(self, template_name):
+ return template.TEMPLATE_TEST_CONTEXT[template_name]
+
+ def test_1_public_client_registration_without_redirect_uri(self):
+ ''' Test 'public' OAuth client registration without any redirect uri '''
+ response = self.register_client(
+ u'OMGOMGOMG', 'public', 'OMGOMG Apache License v2')
+
+ ctx = self.get_context('oauth/client/register.html')
+
+ client = self.db.OAuthClient.query.filter(
+ self.db.OAuthClient.name == u'OMGOMGOMG').first()
+
+ assert response.status_int == 200
+
+ # Should display an error
+ assert len(ctx['form'].redirect_uri.errors)
+
+ # Should not pass through
+ assert not client
+
+ def test_2_successful_public_client_registration(self):
+ ''' Successfully register a public client '''
+ uri = 'http://foo.example'
+ self.register_client(
+ u'OMGOMG', 'public', 'OMG!', uri)
+
+ client = self.db.OAuthClient.query.filter(
+ self.db.OAuthClient.name == u'OMGOMG').first()
+
+ # redirect_uri should be set
+ assert client.redirect_uri == uri
+
+ # Client should have been registered
+ assert client
+
+ def test_3_successful_confidential_client_reg(self):
+ ''' Register a confidential OAuth client '''
+ response = self.register_client(
+ u'GMOGMO', 'confidential', 'NO GMO!')
+
+ assert response.status_int == 302
+
+ client = self.db.OAuthClient.query.filter(
+ self.db.OAuthClient.name == u'GMOGMO').first()
+
+ # Client should have been registered
+ assert client
+
+ return client
+
+ def test_4_authorize_confidential_client(self):
+ ''' Authorize a confidential client as a logged in user '''
+ client = self.test_3_successful_confidential_client_reg()
+
+ client_identifier = client.identifier
+
+ redirect_uri = 'https://foo.example'
+ response = self.test_app.get('/oauth/authorize', {
+ 'client_id': client.identifier,
+ 'scope': 'all',
+ 'redirect_uri': redirect_uri})
+
+ # User-agent should NOT be redirected
+ assert response.status_int == 200
+
+ ctx = self.get_context('oauth/authorize.html')
+
+ form = ctx['form']
+
+ # Short for client authorization post reponse
+ capr = self.test_app.post(
+ '/oauth/client/authorize', {
+ 'client_id': form.client_id.data,
+ 'allow': 'Allow',
+ 'next': form.next.data})
+
+ assert capr.status_int == 302
+
+ authorization_response = capr.follow()
+
+ assert authorization_response.location.startswith(redirect_uri)
+
+ return authorization_response, client_identifier
+
+ def get_code_from_redirect_uri(self, uri):
+ ''' Get the value of ?code= from an URI '''
+ return parse_qs(urlparse(uri).query)['code'][0]
+
+ def test_token_endpoint_successful_confidential_request(self):
+ ''' Successful request against token endpoint '''
+ code_redirect, client_id = self.test_4_authorize_confidential_client()
+
+ code = self.get_code_from_redirect_uri(code_redirect.location)
+
+ client = self.db.OAuthClient.query.filter(
+ self.db.OAuthClient.identifier == unicode(client_id)).first()
+
+ token_res = self.test_app.get('/oauth/access_token?client_id={0}&\
+code={1}&client_secret={2}'.format(client_id, code, client.secret))
+
+ assert token_res.status_int == 200
+
+ token_data = json.loads(token_res.body)
+
+ assert not 'error' in token_data
+ assert 'access_token' in token_data
+ assert 'token_type' in token_data
+ assert 'expires_in' in token_data
+ assert type(token_data['expires_in']) == int
+ assert token_data['expires_in'] > 0
+
+ # There should be a refresh token provided in the token data
+ assert len(token_data['refresh_token'])
+
+ return client_id, token_data
+
+ def test_token_endpont_missing_id_confidential_request(self):
+ ''' Unsuccessful request against token endpoint, missing client_id '''
+ code_redirect, client_id = self.test_4_authorize_confidential_client()
+
+ code = self.get_code_from_redirect_uri(code_redirect.location)
+
+ client = self.db.OAuthClient.query.filter(
+ self.db.OAuthClient.identifier == unicode(client_id)).first()
+
+ token_res = self.test_app.get('/oauth/access_token?\
+code={0}&client_secret={1}'.format(code, client.secret))
+
+ assert token_res.status_int == 200
+
+ token_data = json.loads(token_res.body)
+
+ assert 'error' in token_data
+ assert not 'access_token' in token_data
+ assert token_data['error'] == 'invalid_request'
+ assert len(token_data['error_description'])
+
+ def test_refresh_token(self):
+ ''' Try to get a new access token using the refresh token '''
+ # Get an access token and a refresh token
+ client_id, token_data =\
+ self.test_token_endpoint_successful_confidential_request()
+
+ client = self.db.OAuthClient.query.filter(
+ self.db.OAuthClient.identifier == client_id).first()
+
+ token_res = self.test_app.get('/oauth/access_token',
+ {'refresh_token': token_data['refresh_token'],
+ 'client_id': client_id,
+ 'client_secret': client.secret
+ })
+
+ assert token_res.status_int == 200
+
+ new_token_data = json.loads(token_res.body)
+
+ assert not 'error' in new_token_data
+ assert 'access_token' in new_token_data
+ assert 'token_type' in new_token_data
+ assert 'expires_in' in new_token_data
+ assert type(new_token_data['expires_in']) == int
+ assert new_token_data['expires_in'] > 0
diff --git a/mediagoblin/tests/test_paste.ini b/mediagoblin/tests/test_paste.ini
new file mode 100644
index 00000000..a9595432
--- /dev/null
+++ b/mediagoblin/tests/test_paste.ini
@@ -0,0 +1,40 @@
+[DEFAULT]
+debug = true
+
+[composite:main]
+use = egg:Paste#urlmap
+/ = mediagoblin
+/mgoblin_media/ = publicstore_serve
+/test_static/ = mediagoblin_static
+/theme_static/ = theme_static
+/plugin_static/ = plugin_static
+
+[app:mediagoblin]
+use = egg:mediagoblin#app
+config = %(here)s/mediagoblin.ini
+
+[app:publicstore_serve]
+use = egg:Paste#static
+document_root = %(here)s/user_dev/media/public
+
+[app:mediagoblin_static]
+use = egg:Paste#static
+document_root = %(here)s/mediagoblin/static/
+
+[app:theme_static]
+use = egg:Paste#static
+document_root = %(here)s/user_dev/theme_static/
+cache_max_age = 86400
+
+[app:plugin_static]
+use = egg:Paste#static
+document_root = %(here)s/user_dev/plugin_static/
+cache_max_age = 86400
+
+[celery]
+CELERY_ALWAYS_EAGER = true
+
+[server:main]
+use = egg:Paste#http
+host = 127.0.0.1
+port = 6543
diff --git a/mediagoblin/tests/test_pdf.py b/mediagoblin/tests/test_pdf.py
new file mode 100644
index 00000000..b4d1940a
--- /dev/null
+++ b/mediagoblin/tests/test_pdf.py
@@ -0,0 +1,39 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import tempfile
+import shutil
+import os
+import pytest
+
+from mediagoblin.media_types.pdf.processing import (
+ pdf_info, check_prerequisites, create_pdf_thumb)
+from .resources import GOOD_PDF as GOOD
+
+
+@pytest.mark.skipif("not check_prerequisites()")
+def test_pdf():
+ good_dict = {'pdf_version_major': 1, 'pdf_title': '',
+ 'pdf_page_size_width': 612, 'pdf_author': '',
+ 'pdf_keywords': '', 'pdf_pages': 10,
+ 'pdf_producer': 'dvips + GNU Ghostscript 7.05',
+ 'pdf_version_minor': 3,
+ 'pdf_creator': 'LaTeX with hyperref package',
+ 'pdf_page_size_height': 792}
+ assert pdf_info(GOOD) == good_dict
+ temp_dir = tempfile.mkdtemp()
+ create_pdf_thumb(GOOD, os.path.join(temp_dir, 'good_256_256.png'), 256, 256)
+ shutil.rmtree(temp_dir)
diff --git a/mediagoblin/tests/test_piwigo.py b/mediagoblin/tests/test_piwigo.py
new file mode 100644
index 00000000..16ad0111
--- /dev/null
+++ b/mediagoblin/tests/test_piwigo.py
@@ -0,0 +1,71 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import pytest
+from .tools import fixture_add_user
+
+
+XML_PREFIX = "<?xml version='1.0' encoding='utf-8'?>\n"
+
+
+class Test_PWG(object):
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
+ self.test_app = test_app
+
+ fixture_add_user()
+
+ self.username = u"chris"
+ self.password = "toast"
+
+ def do_post(self, method, params):
+ params["method"] = method
+ return self.test_app.post("/api/piwigo/ws.php", params)
+
+ def do_get(self, method, params=None):
+ if params is None:
+ params = {}
+ params["method"] = method
+ return self.test_app.get("/api/piwigo/ws.php", params)
+
+ def test_session(self):
+ resp = self.do_post("pwg.session.login",
+ {"username": u"nouser", "password": "wrong"})
+ assert resp.body == XML_PREFIX \
+ + '<rsp stat="fail"><err code="999" msg="Invalid username/password"/></rsp>'
+
+ resp = self.do_post("pwg.session.login",
+ {"username": self.username, "password": "wrong"})
+ assert resp.body == XML_PREFIX \
+ + '<rsp stat="fail"><err code="999" msg="Invalid username/password"/></rsp>'
+
+ resp = self.do_get("pwg.session.getStatus")
+ assert resp.body == XML_PREFIX \
+ + '<rsp stat="ok"><username>guest</username></rsp>'
+
+ resp = self.do_post("pwg.session.login",
+ {"username": self.username, "password": self.password})
+ assert resp.body == XML_PREFIX + '<rsp stat="ok">1</rsp>'
+
+ resp = self.do_get("pwg.session.getStatus")
+ assert resp.body == XML_PREFIX \
+ + '<rsp stat="ok"><username>chris</username></rsp>'
+
+ self.do_get("pwg.session.logout")
+
+ resp = self.do_get("pwg.session.getStatus")
+ assert resp.body == XML_PREFIX \
+ + '<rsp stat="ok"><username>guest</username></rsp>'
diff --git a/mediagoblin/tests/test_pluginapi.py b/mediagoblin/tests/test_pluginapi.py
new file mode 100644
index 00000000..eae0ce15
--- /dev/null
+++ b/mediagoblin/tests/test_pluginapi.py
@@ -0,0 +1,466 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import json
+import sys
+
+from configobj import ConfigObj
+import pytest
+import pkg_resources
+from validate import VdtTypeError
+
+from mediagoblin import mg_globals
+from mediagoblin.init.plugins import setup_plugins
+from mediagoblin.init.config import read_mediagoblin_config
+from mediagoblin.gmg_commands.assetlink import link_plugin_assets
+from mediagoblin.tools import pluginapi
+from mediagoblin.tests.tools import get_app
+from mediagoblin.tools.common import CollectingPrinter
+
+
+def with_cleanup(*modules_to_delete):
+ def _with_cleanup(fun):
+ """Wrapper that saves and restores mg_globals"""
+ def _with_cleanup_inner(*args, **kwargs):
+ old_app_config = mg_globals.app_config
+ old_global_config = mg_globals.global_config
+ # Need to delete icky modules before and after so as to make
+ # sure things work correctly.
+ for module in modules_to_delete:
+ try:
+ del sys.modules[module]
+ except KeyError:
+ pass
+ # The plugin cache gets populated as a side-effect of
+ # importing, so it's best to clear it before and after a test.
+ pman = pluginapi.PluginManager()
+ pman.clear()
+ try:
+ return fun(*args, **kwargs)
+ finally:
+ mg_globals.app_config = old_app_config
+ mg_globals.global_config = old_global_config
+ # Need to delete icky modules before and after so as to make
+ # sure things work correctly.
+ for module in modules_to_delete:
+ try:
+ del sys.modules[module]
+ except KeyError:
+ pass
+ pman.clear()
+
+ _with_cleanup_inner.__name__ = fun.__name__
+ return _with_cleanup_inner
+ return _with_cleanup
+
+
+def build_config(sections):
+ """Builds a ConfigObj object with specified data
+
+ :arg sections: list of ``(section_name, section_data,
+ subsection_list)`` tuples where section_data is a dict and
+ subsection_list is a list of ``(section_name, section_data,
+ subsection_list)``, ...
+
+ For example:
+
+ >>> build_config([
+ ... ('mediagoblin', {'key1': 'val1'}, []),
+ ... ('section2', {}, [
+ ... ('subsection1', {}, [])
+ ... ])
+ ... ])
+ """
+ cfg = ConfigObj()
+ cfg.filename = 'foo'
+ def _iter_section(cfg, section_list):
+ for section_name, data, subsection_list in section_list:
+ cfg[section_name] = data
+ _iter_section(cfg[section_name], subsection_list)
+
+ _iter_section(cfg, sections)
+ return cfg
+
+
+@with_cleanup()
+def test_no_plugins():
+ """Run setup_plugins with no plugins in config"""
+ cfg = build_config([('mediagoblin', {}, [])])
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ pman = pluginapi.PluginManager()
+ setup_plugins()
+
+ # Make sure we didn't load anything.
+ assert len(pman.plugins) == 0
+
+
+@with_cleanup('mediagoblin.plugins.sampleplugin')
+def test_one_plugin():
+ """Run setup_plugins with a single working plugin"""
+ cfg = build_config([
+ ('mediagoblin', {}, []),
+ ('plugins', {}, [
+ ('mediagoblin.plugins.sampleplugin', {}, [])
+ ])
+ ])
+
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ pman = pluginapi.PluginManager()
+ setup_plugins()
+
+ # Make sure we only found one plugin
+ assert len(pman.plugins) == 1
+ # Make sure the plugin is the one we think it is.
+ assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
+ # Make sure there was one hook registered
+ assert len(pman.hooks) == 1
+ # Make sure _setup_plugin_called was called once
+ import mediagoblin.plugins.sampleplugin
+ assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
+
+
+@with_cleanup('mediagoblin.plugins.sampleplugin')
+def test_same_plugin_twice():
+ """Run setup_plugins with a single working plugin twice"""
+ cfg = build_config([
+ ('mediagoblin', {}, []),
+ ('plugins', {}, [
+ ('mediagoblin.plugins.sampleplugin', {}, []),
+ ('mediagoblin.plugins.sampleplugin', {}, []),
+ ])
+ ])
+
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ pman = pluginapi.PluginManager()
+ setup_plugins()
+
+ # Make sure we only found one plugin
+ assert len(pman.plugins) == 1
+ # Make sure the plugin is the one we think it is.
+ assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin'
+ # Make sure there was one hook registered
+ assert len(pman.hooks) == 1
+ # Make sure _setup_plugin_called was called once
+ import mediagoblin.plugins.sampleplugin
+ assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1
+
+
+@with_cleanup()
+def test_disabled_plugin():
+ """Run setup_plugins with a single working plugin twice"""
+ cfg = build_config([
+ ('mediagoblin', {}, []),
+ ('plugins', {}, [
+ ('-mediagoblin.plugins.sampleplugin', {}, []),
+ ])
+ ])
+
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ pman = pluginapi.PluginManager()
+ setup_plugins()
+
+ # Make sure we didn't load the plugin
+ assert len(pman.plugins) == 0
+
+
+CONFIG_ALL_CALLABLES = [
+ ('mediagoblin', {}, []),
+ ('plugins', {}, [
+ ('mediagoblin.tests.testplugins.callables1', {}, []),
+ ('mediagoblin.tests.testplugins.callables2', {}, []),
+ ('mediagoblin.tests.testplugins.callables3', {}, []),
+ ])
+ ]
+
+
+@with_cleanup()
+def test_hook_handle():
+ """
+ Test the hook_handle method
+ """
+ cfg = build_config(CONFIG_ALL_CALLABLES)
+
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ setup_plugins()
+
+ # Just one hook provided
+ call_log = []
+ assert pluginapi.hook_handle(
+ "just_one", call_log) == "Called just once"
+ assert call_log == ["expect this one call"]
+
+ # Nothing provided and unhandled not okay
+ call_log = []
+ pluginapi.hook_handle(
+ "nothing_handling", call_log) == None
+ assert call_log == []
+
+ # Nothing provided and unhandled okay
+ call_log = []
+ assert pluginapi.hook_handle(
+ "nothing_handling", call_log, unhandled_okay=True) is None
+ assert call_log == []
+
+ # Multiple provided, go with the first!
+ call_log = []
+ assert pluginapi.hook_handle(
+ "multi_handle", call_log) == "the first returns"
+ assert call_log == ["Hi, I'm the first"]
+
+ # Multiple provided, one has CantHandleIt
+ call_log = []
+ assert pluginapi.hook_handle(
+ "multi_handle_with_canthandle",
+ call_log) == "the second returns"
+ assert call_log == ["Hi, I'm the second"]
+
+
+@with_cleanup()
+def test_hook_runall():
+ """
+ Test the hook_runall method
+ """
+ cfg = build_config(CONFIG_ALL_CALLABLES)
+
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ setup_plugins()
+
+ # Just one hook, check results
+ call_log = []
+ assert pluginapi.hook_runall(
+ "just_one", call_log) == ["Called just once"]
+ assert call_log == ["expect this one call"]
+
+ # None provided, check results
+ call_log = []
+ assert pluginapi.hook_runall(
+ "nothing_handling", call_log) == []
+ assert call_log == []
+
+ # Multiple provided, check results
+ call_log = []
+ assert pluginapi.hook_runall(
+ "multi_handle", call_log) == [
+ "the first returns",
+ "the second returns",
+ "the third returns",
+ ]
+ assert call_log == [
+ "Hi, I'm the first",
+ "Hi, I'm the second",
+ "Hi, I'm the third"]
+
+ # Multiple provided, one has CantHandleIt, check results
+ call_log = []
+ assert pluginapi.hook_runall(
+ "multi_handle_with_canthandle", call_log) == [
+ "the second returns",
+ "the third returns",
+ ]
+ assert call_log == [
+ "Hi, I'm the second",
+ "Hi, I'm the third"]
+
+
+@with_cleanup()
+def test_hook_transform():
+ """
+ Test the hook_transform method
+ """
+ cfg = build_config(CONFIG_ALL_CALLABLES)
+
+ mg_globals.app_config = cfg['mediagoblin']
+ mg_globals.global_config = cfg
+
+ setup_plugins()
+
+ assert pluginapi.hook_transform(
+ "expand_tuple", (-1, 0)) == (-1, 0, 1, 2, 3)
+
+
+def test_plugin_config():
+ """
+ Make sure plugins can set up their own config
+ """
+ config, validation_result = read_mediagoblin_config(
+ pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'appconfig_plugin_specs.ini'))
+
+ pluginspec_section = config['plugins'][
+ 'mediagoblin.tests.testplugins.pluginspec']
+ assert pluginspec_section['some_string'] == 'not blork'
+ assert pluginspec_section['dont_change_me'] == 'still the default'
+
+ # Make sure validation works... this should be an error
+ assert isinstance(
+ validation_result[
+ 'plugins'][
+ 'mediagoblin.tests.testplugins.pluginspec'][
+ 'some_int'],
+ VdtTypeError)
+
+ # the callables thing shouldn't really have anything though.
+ assert len(config['plugins'][
+ 'mediagoblin.tests.testplugins.callables1']) == 0
+
+
+@pytest.fixture()
+def context_modified_app(request):
+ """
+ Get a MediaGoblin app fixture using appconfig_context_modified.ini
+ """
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'appconfig_context_modified.ini'))
+
+
+def test_modify_context(context_modified_app):
+ """
+ Test that we can modify both the view/template specific and
+ global contexts for templates.
+ """
+ # Specific thing passed into a page
+ result = context_modified_app.get("/modify_context/specific/")
+ assert result.body.strip() == """Specific page!
+
+specific thing: in yer specificpage
+global thing: globally appended!
+something: orother
+doubleme: happyhappy"""
+
+ # General test, should have global context variable only
+ result = context_modified_app.get("/modify_context/")
+ assert result.body.strip() == """General page!
+
+global thing: globally appended!
+lol: cats
+doubleme: joyjoy"""
+
+
+@pytest.fixture()
+def static_plugin_app(request):
+ """
+ Get a MediaGoblin app fixture using appconfig_static_plugin.ini
+ """
+ return get_app(
+ request,
+ mgoblin_config=pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'appconfig_static_plugin.ini'))
+
+
+def test_plugin_assetlink(static_plugin_app):
+ """
+ Test that the assetlink command works correctly
+ """
+ linked_assets_dir = mg_globals.app_config['plugin_linked_assets_dir']
+ plugin_link_dir = os.path.join(
+ linked_assets_dir.rstrip(os.path.sep),
+ 'staticstuff')
+
+ plugin_statics = pluginapi.hook_runall("static_setup")
+ assert len(plugin_statics) == 1
+ plugin_static = plugin_statics[0]
+
+ def run_assetlink():
+ printer = CollectingPrinter()
+
+ link_plugin_assets(
+ plugin_static, linked_assets_dir, printer)
+
+ return printer
+
+ # it shouldn't exist yet
+ assert not os.path.lexists(plugin_link_dir)
+
+ # link dir doesn't exist, link it
+ result = run_assetlink().collection[0]
+ assert result == \
+ 'Linked asset directory for plugin "staticstuff":\n %s\nto:\n %s\n' % (
+ plugin_static.file_path.rstrip(os.path.sep),
+ plugin_link_dir)
+ assert os.path.lexists(plugin_link_dir)
+ assert os.path.islink(plugin_link_dir)
+ assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
+
+ # link dir exists, leave it alone
+ # (and it should exist still since we just ran it..)
+ result = run_assetlink().collection[0]
+ assert result == 'Skipping "staticstuff"; already set up.\n'
+ assert os.path.lexists(plugin_link_dir)
+ assert os.path.islink(plugin_link_dir)
+ assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
+
+ # link dir exists, is a symlink to somewhere else (re-link)
+ junk_file_path = os.path.join(
+ linked_assets_dir.rstrip(os.path.sep),
+ 'junk.txt')
+ with file(junk_file_path, 'w') as junk_file:
+ junk_file.write('barf')
+
+ os.unlink(plugin_link_dir)
+ os.symlink(junk_file_path, plugin_link_dir)
+
+ result = run_assetlink().combined_string
+ assert result == """Old link found for "staticstuff"; removing.
+Linked asset directory for plugin "staticstuff":
+ %s
+to:
+ %s
+""" % (plugin_static.file_path.rstrip(os.path.sep), plugin_link_dir)
+ assert os.path.lexists(plugin_link_dir)
+ assert os.path.islink(plugin_link_dir)
+ assert os.path.realpath(plugin_link_dir) == plugin_static.file_path
+
+ # link dir exists, but is a non-symlink
+ os.unlink(plugin_link_dir)
+ with file(plugin_link_dir, 'w') as clobber_file:
+ clobber_file.write('clobbered!')
+
+ result = run_assetlink().collection[0]
+ assert result == 'Could not link "staticstuff": %s exists and is not a symlink\n' % (
+ plugin_link_dir)
+
+ with file(plugin_link_dir, 'r') as clobber_file:
+ assert clobber_file.read() == 'clobbered!'
+
+
+def test_plugin_staticdirect(static_plugin_app):
+ """
+ Test that the staticdirect utilities pull up the right things
+ """
+ result = json.loads(
+ static_plugin_app.get('/staticstuff/').body)
+
+ assert len(result) == 2
+
+ assert result['mgoblin_bunny_pic'] == '/test_static/images/bunny_pic.png'
+ assert result['plugin_bunny_css'] == \
+ '/plugin_static/staticstuff/css/bunnify.css'
+
diff --git a/mediagoblin/tests/test_processing.py b/mediagoblin/tests/test_processing.py
new file mode 100644
index 00000000..591add96
--- /dev/null
+++ b/mediagoblin/tests/test_processing.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+
+from mediagoblin import processing
+
+class TestProcessing(object):
+ def run_fill(self, input, format, output=None):
+ builder = processing.FilenameBuilder(input)
+ result = builder.fill(format)
+ if output is None:
+ return result
+ assert output == result
+
+ def test_easy_filename_fill(self):
+ self.run_fill('/home/user/foo.TXT', '{basename}bar{ext}', 'foobar.txt')
+
+ def test_long_filename_fill(self):
+ self.run_fill('{0}.png'.format('A' * 300), 'image-{basename}{ext}',
+ 'image-{0}.png'.format('A' * 245))
diff --git a/mediagoblin/tests/test_session.py b/mediagoblin/tests/test_session.py
new file mode 100644
index 00000000..78d790eb
--- /dev/null
+++ b/mediagoblin/tests/test_session.py
@@ -0,0 +1,30 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools import session
+
+def test_session():
+ sess = session.Session()
+ assert not sess
+ assert not sess.is_updated()
+ sess['user_id'] = 27
+ assert sess
+ assert not sess.is_updated()
+ sess.save()
+ assert sess.is_updated()
+ sess.delete()
+ assert not sess
+ assert sess.is_updated()
diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py
new file mode 100644
index 00000000..2fc4c043
--- /dev/null
+++ b/mediagoblin/tests/test_sql_migrations.py
@@ -0,0 +1,896 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2012, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import copy
+
+from sqlalchemy import (
+ Table, Column, MetaData, Index,
+ Integer, Float, Unicode, UnicodeText, DateTime, Boolean,
+ ForeignKey, UniqueConstraint, PickleType, VARCHAR)
+from sqlalchemy.orm import sessionmaker, relationship
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.sql import select, insert
+from migrate import changeset
+
+from mediagoblin.db.base import GMGTableBase
+from mediagoblin.db.migration_tools import MigrationManager, RegisterMigration
+from mediagoblin.tools.common import CollectingPrinter
+
+
+# This one will get filled with local migrations
+FULL_MIGRATIONS = {}
+
+
+#######################################################
+# Migration set 1: Define initial models, no migrations
+#######################################################
+
+Base1 = declarative_base(cls=GMGTableBase)
+
+class Creature1(Base1):
+ __tablename__ = "creature"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True, nullable=False, index=True)
+ num_legs = Column(Integer, nullable=False)
+ is_demon = Column(Boolean)
+
+class Level1(Base1):
+ __tablename__ = "level"
+
+ id = Column(Unicode, primary_key=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+ exits = Column(PickleType)
+
+SET1_MODELS = [Creature1, Level1]
+
+SET1_MIGRATIONS = {}
+
+#######################################################
+# Migration set 2: A few migrations and new model
+#######################################################
+
+Base2 = declarative_base(cls=GMGTableBase)
+
+class Creature2(Base2):
+ __tablename__ = "creature"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True, nullable=False, index=True)
+ num_legs = Column(Integer, nullable=False)
+ magical_powers = relationship("CreaturePower2")
+
+class CreaturePower2(Base2):
+ __tablename__ = "creature_power"
+
+ id = Column(Integer, primary_key=True)
+ creature = Column(
+ Integer, ForeignKey('creature.id'), nullable=False)
+ name = Column(Unicode)
+ description = Column(Unicode)
+ hitpower = Column(Integer, nullable=False)
+
+class Level2(Base2):
+ __tablename__ = "level"
+
+ id = Column(Unicode, primary_key=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+
+class LevelExit2(Base2):
+ __tablename__ = "level_exit"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode)
+ from_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False)
+ to_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False)
+
+SET2_MODELS = [Creature2, CreaturePower2, Level2, LevelExit2]
+
+
+@RegisterMigration(1, FULL_MIGRATIONS)
+def creature_remove_is_demon(db_conn):
+ """
+ Remove the is_demon field from the creature model. We don't need
+ it!
+ """
+ # :( Commented out 'cuz of:
+ # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=143&thanks=143&ts=1327882242
+
+ # metadata = MetaData(bind=db_conn.bind)
+ # creature_table = Table(
+ # 'creature', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # creature_table.drop_column('is_demon')
+ pass
+
+
+@RegisterMigration(2, FULL_MIGRATIONS)
+def creature_powers_new_table(db_conn):
+ """
+ Add a new table for creature powers. Nothing needs to go in it
+ yet though as there wasn't anything that previously held this
+ information
+ """
+ metadata = MetaData(bind=db_conn.bind)
+
+ # We have to access the creature table so sqlalchemy can make the
+ # foreign key relationship
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+
+ creature_powers = Table(
+ 'creature_power', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('creature',
+ Integer, ForeignKey('creature.id'), nullable=False),
+ Column('name', Unicode),
+ Column('description', Unicode),
+ Column('hitpower', Integer, nullable=False))
+ metadata.create_all(db_conn.bind)
+
+
+@RegisterMigration(3, FULL_MIGRATIONS)
+def level_exits_new_table(db_conn):
+ """
+ Make a new table for level exits and move the previously pickled
+ stuff over to here (then drop the old unneeded table)
+ """
+ # First, create the table
+ # -----------------------
+ metadata = MetaData(bind=db_conn.bind)
+
+ # Minimal representation of level table.
+ # Not auto-introspecting here because of pickle table. I'm not
+ # sure sqlalchemy can auto-introspect pickle columns.
+ levels = Table(
+ 'level', metadata,
+ Column('id', Unicode, primary_key=True),
+ Column('name', Unicode),
+ Column('description', Unicode),
+ Column('exits', PickleType))
+
+ level_exits = Table(
+ 'level_exit', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('name', Unicode),
+ Column('from_level',
+ Unicode, ForeignKey('level.id'), nullable=False),
+ Column('to_level',
+ Unicode, ForeignKey('level.id'), nullable=False))
+ metadata.create_all(db_conn.bind)
+
+ # And now, convert all the old exit pickles to new level exits
+ # ------------------------------------------------------------
+
+ # query over and insert
+ result = db_conn.execute(
+ select([levels], levels.c.exits!=None))
+
+ for level in result:
+
+ for exit_name, to_level in level['exits'].iteritems():
+ # Insert the level exit
+ db_conn.execute(
+ level_exits.insert().values(
+ name=exit_name,
+ from_level=level.id,
+ to_level=to_level))
+
+ # Finally, drop the old level exits pickle table
+ # ----------------------------------------------
+ levels.drop_column('exits')
+
+
+# A hack! At this point we freeze-fame and get just a partial list of
+# migrations
+
+SET2_MIGRATIONS = copy.copy(FULL_MIGRATIONS)
+
+#######################################################
+# Migration set 3: Final migrations
+#######################################################
+
+Base3 = declarative_base(cls=GMGTableBase)
+
+class Creature3(Base3):
+ __tablename__ = "creature"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode, unique=True, nullable=False, index=True)
+ num_limbs= Column(Integer, nullable=False)
+ magical_powers = relationship("CreaturePower3")
+
+class CreaturePower3(Base3):
+ __tablename__ = "creature_power"
+
+ id = Column(Integer, primary_key=True)
+ creature = Column(
+ Integer, ForeignKey('creature.id'), nullable=False, index=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+ hitpower = Column(Float, nullable=False)
+
+class Level3(Base3):
+ __tablename__ = "level"
+
+ id = Column(Unicode, primary_key=True)
+ name = Column(Unicode)
+ description = Column(Unicode)
+
+class LevelExit3(Base3):
+ __tablename__ = "level_exit"
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode)
+ from_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False, index=True)
+ to_level = Column(
+ Unicode, ForeignKey('level.id'), nullable=False, index=True)
+
+
+SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3]
+SET3_MIGRATIONS = FULL_MIGRATIONS
+
+
+@RegisterMigration(4, FULL_MIGRATIONS)
+def creature_num_legs_to_num_limbs(db_conn):
+ """
+ Turns out we're tracking all sorts of limbs, not "legs"
+ specifically. Humans would be 4 here, for instance. So we
+ renamed the column.
+ """
+ metadata = MetaData(bind=db_conn.bind)
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+ creature_table.c.num_legs.alter(name=u"num_limbs")
+
+
+@RegisterMigration(5, FULL_MIGRATIONS)
+def level_exit_index_from_and_to_level(db_conn):
+ """
+ Index the from and to levels of the level exit table.
+ """
+ metadata = MetaData(bind=db_conn.bind)
+ level_exit = Table(
+ 'level_exit', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+ Index('ix_level_exit_from_level',
+ level_exit.c.from_level).create(db_conn.bind)
+ Index('ix_level_exit_to_level',
+ level_exit.c.to_level).create(db_conn.bind)
+
+
+@RegisterMigration(6, FULL_MIGRATIONS)
+def creature_power_index_creature(db_conn):
+ """
+ Index our foreign key relationship to the creatures
+ """
+ metadata = MetaData(bind=db_conn.bind)
+ creature_power = Table(
+ 'creature_power', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+ Index('ix_creature_power_creature',
+ creature_power.c.creature).create(db_conn.bind)
+
+
+@RegisterMigration(7, FULL_MIGRATIONS)
+def creature_power_hitpower_to_float(db_conn):
+ """
+ Convert hitpower column on creature power table from integer to
+ float.
+
+ Turns out we want super precise values of how much hitpower there
+ really is.
+ """
+ metadata = MetaData(bind=db_conn.bind)
+
+ # We have to access the creature table so sqlalchemy can make the
+ # foreign key relationship
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+
+ creature_power = Table(
+ 'creature_power', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('creature', Integer,
+ ForeignKey('creature.id'), nullable=False,
+ index=True),
+ Column('name', Unicode),
+ Column('description', Unicode),
+ Column('hitpower', Integer, nullable=False))
+
+ creature_power.c.hitpower.alter(type=Float)
+
+
+@RegisterMigration(8, FULL_MIGRATIONS)
+def creature_power_name_creature_unique(db_conn):
+ """
+ Add a unique constraint to name and creature on creature_power.
+
+ We don't want multiple creature powers with the same name per creature!
+ """
+ # Note: We don't actually check to see if this constraint is set
+ # up because at present there's no way to do so in sqlalchemy :\
+
+ metadata = MetaData(bind=db_conn.bind)
+
+ creature_power = Table(
+ 'creature_power', metadata,
+ autoload=True, autoload_with=db_conn.bind)
+
+ cons = changeset.constraint.UniqueConstraint(
+ 'name', 'creature', table=creature_power)
+
+ cons.create()
+
+
+def _insert_migration1_objects(session):
+ """
+ Test objects to insert for the first set of things
+ """
+ # Insert creatures
+ session.add_all(
+ [Creature1(name=u'centipede',
+ num_legs=100,
+ is_demon=False),
+ Creature1(name=u'wolf',
+ num_legs=4,
+ is_demon=False),
+ # don't ask me what a wizardsnake is.
+ Creature1(name=u'wizardsnake',
+ num_legs=0,
+ is_demon=True)])
+
+ # Insert levels
+ session.add_all(
+ [Level1(id=u'necroplex',
+ name=u'The Necroplex',
+ description=u'A complex full of pure deathzone.',
+ exits={
+ u'deathwell': u'evilstorm',
+ u'portal': u'central_park'}),
+ Level1(id=u'evilstorm',
+ name=u'Evil Storm',
+ description=u'A storm full of pure evil.',
+ exits={}), # you can't escape the evilstorm
+ Level1(id=u'central_park',
+ name=u'Central Park, NY, NY',
+ description=u"New York's friendly Central Park.",
+ exits={
+ u'portal': u'necroplex'})])
+
+ session.commit()
+
+
+def _insert_migration2_objects(session):
+ """
+ Test objects to insert for the second set of things
+ """
+ # Insert creatures
+ session.add_all(
+ [Creature2(
+ name=u'centipede',
+ num_legs=100),
+ Creature2(
+ name=u'wolf',
+ num_legs=4,
+ magical_powers = [
+ CreaturePower2(
+ name=u"ice breath",
+ description=u"A blast of icy breath!",
+ hitpower=20),
+ CreaturePower2(
+ name=u"death stare",
+ description=u"A frightening stare, for sure!",
+ hitpower=45)]),
+ Creature2(
+ name=u'wizardsnake',
+ num_legs=0,
+ magical_powers=[
+ CreaturePower2(
+ name=u'death_rattle',
+ description=u'A rattle... of DEATH!',
+ hitpower=1000),
+ CreaturePower2(
+ name=u'sneaky_stare',
+ description=u"The sneakiest stare you've ever seen!",
+ hitpower=300),
+ CreaturePower2(
+ name=u'slithery_smoke',
+ description=u"A blast of slithery, slithery smoke.",
+ hitpower=10),
+ CreaturePower2(
+ name=u'treacherous_tremors',
+ description=u"The ground shakes beneath footed animals!",
+ hitpower=0)])])
+
+ # Insert levels
+ session.add_all(
+ [Level2(id=u'necroplex',
+ name=u'The Necroplex',
+ description=u'A complex full of pure deathzone.'),
+ Level2(id=u'evilstorm',
+ name=u'Evil Storm',
+ description=u'A storm full of pure evil.',
+ exits=[]), # you can't escape the evilstorm
+ Level2(id=u'central_park',
+ name=u'Central Park, NY, NY',
+ description=u"New York's friendly Central Park.")])
+
+ # necroplex exits
+ session.add_all(
+ [LevelExit2(name=u'deathwell',
+ from_level=u'necroplex',
+ to_level=u'evilstorm'),
+ LevelExit2(name=u'portal',
+ from_level=u'necroplex',
+ to_level=u'central_park')])
+
+ # there are no evilstorm exits because there is no exit from the
+ # evilstorm
+
+ # central park exits
+ session.add_all(
+ [LevelExit2(name=u'portal',
+ from_level=u'central_park',
+ to_level=u'necroplex')])
+
+ session.commit()
+
+
+def _insert_migration3_objects(session):
+ """
+ Test objects to insert for the third set of things
+ """
+ # Insert creatures
+ session.add_all(
+ [Creature3(
+ name=u'centipede',
+ num_limbs=100),
+ Creature3(
+ name=u'wolf',
+ num_limbs=4,
+ magical_powers = [
+ CreaturePower3(
+ name=u"ice breath",
+ description=u"A blast of icy breath!",
+ hitpower=20.0),
+ CreaturePower3(
+ name=u"death stare",
+ description=u"A frightening stare, for sure!",
+ hitpower=45.0)]),
+ Creature3(
+ name=u'wizardsnake',
+ num_limbs=0,
+ magical_powers=[
+ CreaturePower3(
+ name=u'death_rattle',
+ description=u'A rattle... of DEATH!',
+ hitpower=1000.0),
+ CreaturePower3(
+ name=u'sneaky_stare',
+ description=u"The sneakiest stare you've ever seen!",
+ hitpower=300.0),
+ CreaturePower3(
+ name=u'slithery_smoke',
+ description=u"A blast of slithery, slithery smoke.",
+ hitpower=10.0),
+ CreaturePower3(
+ name=u'treacherous_tremors',
+ description=u"The ground shakes beneath footed animals!",
+ hitpower=0.0)])],
+ # annnnnd one more to test a floating point hitpower
+ Creature3(
+ name=u'deity',
+ numb_limbs=30,
+ magical_powers=[
+ CreaturePower3(
+ name=u'smite',
+ description=u'Smitten by holy wrath!',
+ hitpower=9999.9)]))
+
+ # Insert levels
+ session.add_all(
+ [Level3(id=u'necroplex',
+ name=u'The Necroplex',
+ description=u'A complex full of pure deathzone.'),
+ Level3(id=u'evilstorm',
+ name=u'Evil Storm',
+ description=u'A storm full of pure evil.',
+ exits=[]), # you can't escape the evilstorm
+ Level3(id=u'central_park',
+ name=u'Central Park, NY, NY',
+ description=u"New York's friendly Central Park.")])
+
+ # necroplex exits
+ session.add_all(
+ [LevelExit3(name=u'deathwell',
+ from_level=u'necroplex',
+ to_level=u'evilstorm'),
+ LevelExit3(name=u'portal',
+ from_level=u'necroplex',
+ to_level=u'central_park')])
+
+ # there are no evilstorm exits because there is no exit from the
+ # evilstorm
+
+ # central park exits
+ session.add_all(
+ [LevelExit3(name=u'portal',
+ from_level=u'central_park',
+ to_level=u'necroplex')])
+
+ session.commit()
+
+
+def create_test_engine():
+ from sqlalchemy import create_engine
+ engine = create_engine('sqlite:///:memory:', echo=False)
+ Session = sessionmaker(bind=engine)
+ return engine, Session
+
+
+def assert_col_type(column, this_class):
+ assert isinstance(column.type, this_class)
+
+
+def _get_level3_exits(session, level):
+ return dict(
+ [(level_exit.name, level_exit.to_level)
+ for level_exit in
+ session.query(LevelExit3).filter_by(from_level=level.id)])
+
+
+def test_set1_to_set3():
+ # Create / connect to database
+ # ----------------------------
+
+ engine, Session = create_test_engine()
+
+ # Create tables by migrating on empty initial set
+ # -----------------------------------------------
+
+ printer = CollectingPrinter()
+ migration_manager = MigrationManager(
+ u'__main__', SET1_MODELS, SET1_MIGRATIONS, Session(),
+ printer)
+
+ # Check latest migration and database current migration
+ assert migration_manager.latest_migration == 0
+ assert migration_manager.database_current_migration == None
+
+ result = migration_manager.init_or_migrate()
+
+ # Make sure output was "inited"
+ assert result == u'inited'
+ # Check output
+ assert printer.combined_string == (
+ "-> Initializing main mediagoblin tables... done.\n")
+ # Check version in database
+ assert migration_manager.latest_migration == 0
+ assert migration_manager.database_current_migration == 0
+
+ # Install the initial set
+ # -----------------------
+
+ _insert_migration1_objects(Session())
+
+ # Try to "re-migrate" with same manager settings... nothing should happen
+ migration_manager = MigrationManager(
+ u'__main__', SET1_MODELS, SET1_MIGRATIONS, Session(),
+ printer)
+ assert migration_manager.init_or_migrate() == None
+
+ # Check version in database
+ assert migration_manager.latest_migration == 0
+ assert migration_manager.database_current_migration == 0
+
+ # Sanity check a few things in the database...
+ metadata = MetaData(bind=engine)
+
+ # Check the structure of the creature table
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(creature_table.c.keys()) == set(
+ ['id', 'name', 'num_legs', 'is_demon'])
+ assert_col_type(creature_table.c.id, Integer)
+ assert_col_type(creature_table.c.name, VARCHAR)
+ assert creature_table.c.name.nullable is False
+ #assert creature_table.c.name.index is True
+ #assert creature_table.c.name.unique is True
+ assert_col_type(creature_table.c.num_legs, Integer)
+ assert creature_table.c.num_legs.nullable is False
+ assert_col_type(creature_table.c.is_demon, Boolean)
+
+ # Check the structure of the level table
+ level_table = Table(
+ 'level', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(level_table.c.keys()) == set(
+ ['id', 'name', 'description', 'exits'])
+ assert_col_type(level_table.c.id, VARCHAR)
+ assert level_table.c.id.primary_key is True
+ assert_col_type(level_table.c.name, VARCHAR)
+ assert_col_type(level_table.c.description, VARCHAR)
+ # Skipping exits... Not sure if we can detect pickletype, not a
+ # big deal regardless.
+
+ # Now check to see if stuff seems to be in there.
+ session = Session()
+
+ creature = session.query(Creature1).filter_by(
+ name=u'centipede').one()
+ assert creature.num_legs == 100
+ assert creature.is_demon == False
+
+ creature = session.query(Creature1).filter_by(
+ name=u'wolf').one()
+ assert creature.num_legs == 4
+ assert creature.is_demon == False
+
+ creature = session.query(Creature1).filter_by(
+ name=u'wizardsnake').one()
+ assert creature.num_legs == 0
+ assert creature.is_demon == True
+
+ level = session.query(Level1).filter_by(
+ id=u'necroplex').one()
+ assert level.name == u'The Necroplex'
+ assert level.description == u'A complex full of pure deathzone.'
+ assert level.exits == {
+ 'deathwell': 'evilstorm',
+ 'portal': 'central_park'}
+
+ level = session.query(Level1).filter_by(
+ id=u'evilstorm').one()
+ assert level.name == u'Evil Storm'
+ assert level.description == u'A storm full of pure evil.'
+ assert level.exits == {} # You still can't escape the evilstorm!
+
+ level = session.query(Level1).filter_by(
+ id=u'central_park').one()
+ assert level.name == u'Central Park, NY, NY'
+ assert level.description == u"New York's friendly Central Park."
+ assert level.exits == {
+ 'portal': 'necroplex'}
+
+ # Create new migration manager, but make sure the db migration
+ # isn't said to be updated yet
+ printer = CollectingPrinter()
+ migration_manager = MigrationManager(
+ u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
+ printer)
+
+ assert migration_manager.latest_migration == 8
+ assert migration_manager.database_current_migration == 0
+
+ # Migrate
+ result = migration_manager.init_or_migrate()
+
+ # Make sure result was "migrated"
+ assert result == u'migrated'
+
+ # TODO: Check output to user
+ assert printer.combined_string == """\
+-> Updating main mediagoblin tables:
+ + Running migration 1, "creature_remove_is_demon"... done.
+ + Running migration 2, "creature_powers_new_table"... done.
+ + Running migration 3, "level_exits_new_table"... done.
+ + Running migration 4, "creature_num_legs_to_num_limbs"... done.
+ + Running migration 5, "level_exit_index_from_and_to_level"... done.
+ + Running migration 6, "creature_power_index_creature"... done.
+ + Running migration 7, "creature_power_hitpower_to_float"... done.
+ + Running migration 8, "creature_power_name_creature_unique"... done.
+"""
+
+ # Make sure version matches expected
+ migration_manager = MigrationManager(
+ u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(),
+ printer)
+ assert migration_manager.latest_migration == 8
+ assert migration_manager.database_current_migration == 8
+
+ # Check all things in database match expected
+
+ # Check the creature table
+ metadata = MetaData(bind=engine)
+ creature_table = Table(
+ 'creature', metadata,
+ autoload=True, autoload_with=engine)
+ # assert set(creature_table.c.keys()) == set(
+ # ['id', 'name', 'num_limbs'])
+ assert set(creature_table.c.keys()) == set(
+ [u'id', 'name', u'num_limbs', u'is_demon'])
+ assert_col_type(creature_table.c.id, Integer)
+ assert_col_type(creature_table.c.name, VARCHAR)
+ assert creature_table.c.name.nullable is False
+ #assert creature_table.c.name.index is True
+ #assert creature_table.c.name.unique is True
+ assert_col_type(creature_table.c.num_limbs, Integer)
+ assert creature_table.c.num_limbs.nullable is False
+
+ # Check the CreaturePower table
+ creature_power_table = Table(
+ 'creature_power', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(creature_power_table.c.keys()) == set(
+ ['id', 'creature', 'name', 'description', 'hitpower'])
+ assert_col_type(creature_power_table.c.id, Integer)
+ assert_col_type(creature_power_table.c.creature, Integer)
+ assert creature_power_table.c.creature.nullable is False
+ assert_col_type(creature_power_table.c.name, VARCHAR)
+ assert_col_type(creature_power_table.c.description, VARCHAR)
+ assert_col_type(creature_power_table.c.hitpower, Float)
+ assert creature_power_table.c.hitpower.nullable is False
+
+ # Check the structure of the level table
+ level_table = Table(
+ 'level', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(level_table.c.keys()) == set(
+ ['id', 'name', 'description'])
+ assert_col_type(level_table.c.id, VARCHAR)
+ assert level_table.c.id.primary_key is True
+ assert_col_type(level_table.c.name, VARCHAR)
+ assert_col_type(level_table.c.description, VARCHAR)
+
+ # Check the structure of the level_exits table
+ level_exit_table = Table(
+ 'level_exit', metadata,
+ autoload=True, autoload_with=engine)
+ assert set(level_exit_table.c.keys()) == set(
+ ['id', 'name', 'from_level', 'to_level'])
+ assert_col_type(level_exit_table.c.id, Integer)
+ assert_col_type(level_exit_table.c.name, VARCHAR)
+ assert_col_type(level_exit_table.c.from_level, VARCHAR)
+ assert level_exit_table.c.from_level.nullable is False
+ #assert level_exit_table.c.from_level.index is True
+ assert_col_type(level_exit_table.c.to_level, VARCHAR)
+ assert level_exit_table.c.to_level.nullable is False
+ #assert level_exit_table.c.to_level.index is True
+
+ # Now check to see if stuff seems to be in there.
+ session = Session()
+ creature = session.query(Creature3).filter_by(
+ name=u'centipede').one()
+ assert creature.num_limbs == 100.0
+ assert creature.magical_powers == []
+
+ creature = session.query(Creature3).filter_by(
+ name=u'wolf').one()
+ assert creature.num_limbs == 4.0
+ assert creature.magical_powers == []
+
+ creature = session.query(Creature3).filter_by(
+ name=u'wizardsnake').one()
+ assert creature.num_limbs == 0.0
+ assert creature.magical_powers == []
+
+ level = session.query(Level3).filter_by(
+ id=u'necroplex').one()
+ assert level.name == u'The Necroplex'
+ assert level.description == u'A complex full of pure deathzone.'
+ level_exits = _get_level3_exits(session, level)
+ assert level_exits == {
+ u'deathwell': u'evilstorm',
+ u'portal': u'central_park'}
+
+ level = session.query(Level3).filter_by(
+ id=u'evilstorm').one()
+ assert level.name == u'Evil Storm'
+ assert level.description == u'A storm full of pure evil.'
+ level_exits = _get_level3_exits(session, level)
+ assert level_exits == {} # You still can't escape the evilstorm!
+
+ level = session.query(Level3).filter_by(
+ id=u'central_park').one()
+ assert level.name == u'Central Park, NY, NY'
+ assert level.description == u"New York's friendly Central Park."
+ level_exits = _get_level3_exits(session, level)
+ assert level_exits == {
+ 'portal': 'necroplex'}
+
+
+#def test_set2_to_set3():
+ # Create / connect to database
+ # Create tables by migrating on empty initial set
+
+ # Install the initial set
+ # Check version in database
+ # Sanity check a few things in the database
+
+ # Migrate
+ # Make sure version matches expected
+ # Check all things in database match expected
+ # pass
+
+
+#def test_set1_to_set2_to_set3():
+ # Create / connect to database
+ # Create tables by migrating on empty initial set
+
+ # Install the initial set
+ # Check version in database
+ # Sanity check a few things in the database
+
+ # Migrate
+ # Make sure version matches expected
+ # Check all things in database match expected
+
+ # Migrate again
+ # Make sure version matches expected again
+ # Check all things in database match expected again
+
+ ##### Set2
+ # creature_table = Table(
+ # 'creature', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(creature_table.c.keys()) == set(
+ # ['id', 'name', 'num_legs'])
+ # assert_col_type(creature_table.c.id, Integer)
+ # assert_col_type(creature_table.c.name, VARCHAR)
+ # assert creature_table.c.name.nullable is False
+ # assert creature_table.c.name.index is True
+ # assert creature_table.c.name.unique is True
+ # assert_col_type(creature_table.c.num_legs, Integer)
+ # assert creature_table.c.num_legs.nullable is False
+
+ # # Check the CreaturePower table
+ # creature_power_table = Table(
+ # 'creature_power', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(creature_power_table.c.keys()) == set(
+ # ['id', 'creature', 'name', 'description', 'hitpower'])
+ # assert_col_type(creature_power_table.c.id, Integer)
+ # assert_col_type(creature_power_table.c.creature, Integer)
+ # assert creature_power_table.c.creature.nullable is False
+ # assert_col_type(creature_power_table.c.name, VARCHAR)
+ # assert_col_type(creature_power_table.c.description, VARCHAR)
+ # assert_col_type(creature_power_table.c.hitpower, Integer)
+ # assert creature_power_table.c.hitpower.nullable is False
+
+ # # Check the structure of the level table
+ # level_table = Table(
+ # 'level', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(level_table.c.keys()) == set(
+ # ['id', 'name', 'description'])
+ # assert_col_type(level_table.c.id, VARCHAR)
+ # assert level_table.c.id.primary_key is True
+ # assert_col_type(level_table.c.name, VARCHAR)
+ # assert_col_type(level_table.c.description, VARCHAR)
+
+ # # Check the structure of the level_exits table
+ # level_exit_table = Table(
+ # 'level_exit', metadata,
+ # autoload=True, autoload_with=db_conn.bind)
+ # assert set(level_exit_table.c.keys()) == set(
+ # ['id', 'name', 'from_level', 'to_level'])
+ # assert_col_type(level_exit_table.c.id, Integer)
+ # assert_col_type(level_exit_table.c.name, VARCHAR)
+ # assert_col_type(level_exit_table.c.from_level, VARCHAR)
+ # assert level_exit_table.c.from_level.nullable is False
+ # assert_col_type(level_exit_table.c.to_level, VARCHAR)
+
+ # pass
diff --git a/mediagoblin/tests/test_staticdirect.py b/mediagoblin/tests/test_staticdirect.py
new file mode 100644
index 00000000..3a9e2fd9
--- /dev/null
+++ b/mediagoblin/tests/test_staticdirect.py
@@ -0,0 +1,9 @@
+from mediagoblin.tools import staticdirect
+
+def test_staticdirect():
+ sdirect = staticdirect.StaticDirect(
+ {None: "/static/",
+ "theme": "http://example.org/themestatic"})
+ assert sdirect("css/monkeys.css") == "/static/css/monkeys.css"
+ assert sdirect("images/lollerskate.png", "theme") == \
+ "http://example.org/themestatic/images/lollerskate.png"
diff --git a/mediagoblin/tests/test_storage.py b/mediagoblin/tests/test_storage.py
new file mode 100644
index 00000000..f6f1d18f
--- /dev/null
+++ b/mediagoblin/tests/test_storage.py
@@ -0,0 +1,321 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import os
+import tempfile
+
+import pytest
+from werkzeug.utils import secure_filename
+
+from mediagoblin import storage
+
+
+################
+# Test utilities
+################
+
+def test_clean_listy_filepath():
+ expected = [u'dir1', u'dir2', u'linooks.jpg']
+ assert storage.clean_listy_filepath(
+ ['dir1', 'dir2', 'linooks.jpg']) == expected
+
+ expected = [u'dir1', u'foo_.._nasty', u'linooks.jpg']
+ assert storage.clean_listy_filepath(
+ ['/dir1/', 'foo/../nasty', 'linooks.jpg']) == expected
+
+ expected = [u'etc', u'passwd']
+ assert storage.clean_listy_filepath(
+ ['../../../etc/', 'passwd']) == expected
+
+ with pytest.raises(storage.InvalidFilepath):
+ storage.clean_listy_filepath(['../../', 'linooks.jpg'])
+
+
+class FakeStorageSystem():
+ def __init__(self, foobie, blech, **kwargs):
+ self.foobie = foobie
+ self.blech = blech
+
+class FakeRemoteStorage(storage.filestorage.BasicFileStorage):
+ # Theoretically despite this, all the methods should work but it
+ # should force copying to the workbench
+ local_storage = False
+
+ def copy_local_to_storage(self, *args, **kwargs):
+ return storage.StorageInterface.copy_local_to_storage(
+ self, *args, **kwargs)
+
+
+def test_storage_system_from_config():
+ this_storage = storage.storage_system_from_config(
+ {'base_url': 'http://example.org/moodia/',
+ 'base_dir': '/tmp/',
+ 'garbage_arg': 'garbage_arg',
+ 'garbage_arg': 'trash'})
+ assert this_storage.base_url == 'http://example.org/moodia/'
+ assert this_storage.base_dir == '/tmp/'
+ assert this_storage.__class__ is storage.filestorage.BasicFileStorage
+
+ this_storage = storage.storage_system_from_config(
+ {'foobie': 'eiboof',
+ 'blech': 'hcelb',
+ 'garbage_arg': 'garbage_arg',
+ 'storage_class':
+ 'mediagoblin.tests.test_storage:FakeStorageSystem'})
+ assert this_storage.foobie == 'eiboof'
+ assert this_storage.blech == 'hcelb'
+ assert unicode(this_storage.__class__) == \
+ u'mediagoblin.tests.test_storage.FakeStorageSystem'
+
+
+##########################
+# Basic file storage tests
+##########################
+
+def get_tmp_filestorage(mount_url=None, fake_remote=False):
+ tmpdir = tempfile.mkdtemp(prefix="test_gmg_storage")
+ if fake_remote:
+ this_storage = FakeRemoteStorage(tmpdir, mount_url)
+ else:
+ this_storage = storage.filestorage.BasicFileStorage(tmpdir, mount_url)
+ return tmpdir, this_storage
+
+
+def cleanup_storage(this_storage, tmpdir, *paths):
+ for p in paths:
+ while p:
+ assert this_storage.delete_dir(p) == True
+ p.pop(-1)
+ os.rmdir(tmpdir)
+
+
+def test_basic_storage__resolve_filepath():
+ tmpdir, this_storage = get_tmp_filestorage()
+
+ result = this_storage._resolve_filepath(['dir1', 'dir2', 'filename.jpg'])
+ assert result == os.path.join(
+ tmpdir, 'dir1/dir2/filename.jpg')
+
+ result = this_storage._resolve_filepath(['../../etc/', 'passwd'])
+ assert result == os.path.join(
+ tmpdir, 'etc/passwd')
+
+ pytest.raises(
+ storage.InvalidFilepath,
+ this_storage._resolve_filepath,
+ ['../../', 'etc', 'passwd'])
+
+ cleanup_storage(this_storage, tmpdir)
+
+
+def test_basic_storage_file_exists():
+ tmpdir, this_storage = get_tmp_filestorage()
+
+ os.makedirs(os.path.join(tmpdir, 'dir1', 'dir2'))
+ filename = os.path.join(tmpdir, 'dir1', 'dir2', 'filename.txt')
+ with open(filename, 'w') as ourfile:
+ ourfile.write("I'm having a lovely day!")
+
+ assert this_storage.file_exists(['dir1', 'dir2', 'filename.txt'])
+ assert not this_storage.file_exists(['dir1', 'dir2', 'thisfile.lol'])
+ assert not this_storage.file_exists(['dnedir1', 'dnedir2', 'somefile.lol'])
+
+ this_storage.delete_file(['dir1', 'dir2', 'filename.txt'])
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
+
+
+def test_basic_storage_get_unique_filepath():
+ tmpdir, this_storage = get_tmp_filestorage()
+
+ # write something that exists
+ os.makedirs(os.path.join(tmpdir, 'dir1', 'dir2'))
+ filename = os.path.join(tmpdir, 'dir1', 'dir2', 'filename.txt')
+ with open(filename, 'w') as ourfile:
+ ourfile.write("I'm having a lovely day!")
+
+ # now we want something new, with the same name!
+ new_filepath = this_storage.get_unique_filepath(
+ ['dir1', 'dir2', 'filename.txt'])
+ assert new_filepath[:-1] == [u'dir1', u'dir2']
+
+ new_filename = new_filepath[-1]
+ assert new_filename.endswith('filename.txt')
+ assert len(new_filename) > len('filename.txt')
+ assert new_filename == secure_filename(new_filename)
+
+ os.remove(filename)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
+
+
+def test_basic_storage_get_file():
+ tmpdir, this_storage = get_tmp_filestorage()
+
+ # Write a brand new file
+ filepath = ['dir1', 'dir2', 'ourfile.txt']
+
+ with this_storage.get_file(filepath, 'w') as our_file:
+ our_file.write('First file')
+ with this_storage.get_file(filepath, 'r') as our_file:
+ assert our_file.read() == 'First file'
+ assert os.path.exists(os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'))
+ with file(os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'), 'r') as our_file:
+ assert our_file.read() == 'First file'
+
+ # Write to the same path but try to get a unique file.
+ new_filepath = this_storage.get_unique_filepath(filepath)
+ assert not os.path.exists(os.path.join(tmpdir, *new_filepath))
+
+ with this_storage.get_file(new_filepath, 'w') as our_file:
+ our_file.write('Second file')
+ with this_storage.get_file(new_filepath, 'r') as our_file:
+ assert our_file.read() == 'Second file'
+ assert os.path.exists(os.path.join(tmpdir, *new_filepath))
+ with file(os.path.join(tmpdir, *new_filepath), 'r') as our_file:
+ assert our_file.read() == 'Second file'
+
+ # Read from an existing file
+ manually_written_file = os.makedirs(
+ os.path.join(tmpdir, 'testydir'))
+ with file(os.path.join(tmpdir, 'testydir/testyfile.txt'), 'w') as testyfile:
+ testyfile.write('testy file! so testy.')
+
+ with this_storage.get_file(['testydir', 'testyfile.txt']) as testyfile:
+ assert testyfile.read() == 'testy file! so testy.'
+
+ this_storage.delete_file(filepath)
+ this_storage.delete_file(new_filepath)
+ this_storage.delete_file(['testydir', 'testyfile.txt'])
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'], ['testydir'])
+
+
+def test_basic_storage_delete_file():
+ tmpdir, this_storage = get_tmp_filestorage()
+
+ assert not os.path.exists(
+ os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'))
+
+ filepath = ['dir1', 'dir2', 'ourfile.txt']
+ with this_storage.get_file(filepath, 'w') as our_file:
+ our_file.write('Testing this file')
+
+ assert os.path.exists(
+ os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'))
+
+ assert this_storage.delete_dir(['dir1', 'dir2']) == False
+ this_storage.delete_file(filepath)
+ assert this_storage.delete_dir(['dir1', 'dir2']) == True
+
+ assert not os.path.exists(
+ os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'))
+
+ cleanup_storage(this_storage, tmpdir, ['dir1'])
+
+
+def test_basic_storage_url_for_file():
+ # Not supplying a base_url should actually just bork.
+ tmpdir, this_storage = get_tmp_filestorage()
+ pytest.raises(
+ storage.NoWebServing,
+ this_storage.file_url,
+ ['dir1', 'dir2', 'filename.txt'])
+ cleanup_storage(this_storage, tmpdir)
+
+ # base_url without domain
+ tmpdir, this_storage = get_tmp_filestorage('/media/')
+ result = this_storage.file_url(
+ ['dir1', 'dir2', 'filename.txt'])
+ expected = '/media/dir1/dir2/filename.txt'
+ assert result == expected
+ cleanup_storage(this_storage, tmpdir)
+
+ # base_url with domain
+ tmpdir, this_storage = get_tmp_filestorage(
+ 'http://media.example.org/ourmedia/')
+ result = this_storage.file_url(
+ ['dir1', 'dir2', 'filename.txt'])
+ expected = 'http://media.example.org/ourmedia/dir1/dir2/filename.txt'
+ assert result == expected
+ cleanup_storage(this_storage, tmpdir)
+
+
+def test_basic_storage_get_local_path():
+ tmpdir, this_storage = get_tmp_filestorage()
+
+ result = this_storage.get_local_path(
+ ['dir1', 'dir2', 'filename.txt'])
+
+ expected = os.path.join(
+ tmpdir, 'dir1/dir2/filename.txt')
+
+ assert result == expected
+
+ cleanup_storage(this_storage, tmpdir)
+
+
+def test_basic_storage_is_local():
+ tmpdir, this_storage = get_tmp_filestorage()
+ assert this_storage.local_storage is True
+ cleanup_storage(this_storage, tmpdir)
+
+
+def test_basic_storage_copy_locally():
+ tmpdir, this_storage = get_tmp_filestorage()
+
+ dest_tmpdir = tempfile.mkdtemp()
+
+ filepath = ['dir1', 'dir2', 'ourfile.txt']
+ with this_storage.get_file(filepath, 'w') as our_file:
+ our_file.write('Testing this file')
+
+ new_file_dest = os.path.join(dest_tmpdir, 'file2.txt')
+
+ this_storage.copy_locally(filepath, new_file_dest)
+ this_storage.delete_file(filepath)
+
+ assert file(new_file_dest).read() == 'Testing this file'
+
+ os.remove(new_file_dest)
+ os.rmdir(dest_tmpdir)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
+
+
+def _test_copy_local_to_storage_works(tmpdir, this_storage):
+ local_filename = tempfile.mktemp()
+ with file(local_filename, 'w') as tmpfile:
+ tmpfile.write('haha')
+
+ this_storage.copy_local_to_storage(
+ local_filename, ['dir1', 'dir2', 'copiedto.txt'])
+
+ os.remove(local_filename)
+
+ assert file(
+ os.path.join(tmpdir, 'dir1/dir2/copiedto.txt'),
+ 'r').read() == 'haha'
+
+ this_storage.delete_file(['dir1', 'dir2', 'copiedto.txt'])
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
+
+
+def test_basic_storage_copy_local_to_storage():
+ tmpdir, this_storage = get_tmp_filestorage()
+ _test_copy_local_to_storage_works(tmpdir, this_storage)
+
+
+def test_general_storage_copy_local_to_storage():
+ tmpdir, this_storage = get_tmp_filestorage(fake_remote=True)
+ _test_copy_local_to_storage_works(tmpdir, this_storage)
diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py
new file mode 100644
index 00000000..162b2d19
--- /dev/null
+++ b/mediagoblin/tests/test_submission.py
@@ -0,0 +1,294 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+reload(sys)
+sys.setdefaultencoding('utf-8')
+
+import urlparse
+import os
+import pytest
+
+from mediagoblin.tests.tools import fixture_add_user
+from mediagoblin import mg_globals
+from mediagoblin.db.models import MediaEntry
+from mediagoblin.tools import template
+from mediagoblin.media_types.image import MEDIA_MANAGER as img_MEDIA_MANAGER
+from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites
+
+from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \
+ BIG_BLUE, GOOD_PDF, GPS_JPG
+
+GOOD_TAG_STRING = u'yin,yang'
+BAD_TAG_STRING = unicode('rage,' + 'f' * 26 + 'u' * 26)
+
+FORM_CONTEXT = ['mediagoblin/submit/start.html', 'submit_form']
+REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request']
+
+
+class TestSubmission:
+ @pytest.fixture(autouse=True)
+ def setup(self, test_app):
+ self.test_app = test_app
+
+ # TODO: Possibly abstract into a decorator like:
+ # @as_authenticated_user('chris')
+ test_user = fixture_add_user()
+
+ self.test_user = test_user
+
+ self.login()
+
+ def login(self):
+ self.test_app.post(
+ '/auth/login/', {
+ 'username': u'chris',
+ 'password': 'toast'})
+
+ def logout(self):
+ self.test_app.get('/auth/logout/')
+
+ def do_post(self, data, *context_keys, **kwargs):
+ url = kwargs.pop('url', '/submit/')
+ do_follow = kwargs.pop('do_follow', False)
+ template.clear_test_template_context()
+ response = self.test_app.post(url, data, **kwargs)
+ if do_follow:
+ response.follow()
+ context_data = template.TEMPLATE_TEST_CONTEXT
+ for key in context_keys:
+ context_data = context_data[key]
+ return response, context_data
+
+ def upload_data(self, filename):
+ return {'upload_files': [('file', filename)]}
+
+ def check_comments(self, request, media_id, count):
+ comments = request.db.MediaComment.find({'media_entry': media_id})
+ assert count == len(list(comments))
+
+ def test_missing_fields(self):
+ # Test blank form
+ # ---------------
+ response, form = self.do_post({}, *FORM_CONTEXT)
+ assert form.file.errors == [u'You must provide a file.']
+
+ # Test blank file
+ # ---------------
+ response, form = self.do_post({'title': u'test title'}, *FORM_CONTEXT)
+ assert form.file.errors == [u'You must provide a file.']
+
+ def check_url(self, response, path):
+ assert urlparse.urlsplit(response.location)[2] == path
+
+ def check_normal_upload(self, title, filename):
+ response, context = self.do_post({'title': title}, do_follow=True,
+ **self.upload_data(filename))
+ self.check_url(response, '/u/{0}/'.format(self.test_user.username))
+ assert 'mediagoblin/user_pages/user.html' in context
+ # Make sure the media view is at least reachable, logged in...
+ url = '/u/{0}/m/{1}/'.format(self.test_user.username,
+ title.lower().replace(' ', '-'))
+ self.test_app.get(url)
+ # ... and logged out too.
+ self.logout()
+ self.test_app.get(url)
+
+ def test_normal_jpg(self):
+ self.check_normal_upload(u'Normal upload 1', GOOD_JPG)
+
+ def test_normal_png(self):
+ self.check_normal_upload(u'Normal upload 2', GOOD_PNG)
+
+ @pytest.mark.skipif("not pdf_check_prerequisites()")
+ def test_normal_pdf(self):
+ response, context = self.do_post({'title': u'Normal upload 3 (pdf)'},
+ do_follow=True,
+ **self.upload_data(GOOD_PDF))
+ self.check_url(response, '/u/{0}/'.format(self.test_user.username))
+ assert 'mediagoblin/user_pages/user.html' in context
+
+ def check_media(self, request, find_data, count=None):
+ media = MediaEntry.find(find_data)
+ if count is not None:
+ assert media.count() == count
+ if count == 0:
+ return
+ return media[0]
+
+ def test_tags(self):
+ # Good tag string
+ # --------
+ response, request = self.do_post({'title': u'Balanced Goblin 2',
+ 'tags': GOOD_TAG_STRING},
+ *REQUEST_CONTEXT, do_follow=True,
+ **self.upload_data(GOOD_JPG))
+ media = self.check_media(request, {'title': u'Balanced Goblin 2'}, 1)
+ assert media.tags[0]['name'] == u'yin'
+ assert media.tags[0]['slug'] == u'yin'
+
+ assert media.tags[1]['name'] == u'yang'
+ assert media.tags[1]['slug'] == u'yang'
+
+ # Test tags that are too long
+ # ---------------
+ response, form = self.do_post({'title': u'Balanced Goblin 2',
+ 'tags': BAD_TAG_STRING},
+ *FORM_CONTEXT,
+ **self.upload_data(GOOD_JPG))
+ assert form.tags.errors == [
+ u'Tags must be shorter than 50 characters. ' \
+ 'Tags that are too long: ' \
+ 'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu']
+
+ def test_delete(self):
+ response, request = self.do_post({'title': u'Balanced Goblin'},
+ *REQUEST_CONTEXT, do_follow=True,
+ **self.upload_data(GOOD_JPG))
+ media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+ media_id = media.id
+
+ # render and post to the edit page.
+ edit_url = request.urlgen(
+ 'mediagoblin.edit.edit_media',
+ user=self.test_user.username, media_id=media_id)
+ self.test_app.get(edit_url)
+ self.test_app.post(edit_url,
+ {'title': u'Balanced Goblin',
+ 'slug': u"Balanced=Goblin",
+ 'tags': u''})
+ media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+ assert media.slug == u"balanced-goblin"
+
+ # Add a comment, so we can test for its deletion later.
+ self.check_comments(request, media_id, 0)
+ comment_url = request.urlgen(
+ 'mediagoblin.user_pages.media_post_comment',
+ user=self.test_user.username, media_id=media_id)
+ response = self.do_post({'comment_content': 'i love this test'},
+ url=comment_url, do_follow=True)[0]
+ self.check_comments(request, media_id, 1)
+
+ # Do not confirm deletion
+ # ---------------------------------------------------
+ delete_url = request.urlgen(
+ 'mediagoblin.user_pages.media_confirm_delete',
+ user=self.test_user.username, media_id=media_id)
+ # Empty data means don't confirm
+ response = self.do_post({}, do_follow=True, url=delete_url)[0]
+ media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+ media_id = media.id
+
+ # Confirm deletion
+ # ---------------------------------------------------
+ response, request = self.do_post({'confirm': 'y'}, *REQUEST_CONTEXT,
+ do_follow=True, url=delete_url)
+ self.check_media(request, {'id': media_id}, 0)
+ self.check_comments(request, media_id, 0)
+
+ def test_evil_file(self):
+ # Test non-suppoerted file with non-supported extension
+ # -----------------------------------------------------
+ response, form = self.do_post({'title': u'Malicious Upload 1'},
+ *FORM_CONTEXT,
+ **self.upload_data(EVIL_FILE))
+ assert len(form.file.errors) == 1
+ assert 'Sorry, I don\'t support that file type :(' == \
+ str(form.file.errors[0])
+
+
+ def test_get_media_manager(self):
+ """Test if the get_media_manger function returns sensible things
+ """
+ response, request = self.do_post({'title': u'Balanced Goblin'},
+ *REQUEST_CONTEXT, do_follow=True,
+ **self.upload_data(GOOD_JPG))
+ media = self.check_media(request, {'title': u'Balanced Goblin'}, 1)
+
+ assert media.media_type == u'mediagoblin.media_types.image'
+ assert isinstance(media.media_manager, img_MEDIA_MANAGER)
+ assert media.media_manager.entry == media
+
+
+ def test_sniffing(self):
+ '''
+ Test sniffing mechanism to assert that regular uploads work as intended
+ '''
+ template.clear_test_template_context()
+ response = self.test_app.post(
+ '/submit/', {
+ 'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'
+ }, upload_files=[(
+ 'file', GOOD_JPG)])
+
+ response.follow()
+
+ context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/user_pages/user.html']
+
+ request = context['request']
+
+ media = request.db.MediaEntry.find_one({
+ u'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'})
+
+ assert media.media_type == 'mediagoblin.media_types.image'
+
+ def check_false_image(self, title, filename):
+ # NOTE: The following 2 tests will ultimately fail, but they
+ # *will* pass the initial form submission step. Instead,
+ # they'll be caught as failures during the processing step.
+ response, context = self.do_post({'title': title}, do_follow=True,
+ **self.upload_data(filename))
+ self.check_url(response, '/u/{0}/'.format(self.test_user.username))
+ entry = mg_globals.database.MediaEntry.find_one({'title': title})
+ assert entry.state == 'failed'
+ assert entry.fail_error == u'mediagoblin.processing:BadMediaFail'
+
+ def test_evil_jpg(self):
+ # Test non-supported file with .jpg extension
+ # -------------------------------------------
+ self.check_false_image(u'Malicious Upload 2', EVIL_JPG)
+
+ def test_evil_png(self):
+ # Test non-supported file with .png extension
+ # -------------------------------------------
+ self.check_false_image(u'Malicious Upload 3', EVIL_PNG)
+
+ def test_media_data(self):
+ self.check_normal_upload(u"With GPS data", GPS_JPG)
+ media = self.check_media(None, {"title": u"With GPS data"}, 1)
+ assert media.media_data.gps_latitude == 59.336666666666666
+
+ def test_processing(self):
+ public_store_dir = mg_globals.global_config[
+ 'storage:publicstore']['base_dir']
+
+ data = {'title': u'Big Blue'}
+ response, request = self.do_post(data, *REQUEST_CONTEXT, do_follow=True,
+ **self.upload_data(BIG_BLUE))
+ media = self.check_media(request, data, 1)
+ last_size = 1024 ** 3 # Needs to be larger than bigblue.png
+ for key, basename in (('original', 'bigblue.png'),
+ ('medium', 'bigblue.medium.png'),
+ ('thumb', 'bigblue.thumbnail.png')):
+ # Does the processed image have a good filename?
+ filename = os.path.join(
+ public_store_dir,
+ *media.media_files[key])
+ assert filename.endswith('_' + basename)
+ # Is it smaller than the last processed image we looked at?
+ size = os.stat(filename).st_size
+ assert last_size > size
+ last_size = size
diff --git a/mediagoblin/tests/test_submission/bigblue.png b/mediagoblin/tests/test_submission/bigblue.png
new file mode 100644
index 00000000..2b2c2a44
--- /dev/null
+++ b/mediagoblin/tests/test_submission/bigblue.png
Binary files differ
diff --git a/mediagoblin/tests/test_submission/evil b/mediagoblin/tests/test_submission/evil
new file mode 100755
index 00000000..2c850e29
--- /dev/null
+++ b/mediagoblin/tests/test_submission/evil
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "In yer base, doin spooky things" \ No newline at end of file
diff --git a/mediagoblin/tests/test_submission/evil.jpg b/mediagoblin/tests/test_submission/evil.jpg
new file mode 100755
index 00000000..2c850e29
--- /dev/null
+++ b/mediagoblin/tests/test_submission/evil.jpg
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "In yer base, doin spooky things" \ No newline at end of file
diff --git a/mediagoblin/tests/test_submission/evil.png b/mediagoblin/tests/test_submission/evil.png
new file mode 100755
index 00000000..2c850e29
--- /dev/null
+++ b/mediagoblin/tests/test_submission/evil.png
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "In yer base, doin spooky things" \ No newline at end of file
diff --git a/mediagoblin/tests/test_submission/good.jpg b/mediagoblin/tests/test_submission/good.jpg
new file mode 100644
index 00000000..936458e9
--- /dev/null
+++ b/mediagoblin/tests/test_submission/good.jpg
Binary files differ
diff --git a/mediagoblin/tests/test_submission/good.pdf b/mediagoblin/tests/test_submission/good.pdf
new file mode 100644
index 00000000..ab5db006
--- /dev/null
+++ b/mediagoblin/tests/test_submission/good.pdf
Binary files differ
diff --git a/mediagoblin/tests/test_submission/good.png b/mediagoblin/tests/test_submission/good.png
new file mode 100644
index 00000000..c1eadf9c
--- /dev/null
+++ b/mediagoblin/tests/test_submission/good.png
Binary files differ
diff --git a/mediagoblin/tests/test_tags.py b/mediagoblin/tests/test_tags.py
new file mode 100644
index 00000000..e25cc283
--- /dev/null
+++ b/mediagoblin/tests/test_tags.py
@@ -0,0 +1,39 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools import text
+
+def test_list_of_dicts_conversion(test_app):
+ """
+ When the user adds tags to a media entry, the string from the form is
+ converted into a list of tags, where each tag is stored in the database
+ as a dict. Each tag dict should contain the tag's name and slug. Another
+ function performs the reverse operation when populating a form to edit tags.
+ """
+ # Leading, trailing, and internal whitespace should be removed and slugified
+ assert text.convert_to_tag_list_of_dicts('sleep , 6 AM, chainsaw! ') == [
+ {'name': u'sleep', 'slug': u'sleep'},
+ {'name': u'6 AM', 'slug': u'6-am'},
+ {'name': u'chainsaw!', 'slug': u'chainsaw'}]
+
+ # If the user enters two identical tags, record only one of them
+ assert text.convert_to_tag_list_of_dicts('echo,echo') == [{'name': u'echo',
+ 'slug': u'echo'}]
+
+ # Make sure converting the list of dicts to a string works
+ assert text.media_tags_as_string([{'name': u'yin', 'slug': u'yin'},
+ {'name': u'yang', 'slug': u'yang'}]) == \
+ u'yin, yang'
diff --git a/mediagoblin/tests/test_timesince.py b/mediagoblin/tests/test_timesince.py
new file mode 100644
index 00000000..6579eb09
--- /dev/null
+++ b/mediagoblin/tests/test_timesince.py
@@ -0,0 +1,57 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from datetime import datetime, timedelta
+
+from mediagoblin.tools.timesince import is_aware, timesince
+
+
+def test_timesince():
+ test_time = datetime.now()
+
+ # it should ignore second and microseconds
+ assert timesince(test_time, test_time + timedelta(microseconds=1)) == "0 minutes"
+ assert timesince(test_time, test_time + timedelta(seconds=1)) == "0 minutes"
+
+ # test minutes, hours, days, weeks, months and years (singular and plural)
+ assert timesince(test_time, test_time + timedelta(minutes=1)) == "1 minute"
+ assert timesince(test_time, test_time + timedelta(minutes=2)) == "2 minutes"
+
+ assert timesince(test_time, test_time + timedelta(hours=1)) == "1 hour"
+ assert timesince(test_time, test_time + timedelta(hours=2)) == "2 hours"
+
+ assert timesince(test_time, test_time + timedelta(days=1)) == "1 day"
+ assert timesince(test_time, test_time + timedelta(days=2)) == "2 days"
+
+ assert timesince(test_time, test_time + timedelta(days=7)) == "1 week"
+ assert timesince(test_time, test_time + timedelta(days=14)) == "2 weeks"
+
+ assert timesince(test_time, test_time + timedelta(days=30)) == "1 month"
+ assert timesince(test_time, test_time + timedelta(days=60)) == "2 months"
+
+ assert timesince(test_time, test_time + timedelta(days=365)) == "1 year"
+ assert timesince(test_time, test_time + timedelta(days=730)) == "2 years"
+
+ # okay now we want to test combinations
+ # e.g. 1 hour, 5 days
+ assert timesince(test_time, test_time + timedelta(days=5, hours=1)) == "5 days, 1 hour"
+
+ assert timesince(test_time, test_time + timedelta(days=15)) == "2 weeks, 1 day"
+
+ assert timesince(test_time, test_time + timedelta(days=97)) == "3 months, 1 week"
+
+ assert timesince(test_time, test_time + timedelta(days=2250)) == "6 years, 2 months"
+
diff --git a/mediagoblin/tests/test_util.py b/mediagoblin/tests/test_util.py
new file mode 100644
index 00000000..bc14f528
--- /dev/null
+++ b/mediagoblin/tests/test_util.py
@@ -0,0 +1,145 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import email
+
+from mediagoblin.tools import common, url, translate, mail, text, testing
+
+testing._activate_testing()
+
+
+def _import_component_testing_method(silly_string):
+ # Just for the sake of testing that our component importer works.
+ return u"'%s' is the silliest string I've ever seen" % silly_string
+
+
+def test_import_component():
+ imported_func = common.import_component(
+ 'mediagoblin.tests.test_util:_import_component_testing_method')
+ result = imported_func('hooobaladoobala')
+ expected = u"'hooobaladoobala' is the silliest string I've ever seen"
+ assert result == expected
+
+
+def test_send_email():
+ mail._clear_test_inboxes()
+
+ # send the email
+ mail.send_email(
+ "sender@mediagoblin.example.org",
+ ["amanda@example.org", "akila@example.org"],
+ "Testing is so much fun!",
+ """HAYYY GUYS!
+
+I hope you like unit tests JUST AS MUCH AS I DO!""")
+
+ # check the main inbox
+ assert len(mail.EMAIL_TEST_INBOX) == 1
+ message = mail.EMAIL_TEST_INBOX.pop()
+ assert message['From'] == "sender@mediagoblin.example.org"
+ assert message['To'] == "amanda@example.org, akila@example.org"
+ assert message['Subject'] == "Testing is so much fun!"
+ assert message.get_payload(decode=True) == """HAYYY GUYS!
+
+I hope you like unit tests JUST AS MUCH AS I DO!"""
+
+ # Check everything that the FakeMhost.sendmail() method got is correct
+ assert len(mail.EMAIL_TEST_MBOX_INBOX) == 1
+ mbox_dict = mail.EMAIL_TEST_MBOX_INBOX.pop()
+ assert mbox_dict['from'] == "sender@mediagoblin.example.org"
+ assert mbox_dict['to'] == ["amanda@example.org", "akila@example.org"]
+ mbox_message = email.message_from_string(mbox_dict['message'])
+ assert mbox_message['From'] == "sender@mediagoblin.example.org"
+ assert mbox_message['To'] == "amanda@example.org, akila@example.org"
+ assert mbox_message['Subject'] == "Testing is so much fun!"
+ assert mbox_message.get_payload(decode=True) == """HAYYY GUYS!
+
+I hope you like unit tests JUST AS MUCH AS I DO!"""
+
+def test_slugify():
+ assert url.slugify(u'a walk in the park') == u'a-walk-in-the-park'
+ assert url.slugify(u'A Walk in the Park') == u'a-walk-in-the-park'
+ assert url.slugify(u'a walk in the park') == u'a-walk-in-the-park'
+ assert url.slugify(u'a walk in-the-park') == u'a-walk-in-the-park'
+ assert url.slugify(u'a w@lk in the park?') == u'a-w-lk-in-the-park'
+ assert url.slugify(u'a walk in the par\u0107') == u'a-walk-in-the-parc'
+ assert url.slugify(u'\u00E0\u0042\u00E7\u010F\u00EB\u0066') == u'abcdef'
+
+def test_locale_to_lower_upper():
+ """
+ Test cc.i18n.util.locale_to_lower_upper()
+ """
+ assert translate.locale_to_lower_upper('en') == 'en'
+ assert translate.locale_to_lower_upper('en_US') == 'en_US'
+ assert translate.locale_to_lower_upper('en-us') == 'en_US'
+
+ # crazy renditions. Useful?
+ assert translate.locale_to_lower_upper('en-US') == 'en_US'
+ assert translate.locale_to_lower_upper('en_us') == 'en_US'
+
+
+def test_locale_to_lower_lower():
+ """
+ Test cc.i18n.util.locale_to_lower_lower()
+ """
+ assert translate.locale_to_lower_lower('en') == 'en'
+ assert translate.locale_to_lower_lower('en_US') == 'en-us'
+ assert translate.locale_to_lower_lower('en-us') == 'en-us'
+
+ # crazy renditions. Useful?
+ assert translate.locale_to_lower_lower('en-US') == 'en-us'
+ assert translate.locale_to_lower_lower('en_us') == 'en-us'
+
+
+def test_gettext_lazy_proxy():
+ from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+ from mediagoblin.tools.translate import pass_to_ugettext, set_thread_locale
+ proxy = _(u"Password")
+ orig = u"Password"
+
+ set_thread_locale("es")
+ p1 = unicode(proxy)
+ p1_should = pass_to_ugettext(orig)
+ assert p1_should != orig, "Test useless, string not translated"
+ assert p1 == p1_should
+
+ set_thread_locale("sv")
+ p2 = unicode(proxy)
+ p2_should = pass_to_ugettext(orig)
+ assert p2_should != orig, "Test broken, string not translated"
+ assert p2 == p2_should
+
+ assert p1_should != p2_should, "Test broken, same translated string"
+ assert p1 != p2
+
+
+def test_html_cleaner():
+ # Remove images
+ result = text.clean_html(
+ '<p>Hi everybody! '
+ '<img src="http://example.org/huge-purple-barney.png" /></p>\n'
+ '<p>:)</p>')
+ assert result == (
+ '<div>'
+ '<p>Hi everybody! </p>\n'
+ '<p>:)</p>'
+ '</div>')
+
+ # Remove evil javascript
+ result = text.clean_html(
+ '<p><a href="javascript:nasty_surprise">innocent link!</a></p>')
+ assert result == (
+ '<p><a href="">innocent link!</a></p>')
diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py
new file mode 100644
index 00000000..6695618b
--- /dev/null
+++ b/mediagoblin/tests/test_workbench.py
@@ -0,0 +1,122 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import tempfile
+
+
+from mediagoblin.tools import workbench
+from mediagoblin.mg_globals import setup_globals
+from mediagoblin.decorators import get_workbench
+from mediagoblin.tests.test_storage import get_tmp_filestorage, cleanup_storage
+
+
+class TestWorkbench(object):
+ def setup(self):
+ self.workbench_base = tempfile.mkdtemp(prefix='gmg_workbench_testing')
+ self.workbench_manager = workbench.WorkbenchManager(
+ self.workbench_base)
+
+ def teardown(self):
+ # If the workbench is empty, this should work.
+ os.rmdir(self.workbench_base)
+
+ def test_create_workbench(self):
+ workbench = self.workbench_manager.create()
+ assert os.path.isdir(workbench.dir)
+ assert workbench.dir.startswith(self.workbench_manager.base_workbench_dir)
+ workbench.destroy()
+
+ def test_joinpath(self):
+ this_workbench = self.workbench_manager.create()
+ tmpname = this_workbench.joinpath('temp.txt')
+ assert tmpname == os.path.join(this_workbench.dir, 'temp.txt')
+ this_workbench.destroy()
+
+ def test_destroy_workbench(self):
+ # kill a workbench
+ this_workbench = self.workbench_manager.create()
+ tmpfile_name = this_workbench.joinpath('temp.txt')
+ tmpfile = file(tmpfile_name, 'w')
+ with tmpfile:
+ tmpfile.write('lollerskates')
+
+ assert os.path.exists(tmpfile_name)
+
+ wb_dir = this_workbench.dir
+ this_workbench.destroy()
+ assert not os.path.exists(tmpfile_name)
+ assert not os.path.exists(wb_dir)
+
+ def test_localized_file(self):
+ tmpdir, this_storage = get_tmp_filestorage()
+ this_workbench = self.workbench_manager.create()
+
+ # Write a brand new file
+ filepath = ['dir1', 'dir2', 'ourfile.txt']
+
+ with this_storage.get_file(filepath, 'w') as our_file:
+ our_file.write('Our file')
+
+ # with a local file storage
+ filename = this_workbench.localized_file(this_storage, filepath)
+ assert filename == os.path.join(
+ tmpdir, 'dir1/dir2/ourfile.txt')
+ this_storage.delete_file(filepath)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
+
+ # with a fake remote file storage
+ tmpdir, this_storage = get_tmp_filestorage(fake_remote=True)
+
+ # ... write a brand new file, again ;)
+ with this_storage.get_file(filepath, 'w') as our_file:
+ our_file.write('Our file')
+
+ filename = this_workbench.localized_file(this_storage, filepath)
+ assert filename == os.path.join(
+ this_workbench.dir, 'ourfile.txt')
+
+ # fake remote file storage, filename_if_copying set
+ filename = this_workbench.localized_file(
+ this_storage, filepath, 'thisfile')
+ assert filename == os.path.join(
+ this_workbench.dir, 'thisfile.txt')
+
+ # fake remote file storage, filename_if_copying set,
+ # keep_extension_if_copying set to false
+ filename = this_workbench.localized_file(
+ this_storage, filepath, 'thisfile.text', False)
+ assert filename == os.path.join(
+ this_workbench.dir, 'thisfile.text')
+
+ this_storage.delete_file(filepath)
+ cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'])
+ this_workbench.destroy()
+
+ def test_workbench_decorator(self):
+ """Test @get_workbench decorator and automatic cleanup"""
+ # The decorator needs mg_globals.workbench_manager
+ setup_globals(workbench_manager=self.workbench_manager)
+
+ @get_workbench
+ def create_it(workbench=None):
+ # workbench dir exists?
+ assert os.path.isdir(workbench.dir)
+ return workbench.dir
+
+ benchdir = create_it()
+ # workbench dir has been cleaned up automatically?
+ assert not os.path.isdir(benchdir)
diff --git a/mediagoblin/tests/testplugins/__init__.py b/mediagoblin/tests/testplugins/__init__.py
new file mode 100644
index 00000000..621845ba
--- /dev/null
+++ b/mediagoblin/tests/testplugins/__init__.py
@@ -0,0 +1,15 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/tests/testplugins/callables1/__init__.py b/mediagoblin/tests/testplugins/callables1/__init__.py
new file mode 100644
index 00000000..fe801a01
--- /dev/null
+++ b/mediagoblin/tests/testplugins/callables1/__init__.py
@@ -0,0 +1,43 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+def setup_plugin():
+ pass
+
+
+def just_one(call_log):
+ call_log.append("expect this one call")
+ return "Called just once"
+
+
+def multi_handle(call_log):
+ call_log.append("Hi, I'm the first")
+ return "the first returns"
+
+def multi_handle_with_canthandle(call_log):
+ return None
+
+
+def expand_tuple(this_tuple):
+ return this_tuple + (1,)
+
+hooks = {
+ 'setup': setup_plugin,
+ 'just_one': just_one,
+ 'multi_handle': multi_handle,
+ 'multi_handle_with_canthandle': multi_handle_with_canthandle,
+ 'expand_tuple': expand_tuple,
+ }
diff --git a/mediagoblin/tests/testplugins/callables2/__init__.py b/mediagoblin/tests/testplugins/callables2/__init__.py
new file mode 100644
index 00000000..9d5cf950
--- /dev/null
+++ b/mediagoblin/tests/testplugins/callables2/__init__.py
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+def setup_plugin():
+ pass
+
+
+def just_one(call_log):
+ assert "SHOULD NOT HAPPEN"
+
+def multi_handle(call_log):
+ call_log.append("Hi, I'm the second")
+ return "the second returns"
+
+def multi_handle_with_canthandle(call_log):
+ call_log.append("Hi, I'm the second")
+ return "the second returns"
+
+def expand_tuple(this_tuple):
+ return this_tuple + (2,)
+
+hooks = {
+ 'setup': setup_plugin,
+ 'just_one': just_one,
+ 'multi_handle': multi_handle,
+ 'multi_handle_with_canthandle': multi_handle_with_canthandle,
+ 'expand_tuple': expand_tuple,
+ }
diff --git a/mediagoblin/tests/testplugins/callables3/__init__.py b/mediagoblin/tests/testplugins/callables3/__init__.py
new file mode 100644
index 00000000..04efc8fc
--- /dev/null
+++ b/mediagoblin/tests/testplugins/callables3/__init__.py
@@ -0,0 +1,41 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+def setup_plugin():
+ pass
+
+
+def just_one(call_log):
+ assert "SHOULD NOT HAPPEN"
+
+def multi_handle(call_log):
+ call_log.append("Hi, I'm the third")
+ return "the third returns"
+
+def multi_handle_with_canthandle(call_log):
+ call_log.append("Hi, I'm the third")
+ return "the third returns"
+
+def expand_tuple(this_tuple):
+ return this_tuple + (3,)
+
+hooks = {
+ 'setup': setup_plugin,
+ 'just_one': just_one,
+ 'multi_handle': multi_handle,
+ 'multi_handle_with_canthandle': multi_handle_with_canthandle,
+ 'expand_tuple': expand_tuple,
+ }
diff --git a/mediagoblin/tests/testplugins/modify_context/__init__.py b/mediagoblin/tests/testplugins/modify_context/__init__.py
new file mode 100644
index 00000000..164e66c1
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/__init__.py
@@ -0,0 +1,55 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools import pluginapi
+import pkg_resources
+
+
+def append_to_specific_context(context):
+ context['specific_page_append'] = 'in yer specificpage'
+ return context
+
+def append_to_global_context(context):
+ context['global_append'] = 'globally appended!'
+ return context
+
+def double_doubleme(context):
+ if 'doubleme' in context:
+ context['doubleme'] = context['doubleme'] * 2
+ return context
+
+
+def setup_plugin():
+ routes = [
+ ('modify_context.specific_page',
+ '/modify_context/specific/',
+ 'mediagoblin.tests.testplugins.modify_context.views:specific'),
+ ('modify_context.general_page',
+ '/modify_context/',
+ 'mediagoblin.tests.testplugins.modify_context.views:general')]
+
+ pluginapi.register_routes(routes)
+ pluginapi.register_template_path(
+ pkg_resources.resource_filename(
+ 'mediagoblin.tests.testplugins.modify_context', 'templates'))
+
+
+hooks = {
+ 'setup': setup_plugin,
+ ('modify_context.specific_page',
+ 'contextplugin/specific.html'): append_to_specific_context,
+ 'template_global_context': append_to_global_context,
+ 'template_context_prerender': double_doubleme}
diff --git a/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html
new file mode 100644
index 00000000..9cf96d3e
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html
@@ -0,0 +1,5 @@
+General page!
+
+global thing: {{ global_append }}
+lol: {{ lol }}
+doubleme: {{ doubleme }}
diff --git a/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html
new file mode 100644
index 00000000..5b1b4c4a
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html
@@ -0,0 +1,6 @@
+Specific page!
+
+specific thing: {{ specific_page_append }}
+global thing: {{ global_append }}
+something: {{ something }}
+doubleme: {{ doubleme }}
diff --git a/mediagoblin/tests/testplugins/modify_context/views.py b/mediagoblin/tests/testplugins/modify_context/views.py
new file mode 100644
index 00000000..701ec6f9
--- /dev/null
+++ b/mediagoblin/tests/testplugins/modify_context/views.py
@@ -0,0 +1,33 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.response import render_to_response
+
+
+def specific(request):
+ return render_to_response(
+ request,
+ 'contextplugin/specific.html',
+ {"something": "orother",
+ "doubleme": "happy"})
+
+
+def general(request):
+ return render_to_response(
+ request,
+ 'contextplugin/general.html',
+ {"lol": "cats",
+ "doubleme": "joy"})
diff --git a/mediagoblin/tests/testplugins/pluginspec/__init__.py b/mediagoblin/tests/testplugins/pluginspec/__init__.py
new file mode 100644
index 00000000..76ca2b1f
--- /dev/null
+++ b/mediagoblin/tests/testplugins/pluginspec/__init__.py
@@ -0,0 +1,22 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+def setup_plugin():
+ pass
+
+hooks = {
+ 'setup': setup_plugin,
+}
diff --git a/mediagoblin/tests/testplugins/pluginspec/config_spec.ini b/mediagoblin/tests/testplugins/pluginspec/config_spec.ini
new file mode 100644
index 00000000..5c9c3bd7
--- /dev/null
+++ b/mediagoblin/tests/testplugins/pluginspec/config_spec.ini
@@ -0,0 +1,4 @@
+[plugin_spec]
+some_string = string(default="blork")
+some_int = integer(default=50)
+dont_change_me = string(default="still the default") \ No newline at end of file
diff --git a/mediagoblin/tests/testplugins/staticstuff/__init__.py b/mediagoblin/tests/testplugins/staticstuff/__init__.py
new file mode 100644
index 00000000..a2591646
--- /dev/null
+++ b/mediagoblin/tests/testplugins/staticstuff/__init__.py
@@ -0,0 +1,36 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.staticdirect import PluginStatic
+from mediagoblin.tools import pluginapi
+from pkg_resources import resource_filename
+
+def setup_plugin():
+ routes = [
+ ('staticstuff.static_demo',
+ '/staticstuff/',
+ 'mediagoblin.tests.testplugins.staticstuff.views:static_demo')]
+
+ pluginapi.register_routes(routes)
+
+
+hooks = {
+ 'setup': setup_plugin,
+ 'static_setup': lambda: PluginStatic(
+ 'staticstuff',
+ resource_filename(
+ 'mediagoblin.tests.testplugins.staticstuff',
+ 'static'))}
diff --git a/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css b/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css
new file mode 100644
index 00000000..1294ab8a
--- /dev/null
+++ b/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css
@@ -0,0 +1,4 @@
+body {
+ background-color: #5edcf1;
+ color: #eb8add;
+} \ No newline at end of file
diff --git a/mediagoblin/tests/testplugins/staticstuff/views.py b/mediagoblin/tests/testplugins/staticstuff/views.py
new file mode 100644
index 00000000..34a5e8cb
--- /dev/null
+++ b/mediagoblin/tests/testplugins/staticstuff/views.py
@@ -0,0 +1,28 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+
+from werkzeug import Response
+
+
+def static_demo(request):
+ return Response(json.dumps({
+ # this does not exist, but we'll pretend it does ;)
+ 'mgoblin_bunny_pic': request.staticdirect(
+ 'images/bunny_pic.png'),
+ 'plugin_bunny_css': request.staticdirect(
+ 'css/bunnify.css', 'staticstuff')}))
diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py
new file mode 100644
index 00000000..2ee39e89
--- /dev/null
+++ b/mediagoblin/tests/tools.py
@@ -0,0 +1,233 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import sys
+import os
+import pkg_resources
+import shutil
+
+from functools import wraps
+
+from paste.deploy import loadapp
+from webtest import TestApp
+
+from mediagoblin import mg_globals
+from mediagoblin.db.models import User, MediaEntry, Collection
+from mediagoblin.tools import testing
+from mediagoblin.init.config import read_mediagoblin_config
+from mediagoblin.db.base import Session
+from mediagoblin.meddleware import BaseMeddleware
+from mediagoblin.auth.lib import bcrypt_gen_password_hash
+from mediagoblin.gmg_commands.dbupdate import run_dbupdate
+
+
+MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__'
+TEST_SERVER_CONFIG = pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'test_paste.ini')
+TEST_APP_CONFIG = pkg_resources.resource_filename(
+ 'mediagoblin.tests', 'test_mgoblin_app.ini')
+
+
+USER_DEV_DIRECTORIES_TO_SETUP = ['media/public', 'media/queue']
+
+
+class TestingMeddleware(BaseMeddleware):
+ """
+ Meddleware for the Unit tests
+
+ It might make sense to perform some tests on all
+ requests/responses. Or prepare them in a special
+ manner. For example all html responses could be tested
+ for being valid html *after* being rendered.
+
+ This module is getting inserted at the front of the
+ meddleware list, which means: requests are handed here
+ first, responses last. So this wraps up the "normal"
+ app.
+
+ If you need to add a test, either add it directly to
+ the appropiate process_request or process_response, or
+ create a new method and call it from process_*.
+ """
+
+ def process_response(self, request, response):
+ # All following tests should be for html only!
+ if getattr(response, 'content_type', None) != "text/html":
+ # Get out early
+ return
+
+ # If the template contains a reference to
+ # /mgoblin_static/ instead of using
+ # /request.staticdirect(), error out here.
+ # This could probably be implemented as a grep on
+ # the shipped templates easier...
+ if response.text.find("/mgoblin_static/") >= 0:
+ raise AssertionError(
+ "Response HTML contains reference to /mgoblin_static/ "
+ "instead of staticdirect. Request was for: "
+ + request.full_path)
+
+ return
+
+
+def get_app(request, paste_config=None, mgoblin_config=None):
+ """Create a MediaGoblin app for testing.
+
+ Args:
+ - request: Not an http request, but a pytest fixture request. We
+ use this to make temporary directories that pytest
+ automatically cleans up as needed.
+ - paste_config: particular paste config used by this application.
+ - mgoblin_config: particular mediagoblin config used by this
+ application.
+ """
+ paste_config = paste_config or TEST_SERVER_CONFIG
+ mgoblin_config = mgoblin_config or TEST_APP_CONFIG
+
+ # This is the directory we're copying the paste/mgoblin config stuff into
+ run_dir = request.config._tmpdirhandler.mktemp(
+ 'mgoblin_app', numbered=True)
+ user_dev_dir = run_dir.mkdir('user_dev').strpath
+
+ new_paste_config = run_dir.join('paste.ini').strpath
+ new_mgoblin_config = run_dir.join('mediagoblin.ini').strpath
+ shutil.copyfile(paste_config, new_paste_config)
+ shutil.copyfile(mgoblin_config, new_mgoblin_config)
+
+ Session.rollback()
+ Session.remove()
+
+ # install user_dev directories
+ for directory in USER_DEV_DIRECTORIES_TO_SETUP:
+ full_dir = os.path.join(user_dev_dir, directory)
+ os.makedirs(full_dir)
+
+ # Get app config
+ global_config, validation_result = read_mediagoblin_config(new_mgoblin_config)
+ app_config = global_config['mediagoblin']
+
+ # Run database setup/migrations
+ run_dbupdate(app_config, global_config)
+
+ # setup app and return
+ test_app = loadapp(
+ 'config:' + new_paste_config)
+
+ # Insert the TestingMeddleware, which can do some
+ # sanity checks on every request/response.
+ # Doing it this way is probably not the cleanest way.
+ # We'll fix it, when we have plugins!
+ mg_globals.app.meddleware.insert(0, TestingMeddleware(mg_globals.app))
+
+ app = TestApp(test_app)
+
+ return app
+
+
+def install_fixtures_simple(db, fixtures):
+ """
+ Very simply install fixtures in the database
+ """
+ for collection_name, collection_fixtures in fixtures.iteritems():
+ collection = db[collection_name]
+ for fixture in collection_fixtures:
+ collection.insert(fixture)
+
+
+def assert_db_meets_expected(db, expected):
+ """
+ Assert a database contains the things we expect it to.
+
+ Objects are found via 'id', so you should make sure your document
+ has an id.
+
+ Args:
+ - db: pymongo or mongokit database connection
+ - expected: the data we expect. Formatted like:
+ {'collection_name': [
+ {'id': 'foo',
+ 'some_field': 'some_value'},]}
+ """
+ for collection_name, collection_data in expected.iteritems():
+ collection = db[collection_name]
+ for expected_document in collection_data:
+ document = collection.find_one({'id': expected_document['id']})
+ assert document is not None # make sure it exists
+ assert document == expected_document # make sure it matches
+
+
+def fixture_add_user(username=u'chris', password=u'toast',
+ active_user=True):
+ # Reuse existing user or create a new one
+ test_user = User.query.filter_by(username=username).first()
+ if test_user is None:
+ test_user = User()
+ test_user.username = username
+ test_user.email = username + u'@example.com'
+ if password is not None:
+ test_user.pw_hash = bcrypt_gen_password_hash(password)
+ if active_user:
+ test_user.email_verified = True
+ test_user.status = u'active'
+
+ test_user.save()
+
+ # Reload
+ test_user = User.query.filter_by(username=username).first()
+
+ # ... and detach from session:
+ Session.expunge(test_user)
+
+ return test_user
+
+
+def fixture_media_entry(title=u"Some title", slug=None,
+ uploader=None, save=True, gen_slug=True):
+ entry = MediaEntry()
+ entry.title = title
+ entry.slug = slug
+ entry.uploader = uploader or fixture_add_user().id
+ entry.media_type = u'image'
+
+ if gen_slug:
+ entry.generate_slug()
+ if save:
+ entry.save()
+
+ return entry
+
+
+def fixture_add_collection(name=u"My first Collection", user=None):
+ if user is None:
+ user = fixture_add_user()
+ coll = Collection.query.filter_by(creator=user.id, title=name).first()
+ if coll is not None:
+ return coll
+ coll = Collection()
+ coll.creator = user.id
+ coll.title = name
+ coll.generate_slug()
+ coll.save()
+
+ # Reload
+ Session.refresh(coll)
+
+ # ... and detach from session:
+ Session.expunge(coll)
+
+ return coll
+
diff --git a/mediagoblin/themes/airy/AGPLv3.txt b/mediagoblin/themes/airy/AGPLv3.txt
new file mode 100644
index 00000000..dba13ed2
--- /dev/null
+++ b/mediagoblin/themes/airy/AGPLv3.txt
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/themes/airy/CC0_1.0.txt b/mediagoblin/themes/airy/CC0_1.0.txt
new file mode 100644
index 00000000..0e259d42
--- /dev/null
+++ b/mediagoblin/themes/airy/CC0_1.0.txt
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/mediagoblin/themes/airy/assets/css/airy.css b/mediagoblin/themes/airy/assets/css/airy.css
new file mode 100644
index 00000000..c4bea5cb
--- /dev/null
+++ b/mediagoblin/themes/airy/assets/css/airy.css
@@ -0,0 +1,82 @@
+body {
+ color: #4a4a4a;
+ background-color: #F7F7F7;
+}
+
+h1,h2,h3 {
+ color: #4a4a4a;
+}
+
+a {
+ color: #37AB74;
+}
+
+.navigation_button {
+ background-color: #fff;
+ border: 1px solid;
+ border-color: #e4e4e4;
+ border-width: 1px 1px 2px;
+ color: #4a4a4a;
+ font-weight: bold;
+}
+
+p.navigation_button {
+ font-weight: normal;
+ color: #A2A2A2;
+}
+
+header {
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #e4e4e4;
+ width: 940px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+@media screen and (max-width: 940px) {
+ header {
+ width: 100%;
+ }
+}
+
+footer {
+ border-top: 1px solid #E4E4E4;
+}
+
+.button_action, .button_action_highlight, .button_form {
+ color: #4a4a4a;
+ background-color: #fff;
+ border: 1px solid;
+ border-color: #E4E4E4;
+ border-width: 1px 1px 2px;
+ padding: 5px 10px;
+}
+
+.button_action_highlight, .button_form {
+ color: #fff;
+ background-color: #37AB74;
+ border-color: #6CAA8E;
+ border-width: 1px 1px 2px;
+}
+
+input, textarea {
+ color: #4a4a4a;
+}
+
+.form_box, .form_box_xl {
+ background-color: #fff;
+ border: 1px solid #e4e4e4;
+ border-radius: 6px;
+}
+
+.media_thumbnail {
+ background-color: #fff;
+}
+
+.media_thumbnail a {
+ color: #4a4a4a;
+}
+
+.empty_space {
+ background-image: url("../images/empty_dots.png");
+}
diff --git a/mediagoblin/themes/airy/assets/images/empty_dots.png b/mediagoblin/themes/airy/assets/images/empty_dots.png
new file mode 100644
index 00000000..5ee050b2
--- /dev/null
+++ b/mediagoblin/themes/airy/assets/images/empty_dots.png
Binary files differ
diff --git a/mediagoblin/themes/airy/assets/images/icon_feed.png b/mediagoblin/themes/airy/assets/images/icon_feed.png
new file mode 100644
index 00000000..18c085b4
--- /dev/null
+++ b/mediagoblin/themes/airy/assets/images/icon_feed.png
Binary files differ
diff --git a/mediagoblin/themes/airy/assets/images/logo.png b/mediagoblin/themes/airy/assets/images/logo.png
new file mode 100644
index 00000000..a6eeaca0
--- /dev/null
+++ b/mediagoblin/themes/airy/assets/images/logo.png
Binary files differ
diff --git a/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html b/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html
new file mode 100644
index 00000000..c8500159
--- /dev/null
+++ b/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html
@@ -0,0 +1,25 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+
+{% block mediagoblin_logo %}
+ <a class="logo"
+ href="{{ request.urlgen('index') }}">
+ <img src="{{ request.staticdirect('/images/logo.png', 'theme') }}"
+ alt="{% trans %}MediaGoblin logo{% endtrans %}" />
+ </a>
+{% endblock mediagoblin_logo -%}
diff --git a/mediagoblin/themes/airy/templates/mediagoblin/extra_head.html b/mediagoblin/themes/airy/templates/mediagoblin/extra_head.html
new file mode 100644
index 00000000..03e7db90
--- /dev/null
+++ b/mediagoblin/themes/airy/templates/mediagoblin/extra_head.html
@@ -0,0 +1,20 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+-#}
+
+<link rel="stylesheet" type="text/css"
+ href="{{ request.staticdirect('/css/airy.css', 'theme') }}"/>
diff --git a/mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html b/mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html
new file mode 100644
index 00000000..cf5099a2
--- /dev/null
+++ b/mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html
@@ -0,0 +1,23 @@
+{#
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#}
+
+<a href="{{ feed_url }}">
+ <img src="{{ request.staticdirect('/images/icon_feed.png', 'theme') }}"
+ class="media_icon" alt="{% trans %}feed icon{% endtrans %}" />
+</a>
+<a href="{{ feed_url }}">{%- trans %}Atom feed{% endtrans -%}</a>
diff --git a/mediagoblin/themes/airy/theme.cfg b/mediagoblin/themes/airy/theme.cfg
new file mode 100644
index 00000000..b02986ba
--- /dev/null
+++ b/mediagoblin/themes/airy/theme.cfg
@@ -0,0 +1,4 @@
+[theme]
+name = Airy
+description = A light theme based on the default MediaGoblin theme. I have a nagging suspicion that I'm subconciously copying something else, so if you come across a website that looks exactly the same, let me know!
+licensing = AGPLv3 or later templates; assets (images/css) waived under CC0 1.0
diff --git a/mediagoblin/tools/__init__.py b/mediagoblin/tools/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediagoblin/tools/__init__.py
diff --git a/mediagoblin/tools/common.py b/mediagoblin/tools/common.py
new file mode 100644
index 00000000..34586611
--- /dev/null
+++ b/mediagoblin/tools/common.py
@@ -0,0 +1,73 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+
+global TESTS_ENABLED
+TESTS_ENABLED = False
+
+
+def import_component(import_string):
+ """
+ Import a module component defined by STRING. Probably a method,
+ class, or global variable.
+
+ Args:
+ - import_string: a string that defines what to import. Written
+ in the format of "module1.module2:component"
+ """
+ module_name, func_name = import_string.split(':', 1)
+ __import__(module_name)
+ module = sys.modules[module_name]
+ func = getattr(module, func_name)
+ return func
+
+
+def simple_printer(string):
+ """
+ Prints a string, but without an auto \n at the end.
+
+ Useful for places where we want to dependency inject for printing.
+ """
+ sys.stdout.write(string)
+ sys.stdout.flush()
+
+
+class CollectingPrinter(object):
+ """
+ Another printer object, this one useful for capturing output for
+ examination during testing or otherwise.
+
+ Use this like:
+
+ >>> printer = CollectingPrinter()
+ >>> printer("herp derp\n")
+ >>> printer("lollerskates\n")
+ >>> printer.combined_string
+ "herp derp\nlollerskates\n"
+ """
+ def __init__(self):
+ self.collection = []
+
+ def __call__(self, string):
+ self.collection.append(string)
+
+ @property
+ def combined_string(self):
+ return u''.join(self.collection)
+
+
diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py
new file mode 100644
index 00000000..1379d21b
--- /dev/null
+++ b/mediagoblin/tools/crypto.py
@@ -0,0 +1,113 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import errno
+import itsdangerous
+import logging
+import os.path
+import random
+import tempfile
+from mediagoblin import mg_globals
+
+_log = logging.getLogger(__name__)
+
+
+# Use the system (hardware-based) random number generator if it exists.
+# -- this optimization is lifted from Django
+try:
+ getrandbits = random.SystemRandom().getrandbits
+except AttributeError:
+ getrandbits = random.getrandbits
+
+
+__itsda_secret = None
+
+
+def load_key(filename):
+ global __itsda_secret
+ key_file = open(filename)
+ try:
+ __itsda_secret = key_file.read()
+ finally:
+ key_file.close()
+
+
+def create_key(key_dir, key_filepath):
+ global __itsda_secret
+ old_umask = os.umask(077)
+ key_file = None
+ try:
+ if not os.path.isdir(key_dir):
+ os.makedirs(key_dir)
+ _log.info("Created %s", key_dir)
+ key = str(getrandbits(192))
+ key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin',
+ delete=False)
+ key_file.write(key)
+ key_file.flush()
+ os.rename(key_file.name, key_filepath)
+ key_file.close()
+ finally:
+ os.umask(old_umask)
+ if (key_file is not None) and (not key_file.closed):
+ key_file.close()
+ os.unlink(key_file.name)
+ __itsda_secret = key
+ _log.info("Saved new key for It's Dangerous")
+
+
+def setup_crypto():
+ global __itsda_secret
+ key_dir = mg_globals.app_config["crypto_path"]
+ key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin')
+ try:
+ load_key(key_filepath)
+ except IOError, error:
+ if error.errno != errno.ENOENT:
+ raise
+ create_key(key_dir, key_filepath)
+
+
+def get_timed_signer_url(namespace):
+ """
+ This gives a basic signing/verifying object.
+
+ The namespace makes sure signed tokens can't be used in
+ a different area. Like using a forgot-password-token as
+ a session cookie.
+
+ Basic usage:
+
+ .. code-block:: python
+
+ _signer = None
+ TOKEN_VALID_DAYS = 10
+ def setup():
+ global _signer
+ _signer = get_timed_signer_url("session cookie")
+ def create_token(obj):
+ return _signer.dumps(obj)
+ def parse_token(token):
+ # This might raise an exception in case
+ # of an invalid token, or an expired token.
+ return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600)
+
+ For more details see
+ http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer
+ """
+ assert __itsda_secret is not None
+ return itsdangerous.URLSafeTimedSerializer(__itsda_secret,
+ salt=namespace)
diff --git a/mediagoblin/tools/exif.py b/mediagoblin/tools/exif.py
new file mode 100644
index 00000000..6b3639e8
--- /dev/null
+++ b/mediagoblin/tools/exif.py
@@ -0,0 +1,187 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+try:
+ from EXIF import process_file, Ratio
+except ImportError:
+ from mediagoblin.tools.extlib.EXIF import process_file, Ratio
+
+from mediagoblin.processing import BadMediaFail
+from mediagoblin.tools.translate import pass_to_ugettext as _
+
+# A list of tags that should be stored for faster access
+USEFUL_TAGS = [
+ 'Image Make',
+ 'Image Model',
+ 'EXIF FNumber',
+ 'EXIF Flash',
+ 'EXIF FocalLength',
+ 'EXIF ExposureTime',
+ 'EXIF ApertureValue',
+ 'EXIF ExposureMode',
+ 'EXIF ISOSpeedRatings',
+ 'EXIF UserComment',
+ ]
+
+
+def exif_image_needs_rotation(exif_tags):
+ """
+ Returns True if EXIF orientation requires rotation
+ """
+ return 'Image Orientation' in exif_tags \
+ and exif_tags['Image Orientation'].values[0] != 1
+
+
+def exif_fix_image_orientation(im, exif_tags):
+ """
+ Translate any EXIF orientation to raw orientation
+
+ Cons:
+ - Well, it changes the image, which means we'll recompress
+ it... not a problem if scaling it down already anyway. We might
+ lose some quality in recompressing if it's at the same-size
+ though
+
+ Pros:
+ - Prevents neck pain
+ """
+ # Rotate image
+ if 'Image Orientation' in exif_tags:
+ rotation_map = {
+ 3: 180,
+ 6: 270,
+ 8: 90}
+ orientation = exif_tags['Image Orientation'].values[0]
+ if orientation in rotation_map:
+ im = im.rotate(
+ rotation_map[orientation])
+
+ return im
+
+
+def extract_exif(filename):
+ """
+ Returns EXIF tags found in file at ``filename``
+ """
+ try:
+ with file(filename) as image:
+ return process_file(image, details=False)
+ except IOError:
+ raise BadMediaFail(_('Could not read the image file.'))
+
+
+def clean_exif(exif):
+ '''
+ Clean the result from anything the database cannot handle
+ '''
+ # Discard any JPEG thumbnail, for database compatibility
+ # and that I cannot see a case when we would use it.
+ # It takes up some space too.
+ disabled_tags = [
+ 'Thumbnail JPEGInterchangeFormatLength',
+ 'JPEGThumbnail',
+ 'Thumbnail JPEGInterchangeFormat']
+
+ return dict((key, _ifd_tag_to_dict(value)) for (key, value)
+ in exif.iteritems() if key not in disabled_tags)
+
+
+def _ifd_tag_to_dict(tag):
+ '''
+ Takes an IFD tag object from the EXIF library and converts it to a dict
+ that can be stored as JSON in the database.
+ '''
+ data = {
+ 'printable': tag.printable,
+ 'tag': tag.tag,
+ 'field_type': tag.field_type,
+ 'field_offset': tag.field_offset,
+ 'field_length': tag.field_length,
+ 'values': None}
+
+ if isinstance(tag.printable, str):
+ # Force it to be decoded as UTF-8 so that it'll fit into the DB
+ data['printable'] = tag.printable.decode('utf8', 'replace')
+
+ if type(tag.values) == list:
+ data['values'] = [_ratio_to_list(val) if isinstance(val, Ratio) else val
+ for val in tag.values]
+ else:
+ if isinstance(tag.values, str):
+ # Force UTF-8, so that it fits into the DB
+ data['values'] = tag.values.decode('utf8', 'replace')
+ else:
+ data['values'] = tag.values
+
+ return data
+
+
+def _ratio_to_list(ratio):
+ return [ratio.num, ratio.den]
+
+
+def get_useful(tags):
+ return dict((key, tag) for (key, tag) in tags.iteritems())
+
+
+def get_gps_data(tags):
+ """
+ Processes EXIF data returned by EXIF.py
+ """
+ gps_data = {}
+
+ if not 'Image GPSInfo' in tags:
+ return gps_data
+
+ try:
+ dms_data = {
+ 'latitude': tags['GPS GPSLatitude'],
+ 'longitude': tags['GPS GPSLongitude']}
+
+ for key, dat in dms_data.iteritems():
+ gps_data[key] = (
+ lambda v:
+ float(v[0].num) / float(v[0].den) \
+ + (float(v[1].num) / float(v[1].den) / 60) \
+ + (float(v[2].num) / float(v[2].den) / (60 * 60))
+ )(dat.values)
+
+ if tags['GPS GPSLatitudeRef'].values == 'S':
+ gps_data['latitude'] /= -1
+
+ if tags['GPS GPSLongitudeRef'].values == 'W':
+ gps_data['longitude'] /= -1
+
+ except KeyError:
+ pass
+
+ try:
+ gps_data['direction'] = (
+ lambda d:
+ float(d.num) / float(d.den)
+ )(tags['GPS GPSImgDirection'].values[0])
+ except KeyError:
+ pass
+
+ try:
+ gps_data['altitude'] = (
+ lambda a:
+ float(a.num) / float(a.den)
+ )(tags['GPS GPSAltitude'].values[0])
+ except KeyError:
+ pass
+
+ return gps_data
diff --git a/mediagoblin/tools/extlib/EXIF.py b/mediagoblin/tools/extlib/EXIF.py
new file mode 120000
index 00000000..82a2fb30
--- /dev/null
+++ b/mediagoblin/tools/extlib/EXIF.py
@@ -0,0 +1 @@
+../../../extlib/exif/EXIF.py \ No newline at end of file
diff --git a/mediagoblin/tools/extlib/__init__.py b/mediagoblin/tools/extlib/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/mediagoblin/tools/extlib/__init__.py
diff --git a/mediagoblin/tools/extlib/wtf_html5.py b/mediagoblin/tools/extlib/wtf_html5.py
new file mode 120000
index 00000000..5028c599
--- /dev/null
+++ b/mediagoblin/tools/extlib/wtf_html5.py
@@ -0,0 +1 @@
+../../../extlib/flask-wtf/html5.py \ No newline at end of file
diff --git a/mediagoblin/tools/files.py b/mediagoblin/tools/files.py
new file mode 100644
index 00000000..848c86f2
--- /dev/null
+++ b/mediagoblin/tools/files.py
@@ -0,0 +1,43 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin import mg_globals
+
+
+def delete_media_files(media):
+ """
+ Delete all files associated with a MediaEntry
+
+ Arguments:
+ - media: A MediaEntry document
+ """
+ no_such_files = []
+ for listpath in media.media_files.itervalues():
+ try:
+ mg_globals.public_store.delete_file(
+ listpath)
+ except OSError:
+ no_such_files.append("/".join(listpath))
+
+ for attachment in media.attachment_files:
+ try:
+ mg_globals.public_store.delete_file(
+ attachment['filepath'])
+ except OSError:
+ no_such_files.append("/".join(attachment['filepath']))
+
+ if no_such_files:
+ raise OSError(", ".join(no_such_files))
diff --git a/mediagoblin/tools/licenses.py b/mediagoblin/tools/licenses.py
new file mode 100644
index 00000000..a964980e
--- /dev/null
+++ b/mediagoblin/tools/licenses.py
@@ -0,0 +1,69 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from collections import namedtuple
+# Give a License attribute names: uri, name, abbreviation
+License = namedtuple("License", ["abbreviation", "name", "uri"])
+
+SORTED_LICENSES = [
+ License("All rights reserved", "No license specified", ""),
+ License("CC BY 3.0", "Creative Commons Attribution Unported 3.0",
+ "http://creativecommons.org/licenses/by/3.0/"),
+ License("CC BY-SA 3.0",
+ "Creative Commons Attribution-ShareAlike Unported 3.0",
+ "http://creativecommons.org/licenses/by-sa/3.0/"),
+ License("CC BY-ND 3.0",
+ "Creative Commons Attribution-NoDerivs 3.0 Unported",
+ "http://creativecommons.org/licenses/by-nd/3.0/"),
+ License("CC BY-NC 3.0",
+ "Creative Commons Attribution-NonCommercial Unported 3.0",
+ "http://creativecommons.org/licenses/by-nc/3.0/"),
+ License("CC BY-NC-SA 3.0",
+ "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported",
+ "http://creativecommons.org/licenses/by-nc-sa/3.0/"),
+ License("CC BY-NC-ND 3.0",
+ "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported",
+ "http://creativecommons.org/licenses/by-nc-nd/3.0/"),
+ License("CC0 1.0",
+ "Creative Commons CC0 1.0 Universal",
+ "http://creativecommons.org/publicdomain/zero/1.0/"),
+ License("Public Domain","Public Domain",
+ "http://creativecommons.org/publicdomain/mark/1.0/"),
+ ]
+
+# dict {uri: License,...} to enable fast license lookup by uri. Ideally,
+# we'd want to use an OrderedDict (python 2.7+) here to avoid having the
+# same data in two structures
+SUPPORTED_LICENSES = dict(((l.uri, l) for l in SORTED_LICENSES))
+
+
+def get_license_by_url(url):
+ """Look up a license by its url and return the License object"""
+ try:
+ return SUPPORTED_LICENSES[url]
+ except KeyError:
+ # in case of an unknown License, just display the url given
+ # rather than exploding in the user's face.
+ return License(url, url, url)
+
+
+def licenses_as_choices():
+ """List of (uri, abbreviation) tuples for HTML choice field population
+
+ The data seems to be consumed/deleted during usage, so hand over a
+ throwaway list, rather than just a generator.
+ """
+ return [(lic.uri, lic.abbreviation) for lic in SORTED_LICENSES]
diff --git a/mediagoblin/tools/mail.py b/mediagoblin/tools/mail.py
new file mode 100644
index 00000000..6886c859
--- /dev/null
+++ b/mediagoblin/tools/mail.py
@@ -0,0 +1,150 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import smtplib
+from email.MIMEText import MIMEText
+from mediagoblin import mg_globals, messages
+from mediagoblin.tools import common
+
+### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+### Special email test stuff begins HERE
+### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# We have two "test inboxes" here:
+#
+# EMAIL_TEST_INBOX:
+# ----------------
+# If you're writing test views, you'll probably want to check this.
+# It contains a list of MIMEText messages.
+#
+# EMAIL_TEST_MBOX_INBOX:
+# ----------------------
+# This collects the messages from the FakeMhost inbox. It's reslly
+# just here for testing the send_email method itself.
+#
+# Anyway this contains:
+# - from
+# - to: a list of email recipient addresses
+# - message: not just the body, but the whole message, including
+# headers, etc.
+#
+# ***IMPORTANT!***
+# ----------------
+# Before running tests that call functions which send email, you should
+# always call _clear_test_inboxes() to "wipe" the inboxes clean.
+
+EMAIL_TEST_INBOX = []
+EMAIL_TEST_MBOX_INBOX = []
+
+
+class FakeMhost(object):
+ """
+ Just a fake mail host so we can capture and test messages
+ from send_email
+ """
+ def login(self, *args, **kwargs):
+ pass
+
+ def sendmail(self, from_addr, to_addrs, message):
+ EMAIL_TEST_MBOX_INBOX.append(
+ {'from': from_addr,
+ 'to': to_addrs,
+ 'message': message})
+
+
+def _clear_test_inboxes():
+ global EMAIL_TEST_INBOX
+ global EMAIL_TEST_MBOX_INBOX
+ EMAIL_TEST_INBOX = []
+ EMAIL_TEST_MBOX_INBOX = []
+
+
+### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+### </Special email test stuff>
+### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+def send_email(from_addr, to_addrs, subject, message_body):
+ """
+ Simple email sending wrapper, use this so we can capture messages
+ for unit testing purposes.
+
+ Args:
+ - from_addr: address you're sending the email from
+ - to_addrs: list of recipient email addresses
+ - subject: subject of the email
+ - message_body: email body text
+ """
+ if common.TESTS_ENABLED or mg_globals.app_config['email_debug_mode']:
+ mhost = FakeMhost()
+ elif not mg_globals.app_config['email_debug_mode']:
+ mhost = smtplib.SMTP(
+ mg_globals.app_config['email_smtp_host'],
+ mg_globals.app_config['email_smtp_port'])
+
+ # SMTP.__init__ Issues SMTP.connect implicitly if host
+ if not mg_globals.app_config['email_smtp_host']: # e.g. host = ''
+ mhost.connect() # We SMTP.connect explicitly
+
+ if ((not common.TESTS_ENABLED)
+ and (mg_globals.app_config['email_smtp_user']
+ or mg_globals.app_config['email_smtp_pass'])):
+ mhost.login(
+ mg_globals.app_config['email_smtp_user'],
+ mg_globals.app_config['email_smtp_pass'])
+
+ message = MIMEText(message_body.encode('utf-8'), 'plain', 'utf-8')
+ message['Subject'] = subject
+ message['From'] = from_addr
+ message['To'] = ', '.join(to_addrs)
+
+ if common.TESTS_ENABLED:
+ EMAIL_TEST_INBOX.append(message)
+
+ elif mg_globals.app_config['email_debug_mode']:
+ print u"===== Email ====="
+ print u"From address: %s" % message['From']
+ print u"To addresses: %s" % message['To']
+ print u"Subject: %s" % message['Subject']
+ print u"-- Body: --"
+ print message.get_payload(decode=True)
+
+ return mhost.sendmail(from_addr, to_addrs, message.as_string())
+
+
+def normalize_email(email):
+ """return case sensitive part, lower case domain name
+
+ :returns: None in case of broken email addresses"""
+ try:
+ em_user, em_dom = email.split('@', 1)
+ except ValueError:
+ # email contained no '@'
+ return None
+ email = "@".join((em_user, em_dom.lower()))
+ return email
+
+
+def email_debug_message(request):
+ """
+ If the server is running in email debug mode (which is
+ the current default), give a debug message to the user
+ so that they have an idea where to find their email.
+ """
+ if mg_globals.app_config['email_debug_mode']:
+ # DEBUG message, no need to translate
+ messages.add_message(request, messages.DEBUG,
+ u"This instance is running in email debug mode. "
+ u"The email will be on the console of the server process.")
diff --git a/mediagoblin/tools/pagination.py b/mediagoblin/tools/pagination.py
new file mode 100644
index 00000000..d0f08c94
--- /dev/null
+++ b/mediagoblin/tools/pagination.py
@@ -0,0 +1,113 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import urllib
+import copy
+from math import ceil, floor
+from itertools import izip, count
+
+
+PAGINATION_DEFAULT_PER_PAGE = 30
+
+
+class Pagination(object):
+ """
+ Pagination class for database queries.
+
+ Initialization through __init__(self, cursor, page=1, per_page=2),
+ get actual data slice through __call__().
+ """
+
+ def __init__(self, page, cursor, per_page=PAGINATION_DEFAULT_PER_PAGE,
+ jump_to_id=False):
+ """
+ Initializes Pagination
+
+ Args:
+ - page: requested page
+ - per_page: number of objects per page
+ - cursor: db cursor
+ - jump_to_id: object id, sets the page to the page containing the
+ object with id == jump_to_id.
+ """
+ self.page = page
+ self.per_page = per_page
+ self.cursor = cursor
+ self.total_count = self.cursor.count()
+ self.active_id = None
+
+ if jump_to_id:
+ cursor = copy.copy(self.cursor)
+
+ for (doc, increment) in izip(cursor, count(0)):
+ if doc.id == jump_to_id:
+ self.page = 1 + int(floor(increment / self.per_page))
+
+ self.active_id = jump_to_id
+ break
+
+ def __call__(self):
+ """
+ Returns slice of objects for the requested page
+ """
+ # TODO, return None for out of index so templates can
+ # distinguish between empty galleries and out-of-bound pages???
+ return self.cursor.slice(
+ (self.page - 1) * self.per_page,
+ self.page * self.per_page)
+
+ @property
+ def pages(self):
+ return int(ceil(self.total_count / float(self.per_page)))
+
+ @property
+ def has_prev(self):
+ return self.page > 1
+
+ @property
+ def has_next(self):
+ return self.page < self.pages
+
+ def iter_pages(self, left_edge=2, left_current=2,
+ right_current=5, right_edge=2):
+ last = 0
+ for num in xrange(1, self.pages + 1):
+ if num <= left_edge or \
+ (num > self.page - left_current - 1 and \
+ num < self.page + right_current) or \
+ num > self.pages - right_edge:
+ if last + 1 != num:
+ yield None
+ yield num
+ last = num
+
+ def get_page_url_explicit(self, base_url, get_params, page_no):
+ """
+ Get a page url by adding a page= parameter to the base url
+ """
+ new_get_params = dict(get_params) or {}
+ new_get_params['page'] = page_no
+ return "%s?%s" % (
+ base_url, urllib.urlencode(new_get_params))
+
+ def get_page_url(self, request, page_no):
+ """
+ Get a new page url based of the request, and the new page number.
+
+ This is a nice wrapper around get_page_url_explicit()
+ """
+ return self.get_page_url_explicit(
+ request.full_path, request.GET, page_no)
diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py
new file mode 100644
index 00000000..3f98aa8a
--- /dev/null
+++ b/mediagoblin/tools/pluginapi.py
@@ -0,0 +1,367 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+This module implements the plugin api bits.
+
+Two things about things in this module:
+
+1. they should be excessively well documented because we should pull
+ from this file for the docs
+
+2. they should be well tested
+
+
+How do plugins work?
+====================
+
+Plugins are structured like any Python project. You create a Python package.
+In that package, you define a high-level ``__init__.py`` module that has a
+``hooks`` dict that maps hooks to callables that implement those hooks.
+
+Additionally, you want a LICENSE file that specifies the license and a
+``setup.py`` that specifies the metadata for packaging your plugin. A rough
+file structure could look like this::
+
+ myplugin/
+ |- setup.py # plugin project packaging metadata
+ |- README # holds plugin project information
+ |- LICENSE # holds license information
+ |- myplugin/ # plugin package directory
+ |- __init__.py # has hooks dict and code
+
+
+Lifecycle
+=========
+
+1. All the modules listed as subsections of the ``plugins`` section in
+ the config file are imported. MediaGoblin registers any hooks in
+ the ``hooks`` dict of those modules.
+
+2. After all plugin modules are imported, the ``setup`` hook is called
+ allowing plugins to do any set up they need to do.
+
+"""
+
+import logging
+
+from functools import wraps
+
+from mediagoblin import mg_globals
+
+
+_log = logging.getLogger(__name__)
+
+
+class PluginManager(object):
+ """Manager for plugin things
+
+ .. Note::
+
+ This is a Borg class--there is one and only one of this class.
+ """
+ __state = {
+ # list of plugin classes
+ "plugins": [],
+
+ # map of hook names -> list of callables for that hook
+ "hooks": {},
+
+ # list of registered template paths
+ "template_paths": set(),
+
+ # list of template hooks
+ "template_hooks": {},
+
+ # list of registered routes
+ "routes": [],
+ }
+
+ def clear(self):
+ """This is only useful for testing."""
+ # Why lists don't have a clear is not clear.
+ del self.plugins[:]
+ del self.routes[:]
+ self.hooks.clear()
+ self.template_paths.clear()
+
+ def __init__(self):
+ self.__dict__ = self.__state
+
+ def register_plugin(self, plugin):
+ """Registers a plugin class"""
+ self.plugins.append(plugin)
+
+ def register_hooks(self, hook_mapping):
+ """Takes a hook_mapping and registers all the hooks"""
+ for hook, callables in hook_mapping.items():
+ if isinstance(callables, (list, tuple)):
+ self.hooks.setdefault(hook, []).extend(list(callables))
+ else:
+ # In this case, it's actually a single callable---not a
+ # list of callables.
+ self.hooks.setdefault(hook, []).append(callables)
+
+ def get_hook_callables(self, hook_name):
+ return self.hooks.get(hook_name, [])
+
+ def register_template_path(self, path):
+ """Registers a template path"""
+ self.template_paths.add(path)
+
+ def get_template_paths(self):
+ """Returns a tuple of registered template paths"""
+ return tuple(self.template_paths)
+
+ def register_route(self, route):
+ """Registers a single route"""
+ _log.debug('registering route: {0}'.format(route))
+ self.routes.append(route)
+
+ def get_routes(self):
+ return tuple(self.routes)
+
+ def register_template_hooks(self, template_hooks):
+ for hook, templates in template_hooks.items():
+ if isinstance(templates, (list, tuple)):
+ self.template_hooks.setdefault(hook, []).extend(list(templates))
+ else:
+ # In this case, it's actually a single callable---not a
+ # list of callables.
+ self.template_hooks.setdefault(hook, []).append(templates)
+
+ def get_template_hooks(self, hook_name):
+ return self.template_hooks.get(hook_name, [])
+
+
+def register_routes(routes):
+ """Registers one or more routes
+
+ If your plugin handles requests, then you need to call this with
+ the routes your plugin handles.
+
+ A "route" is a `routes.Route` object. See `the routes.Route
+ documentation
+ <http://routes.readthedocs.org/en/latest/modules/route.html>`_ for
+ more details.
+
+ Example passing in a single route:
+
+ >>> register_routes(('about-view', '/about',
+ ... 'mediagoblin.views:about_view_handler'))
+
+ Example passing in a list of routes:
+
+ >>> register_routes([
+ ... ('contact-view', '/contact', 'mediagoblin.views:contact_handler'),
+ ... ('about-view', '/about', 'mediagoblin.views:about_handler')
+ ... ])
+
+
+ .. Note::
+
+ Be careful when designing your route urls. If they clash with
+ core urls, then it could result in DISASTER!
+ """
+ if isinstance(routes, list):
+ for route in routes:
+ PluginManager().register_route(route)
+ else:
+ PluginManager().register_route(routes)
+
+
+def register_template_path(path):
+ """Registers a path for template loading
+
+ If your plugin has templates, then you need to call this with
+ the absolute path of the root of templates directory.
+
+ Example:
+
+ >>> my_plugin_dir = os.path.dirname(__file__)
+ >>> template_dir = os.path.join(my_plugin_dir, 'templates')
+ >>> register_template_path(template_dir)
+
+ .. Note::
+
+ You can only do this in `setup_plugins()`. Doing this after
+ that will have no effect on template loading.
+
+ """
+ PluginManager().register_template_path(path)
+
+
+def get_config(key):
+ """Retrieves the configuration for a specified plugin by key
+
+ Example:
+
+ >>> get_config('mediagoblin.plugins.sampleplugin')
+ {'foo': 'bar'}
+ >>> get_config('myplugin')
+ {}
+ >>> get_config('flatpages')
+ {'directory': '/srv/mediagoblin/pages', 'nesting': 1}}
+
+ """
+
+ global_config = mg_globals.global_config
+ plugin_section = global_config.get('plugins', {})
+ return plugin_section.get(key, {})
+
+
+def register_template_hooks(template_hooks):
+ """
+ Register a dict of template hooks.
+
+ Takes template_hooks as an argument, which is a dictionary of
+ template hook names/keys to the templates they should provide.
+ (The value can either be a single template path or an iterable
+ of paths.)
+
+ Example:
+
+ .. code-block:: python
+
+ {"media_sidebar": "/plugin/sidemess/mess_up_the_side.html",
+ "media_descriptionbox": ["/plugin/sidemess/even_more_mess.html",
+ "/plugin/sidemess/so_much_mess.html"]}
+ """
+ PluginManager().register_template_hooks(template_hooks)
+
+
+def get_hook_templates(hook_name):
+ """
+ Get a list of hook templates for this hook_name.
+
+ Note: for the most part, you access this via a template tag, not
+ this method directly, like so:
+
+ .. code-block:: html+jinja
+
+ {% template_hook "media_sidebar" %}
+
+ ... which will include all templates for you, partly using this
+ method.
+
+ However, this method is exposed to templates, and if you wish, you
+ can iterate over templates in a template hook manually like so:
+
+ .. code-block:: html+jinja
+
+ {% for template_path in get_hook_templates("media_sidebar") %}
+ <div class="extra_structure">
+ {% include template_path %}
+ </div>
+ {% endfor %}
+
+ Returns:
+ A list of strings representing template paths.
+ """
+ return PluginManager().get_template_hooks(hook_name)
+
+
+#############################
+## Hooks: The Next Generation
+#############################
+
+
+def hook_handle(hook_name, *args, **kwargs):
+ """
+ Run through hooks attempting to find one that handle this hook.
+
+ All callables called with the same arguments until one handles
+ things and returns a non-None value.
+
+ (If you are writing a handler and you don't have a particularly
+ useful value to return even though you've handled this, returning
+ True is a good solution.)
+
+ Note that there is a special keyword argument:
+ if "default_handler" is passed in as a keyword argument, this will
+ be used if no handler is found.
+
+ Some examples of using this:
+ - You need an interface implemented, but only one fit for it
+ - You need to *do* something, but only one thing needs to do it.
+ """
+ default_handler = kwargs.pop('default_handler', None)
+
+ callables = PluginManager().get_hook_callables(hook_name)
+
+ result = None
+
+ for callable in callables:
+ result = callable(*args, **kwargs)
+
+ if result is not None:
+ break
+
+ if result is None and default_handler is not None:
+ result = default_handler(*args, **kwargs)
+
+ return result
+
+
+def hook_runall(hook_name, *args, **kwargs):
+ """
+ Run through all callable hooks and pass in arguments.
+
+ All non-None results are accrued in a list and returned from this.
+ (Other "false-like" values like False and friends are still
+ accrued, however.)
+
+ Some examples of using this:
+ - You have an interface call where actually multiple things can
+ and should implement it
+ - You need to get a list of things from various plugins that
+ handle them and do something with them
+ - You need to *do* something, and actually multiple plugins need
+ to do it separately
+ """
+ callables = PluginManager().get_hook_callables(hook_name)
+
+ results = []
+
+ for callable in callables:
+ result = callable(*args, **kwargs)
+
+ if result is not None:
+ results.append(result)
+
+ return results
+
+
+def hook_transform(hook_name, arg):
+ """
+ Run through a bunch of hook callables and transform some input.
+
+ Note that unlike the other hook tools, this one only takes ONE
+ argument. This argument is passed to each function, which in turn
+ returns something that becomes the input of the next callable.
+
+ Some examples of using this:
+ - You have an object, say a form, but you want plugins to each be
+ able to modify it.
+ """
+ result = arg
+
+ callables = PluginManager().get_hook_callables(hook_name)
+
+ for callable in callables:
+ result = callable(result)
+
+ return result
diff --git a/mediagoblin/tools/processing.py b/mediagoblin/tools/processing.py
new file mode 100644
index 00000000..2abe6452
--- /dev/null
+++ b/mediagoblin/tools/processing.py
@@ -0,0 +1,87 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import json
+import traceback
+
+from urllib2 import urlopen, Request, HTTPError
+from urllib import urlencode
+
+_log = logging.getLogger(__name__)
+
+TESTS_CALLBACKS = {}
+
+
+def create_post_request(url, data, **kw):
+ '''
+ Issue a HTTP POST request.
+
+ Args:
+ url: The URL to which the POST request should be issued
+ data: The data to be send in the body of the request
+ **kw:
+ data_parser: The parser function that is used to parse the `data`
+ argument
+ '''
+ data_parser = kw.get('data_parser', urlencode)
+ headers = kw.get('headers', {})
+
+ return Request(url, data_parser(data), headers=headers)
+
+
+def json_processing_callback(entry):
+ '''
+ Send an HTTP post to the registered callback url, if any.
+ '''
+ if not entry.processing_metadata:
+ _log.debug('No processing callback for {0}'.format(entry))
+ return
+
+ url = entry.processing_metadata[0].callback_url
+
+ _log.debug('Sending processing callback for {0} ({1})'.format(
+ entry,
+ url))
+
+ headers = {
+ 'Content-Type': 'application/json'}
+
+ data = {
+ 'id': entry.id,
+ 'state': entry.state}
+
+ # Trigger testing mode, no callback will be sent
+ if url.endswith('secrettestmediagoblinparam'):
+ TESTS_CALLBACKS.update({url: data})
+ return True
+
+ request = create_post_request(
+ url,
+ data,
+ headers=headers,
+ data_parser=json.dumps)
+
+ try:
+ urlopen(request)
+ _log.debug('Processing callback for {0} sent'.format(entry))
+
+ return True
+ except HTTPError:
+ _log.error('Failed to send callback: {0}'.format(
+ traceback.format_exc()))
+
+ return False
diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py
new file mode 100644
index 00000000..ee342eae
--- /dev/null
+++ b/mediagoblin/tools/request.py
@@ -0,0 +1,38 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+from mediagoblin.db.models import User
+
+_log = logging.getLogger(__name__)
+
+
+def setup_user_in_request(request):
+ """
+ Examine a request and tack on a request.user parameter if that's
+ appropriate.
+ """
+ if 'user_id' not in request.session:
+ request.user = None
+ return
+
+ request.user = User.query.get(request.session['user_id'])
+
+ if not request.user:
+ # Something's wrong... this user doesn't exist? Invalidate
+ # this session.
+ _log.warn("Killing session for user id %r", request.session['user_id'])
+ request.session.delete()
diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py
new file mode 100644
index 00000000..aaf31d0b
--- /dev/null
+++ b/mediagoblin/tools/response.py
@@ -0,0 +1,108 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import werkzeug.utils
+from werkzeug.wrappers import Response as wz_Response
+from mediagoblin.tools.template import render_template
+from mediagoblin.tools.translate import (lazy_pass_to_ugettext as _,
+ pass_to_ugettext)
+
+class Response(wz_Response):
+ """Set default response mimetype to HTML, otherwise we get text/plain"""
+ default_mimetype = u'text/html'
+
+
+def render_to_response(request, template, context, status=200):
+ """Much like Django's shortcut.render()"""
+ return Response(
+ render_template(request, template, context),
+ status=status)
+
+
+def render_error(request, status=500, title=_('Oops!'),
+ err_msg=_('An error occured')):
+ """Render any error page with a given error code, title and text body
+
+ Title and description are passed through as-is to allow html. Make
+ sure no user input is contained therein for security reasons. The
+ description will be wrapped in <p></p> tags.
+ """
+ return Response(render_template(request, 'mediagoblin/error.html',
+ {'err_code': status, 'title': title, 'err_msg': err_msg}),
+ status=status)
+
+
+def render_403(request):
+ """Render a standard 403 page"""
+ _ = pass_to_ugettext
+ title = _('Operation not allowed')
+ err_msg = _("Sorry Dave, I can't let you do that!</p><p>You have tried "
+ " to perform a function that you are not allowed to. Have you "
+ "been trying to delete all user accounts again?")
+ return render_error(request, 403, title, err_msg)
+
+def render_404(request):
+ """Render a standard 404 page."""
+ _ = pass_to_ugettext
+ err_msg = _("There doesn't seem to be a page at this address. Sorry!</p>"
+ "<p>If you're sure the address is correct, maybe the page "
+ "you're looking for has been moved or deleted.")
+ return render_error(request, 404, err_msg=err_msg)
+
+
+def render_http_exception(request, exc, description):
+ """Return Response() given a werkzeug.HTTPException
+
+ :param exc: werkzeug.HTTPException or subclass thereof
+ :description: message describing the error."""
+ # If we were passed the HTTPException stock description on
+ # exceptions where we have localized ones, use those:
+ stock_desc = (description == exc.__class__.description)
+
+ if stock_desc and exc.code == 403:
+ return render_403(request)
+ elif stock_desc and exc.code == 404:
+ return render_404(request)
+
+ return render_error(request, title=exc.args[0],
+ err_msg=description,
+ status=exc.code)
+
+
+def redirect(request, *args, **kwargs):
+ """Redirects to an URL, using urlgen params or location string
+
+ :param querystring: querystring to be appended to the URL
+ :param location: If the location keyword is given, redirect to the URL
+ """
+ querystring = kwargs.pop('querystring', None)
+
+ # Redirect to URL if given by "location=..."
+ if 'location' in kwargs:
+ location = kwargs.pop('location')
+ else:
+ location = request.urlgen(*args, **kwargs)
+
+ if querystring:
+ location += querystring
+ return werkzeug.utils.redirect(location)
+
+
+def redirect_obj(request, obj):
+ """Redirect to the page for the given object.
+
+ Requires obj to have a .url_for_self method."""
+ return redirect(request, location=obj.url_for_self(request.urlgen))
diff --git a/mediagoblin/tools/routing.py b/mediagoblin/tools/routing.py
new file mode 100644
index 00000000..a15795fe
--- /dev/null
+++ b/mediagoblin/tools/routing.py
@@ -0,0 +1,67 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+
+import six
+from werkzeug.routing import Map, Rule
+from mediagoblin.tools.common import import_component
+
+
+_log = logging.getLogger(__name__)
+
+url_map = Map()
+
+
+class MGRoute(Rule):
+ def __init__(self, endpoint, url, controller):
+ Rule.__init__(self, url, endpoint=endpoint)
+ self.gmg_controller = controller
+
+ def empty(self):
+ new_rule = Rule.empty(self)
+ new_rule.gmg_controller = self.gmg_controller
+ return new_rule
+
+
+def endpoint_to_controller(rule):
+ endpoint = rule.endpoint
+ view_func = rule.gmg_controller
+
+ _log.debug('endpoint: {0} view_func: {1}'.format(endpoint, view_func))
+
+ # import the endpoint, or if it's already a callable, call that
+ if isinstance(view_func, six.string_types):
+ view_func = import_component(view_func)
+ rule.gmg_controller = view_func
+
+ return view_func
+
+
+def add_route(endpoint, url, controller):
+ """
+ Add a route to the url mapping
+ """
+ url_map.add(MGRoute(endpoint, url, controller))
+
+
+def mount(mountpoint, routes):
+ """
+ Mount a bunch of routes to this mountpoint
+ """
+ for endpoint, url, controller in routes:
+ url = "%s/%s" % (mountpoint.rstrip('/'), url.lstrip('/'))
+ add_route(endpoint, url, controller)
diff --git a/mediagoblin/tools/session.py b/mediagoblin/tools/session.py
new file mode 100644
index 00000000..fdc32523
--- /dev/null
+++ b/mediagoblin/tools/session.py
@@ -0,0 +1,68 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import itsdangerous
+import logging
+
+import crypto
+
+_log = logging.getLogger(__name__)
+
+class Session(dict):
+ def __init__(self, *args, **kwargs):
+ self.send_new_cookie = False
+ dict.__init__(self, *args, **kwargs)
+
+ def save(self):
+ self.send_new_cookie = True
+
+ def is_updated(self):
+ return self.send_new_cookie
+
+ def delete(self):
+ self.clear()
+ self.save()
+
+
+class SessionManager(object):
+ def __init__(self, cookie_name='MGSession', namespace=None):
+ if namespace is None:
+ namespace = cookie_name
+ self.signer = crypto.get_timed_signer_url(namespace)
+ self.cookie_name = cookie_name
+
+ def load_session_from_cookie(self, request):
+ cookie = request.cookies.get(self.cookie_name)
+ if not cookie:
+ return Session()
+ ### FIXME: Future cookie-blacklisting code
+ # m = BadCookie.query.filter_by(cookie = cookie)
+ # if m:
+ # _log.warn("Bad cookie received: %s", m.reason)
+ # raise BadRequest()
+ try:
+ return Session(self.signer.loads(cookie))
+ except itsdangerous.BadData:
+ return Session()
+
+ def save_session_to_cookie(self, session, request, response):
+ if not session.is_updated():
+ return
+ elif not session:
+ response.delete_cookie(self.cookie_name)
+ else:
+ response.set_cookie(self.cookie_name, self.signer.dumps(session),
+ httponly=True)
diff --git a/mediagoblin/tools/staticdirect.py b/mediagoblin/tools/staticdirect.py
new file mode 100644
index 00000000..ef8b20d0
--- /dev/null
+++ b/mediagoblin/tools/staticdirect.py
@@ -0,0 +1,101 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+####################################
+# Staticdirect infrastructure.
+# Borrowed largely from cc.engine
+# by Chris Webber & Creative Commons
+#
+# This needs documentation!
+####################################
+
+import logging
+
+_log = logging.getLogger(__name__)
+
+
+class StaticDirect(object):
+ """
+ Direct to a static resource.
+
+ This StaticDirect class can take a series of "domains" to
+ staticdirect to. In general, you should supply a None domain, as
+ that's the "default" domain.
+
+ Things work like this:
+ >>> staticdirect = StaticDirect(
+ ... {None: "/static/",
+ ... "theme": "http://example.org/themestatic/"})
+ >>> staticdirect("css/monkeys.css")
+ "/static/css/monkeys.css"
+ >>> staticdirect("images/lollerskate.png", "theme")
+ "http://example.org/themestatic/images/lollerskate.png"
+ """
+ def __init__(self, domains):
+ self.domains = dict(
+ [(key, value.rstrip('/'))
+ for key, value in domains.iteritems()])
+ self.cache = {}
+
+ def __call__(self, filepath, domain=None):
+ if domain in self.cache and filepath in self.cache[domain]:
+ return self.cache[domain][filepath]
+
+ static_direction = self.cache.setdefault(
+ domain, {})[filepath] = self.get(filepath, domain)
+ return static_direction
+
+ def get(self, filepath, domain=None):
+ return '%s/%s' % (
+ self.domains[domain], filepath.lstrip('/'))
+
+
+class PluginStatic(object):
+ """Pass this into the ``'static_setup'`` hook to register your
+ plugin's static directory.
+
+ This has two mandatory attributes that you must pass in on class
+ init:
+ - name: this name will be both used for lookup in "urlgen" for
+ your plugin's static resources and for the subdirectory that
+ it'll be "mounted" to for serving via your web browser. It
+ *MUST* be unique. If writing a plugin bundled with MediaGoblin
+ please use the pattern 'coreplugin__foo' where 'foo' is your
+ plugin name. All external plugins should use their modulename,
+ so if your plugin is 'mg_bettertags' you should also call this
+ name 'mg_bettertags'.
+ - file_path: the directory your plugin's static resources are
+ located in. It's recommended that you use
+ pkg_resources.resource_filename() for this.
+
+ An example of using this::
+
+ from pkg_resources import resource_filename
+ from mediagoblin.tools.staticdirect import PluginStatic
+
+ hooks = {
+ 'static_setup': lambda: PluginStatic(
+ 'mg_bettertags',
+ resource_filename('mg_bettertags', 'static'))
+ }
+
+ """
+ def __init__(self, name, file_path):
+ self.name = name
+ self.file_path = file_path
+
+ def __call__(self):
+ return self
diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py
new file mode 100644
index 00000000..3d651a6e
--- /dev/null
+++ b/mediagoblin/tools/template.py
@@ -0,0 +1,160 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import jinja2
+from jinja2.ext import Extension
+from jinja2.nodes import Include, Const
+
+from babel.localedata import exists
+from werkzeug.urls import url_quote_plus
+
+from mediagoblin import mg_globals
+from mediagoblin import messages
+from mediagoblin import _version
+from mediagoblin.tools import common
+from mediagoblin.tools.translate import set_thread_locale
+from mediagoblin.tools.pluginapi import get_hook_templates, hook_transform
+from mediagoblin.tools.timesince import timesince
+from mediagoblin.meddleware.csrf import render_csrf_form_token
+
+
+
+SETUP_JINJA_ENVS = {}
+
+
+def get_jinja_env(template_loader, locale):
+ """
+ Set up the Jinja environment,
+
+ (In the future we may have another system for providing theming;
+ for now this is good enough.)
+ """
+ set_thread_locale(locale)
+
+ # If we have a jinja environment set up with this locale, just
+ # return that one.
+ if locale in SETUP_JINJA_ENVS:
+ return SETUP_JINJA_ENVS[locale]
+
+ # jinja2.StrictUndefined will give exceptions on references
+ # to undefined/unknown variables in templates.
+ template_env = jinja2.Environment(
+ loader=template_loader, autoescape=True,
+ undefined=jinja2.StrictUndefined,
+ extensions=[
+ 'jinja2.ext.i18n', 'jinja2.ext.autoescape',
+ TemplateHookExtension])
+
+ template_env.install_gettext_callables(
+ mg_globals.thread_scope.translations.ugettext,
+ mg_globals.thread_scope.translations.ungettext)
+
+ # All templates will know how to ...
+ # ... fetch all waiting messages and remove them from the queue
+ # ... construct a grid of thumbnails or other media
+ # ... have access to the global and app config
+ template_env.globals['fetch_messages'] = messages.fetch_messages
+ template_env.globals['app_config'] = mg_globals.app_config
+ template_env.globals['global_config'] = mg_globals.global_config
+ template_env.globals['version'] = _version.__version__
+
+ template_env.filters['urlencode'] = url_quote_plus
+
+ # add human readable fuzzy date time
+ template_env.globals['timesince'] = timesince
+
+ # allow for hooking up plugin templates
+ template_env.globals['get_hook_templates'] = get_hook_templates
+
+ template_env.globals = hook_transform(
+ 'template_global_context', template_env.globals)
+
+ if exists(locale):
+ SETUP_JINJA_ENVS[locale] = template_env
+
+ return template_env
+
+
+# We'll store context information here when doing unit tests
+TEMPLATE_TEST_CONTEXT = {}
+
+
+def render_template(request, template_path, context):
+ """
+ Render a template with context.
+
+ Always inserts the request into the context, so you don't have to.
+ Also stores the context if we're doing unit tests. Helpful!
+ """
+ template = request.template_env.get_template(
+ template_path)
+ context['request'] = request
+ rendered_csrf_token = render_csrf_form_token(request)
+ if rendered_csrf_token is not None:
+ context['csrf_token'] = render_csrf_form_token(request)
+
+ # allow plugins to do things to the context
+ if request.controller_name:
+ context = hook_transform(
+ (request.controller_name, template_path),
+ context)
+
+ # More evil: allow plugins to possibly do something to the context
+ # in every request ever with access to the request and other
+ # variables. Note: this is slower than using
+ # template_global_context
+ context = hook_transform(
+ 'template_context_prerender', context)
+
+ rendered = template.render(context)
+
+ if common.TESTS_ENABLED:
+ TEMPLATE_TEST_CONTEXT[template_path] = context
+
+ return rendered
+
+
+def clear_test_template_context():
+ global TEMPLATE_TEST_CONTEXT
+ TEMPLATE_TEST_CONTEXT = {}
+
+
+class TemplateHookExtension(Extension):
+ """
+ Easily loop through a bunch of templates from a template hook.
+
+ Use:
+ {% template_hook("comment_extras") %}
+
+ ... will include all templates hooked into the comment_extras section.
+ """
+
+ tags = set(["template_hook"])
+
+ def parse(self, parser):
+ includes = []
+ expr = parser.parse_expression()
+ lineno = expr.lineno
+ hook_name = expr.args[0].value
+
+ for template_name in get_hook_templates(hook_name):
+ includes.append(
+ parser.parse_import_context(
+ Include(Const(template_name), True, False, lineno=lineno),
+ True))
+
+ return includes
diff --git a/mediagoblin/tools/testing.py b/mediagoblin/tools/testing.py
new file mode 100644
index 00000000..7f2bcbfb
--- /dev/null
+++ b/mediagoblin/tools/testing.py
@@ -0,0 +1,45 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools import common
+from mediagoblin.tools.template import clear_test_template_context
+from mediagoblin.tools.mail import EMAIL_TEST_INBOX, EMAIL_TEST_MBOX_INBOX
+
+def _activate_testing():
+ """
+ Call this to activate testing in util.py
+ """
+
+ common.TESTS_ENABLED = True
+
+def clear_test_buckets():
+ """
+ We store some things for testing purposes that should be cleared
+ when we want a "clean slate" of information for our next round of
+ tests. Call this function to wipe all that stuff clean.
+
+ Also wipes out some other things we might redefine during testing,
+ like the jinja envs.
+ """
+ global SETUP_JINJA_ENVS
+ SETUP_JINJA_ENVS = {}
+
+ global EMAIL_TEST_INBOX
+ global EMAIL_TEST_MBOX_INBOX
+ EMAIL_TEST_INBOX = []
+ EMAIL_TEST_MBOX_INBOX = []
+
+ clear_test_template_context()
diff --git a/mediagoblin/tools/text.py b/mediagoblin/tools/text.py
new file mode 100644
index 00000000..96df49d2
--- /dev/null
+++ b/mediagoblin/tools/text.py
@@ -0,0 +1,124 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import wtforms
+import markdown
+from lxml.html.clean import Cleaner
+
+from mediagoblin import mg_globals
+from mediagoblin.tools import url
+
+
+# A super strict version of the lxml.html cleaner class
+HTML_CLEANER = Cleaner(
+ scripts=True,
+ javascript=True,
+ comments=True,
+ style=True,
+ links=True,
+ page_structure=True,
+ processing_instructions=True,
+ embedded=True,
+ frames=True,
+ forms=True,
+ annoying_tags=True,
+ allow_tags=[
+ 'div', 'b', 'i', 'em', 'strong', 'p', 'ul', 'ol', 'li', 'a', 'br',
+ 'pre', 'code'],
+ remove_unknown_tags=False, # can't be used with allow_tags
+ safe_attrs_only=True,
+ add_nofollow=True, # for now
+ host_whitelist=(),
+ whitelist_tags=set([]))
+
+
+def clean_html(html):
+ # clean_html barfs on an empty string
+ if not html:
+ return u''
+
+ return HTML_CLEANER.clean_html(html)
+
+
+def convert_to_tag_list_of_dicts(tag_string):
+ """
+ Filter input from incoming string containing user tags,
+
+ Strips trailing, leading, and internal whitespace, and also converts
+ the "tags" text into an array of tags
+ """
+ taglist = []
+ if tag_string:
+
+ # Strip out internal, trailing, and leading whitespace
+ stripped_tag_string = u' '.join(tag_string.strip().split())
+
+ # Split the tag string into a list of tags
+ for tag in stripped_tag_string.split(','):
+ tag = tag.strip()
+ # Ignore empty or duplicate tags
+ if tag and tag not in [t['name'] for t in taglist]:
+ taglist.append({'name': tag,
+ 'slug': url.slugify(tag)})
+ return taglist
+
+
+def media_tags_as_string(media_entry_tags):
+ """
+ Generate a string from a media item's tags, stored as a list of dicts
+
+ This is the opposite of convert_to_tag_list_of_dicts
+ """
+ tags_string = ''
+ if media_entry_tags:
+ tags_string = u', '.join([tag['name'] for tag in media_entry_tags])
+ return tags_string
+
+
+TOO_LONG_TAG_WARNING = \
+ u'Tags must be shorter than %s characters. Tags that are too long: %s'
+
+
+def tag_length_validator(form, field):
+ """
+ Make sure tags do not exceed the maximum tag length.
+ """
+ tags = convert_to_tag_list_of_dicts(field.data)
+ too_long_tags = [
+ tag['name'] for tag in tags
+ if len(tag['name']) > mg_globals.app_config['tags_max_length']]
+
+ if too_long_tags:
+ raise wtforms.ValidationError(
+ TOO_LONG_TAG_WARNING % (mg_globals.app_config['tags_max_length'],
+ ', '.join(too_long_tags)))
+
+
+# Don't use the safe mode, because lxml.html.clean is better and we are using
+# it anyway
+UNSAFE_MARKDOWN_INSTANCE = markdown.Markdown()
+
+
+def cleaned_markdown_conversion(text):
+ """
+ Take a block of text, run it through MarkDown, and clean its HTML.
+ """
+ # Markdown will do nothing with and clean_html can do nothing with
+ # an empty string :)
+ if not text:
+ return u''
+
+ return clean_html(UNSAFE_MARKDOWN_INSTANCE.convert(text))
diff --git a/mediagoblin/tools/theme.py b/mediagoblin/tools/theme.py
new file mode 100644
index 00000000..97b041a6
--- /dev/null
+++ b/mediagoblin/tools/theme.py
@@ -0,0 +1,89 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+"""
+
+
+import pkg_resources
+import os
+
+from configobj import ConfigObj
+
+
+BUILTIN_THEME_DIR = pkg_resources.resource_filename('mediagoblin', 'themes')
+
+
+def themedata_for_theme_dir(name, theme_dir):
+ """
+ Given a theme directory, extract important theme information.
+ """
+ # open config
+ config = ConfigObj(os.path.join(theme_dir, 'theme.ini')).get('theme', {})
+
+ templates_dir = os.path.join(theme_dir, 'templates')
+ if not os.path.exists(templates_dir):
+ templates_dir = None
+
+ assets_dir = os.path.join(theme_dir, 'assets')
+ if not os.path.exists(assets_dir):
+ assets_dir = None
+
+ themedata = {
+ 'name': config.get('name', name),
+ 'description': config.get('description'),
+ 'licensing': config.get('licensing'),
+ 'dir': theme_dir,
+ 'templates_dir': templates_dir,
+ 'assets_dir': assets_dir,
+ 'config': config}
+
+ return themedata
+
+
+def register_themes(app_config, builtin_dir=BUILTIN_THEME_DIR):
+ """
+ Register all themes relevant to this application.
+ """
+ registry = {}
+
+ def _install_themes_in_dir(directory):
+ for themedir in os.listdir(directory):
+ abs_themedir = os.path.join(directory, themedir)
+ if not os.path.isdir(abs_themedir):
+ continue
+
+ themedata = themedata_for_theme_dir(themedir, abs_themedir)
+ registry[themedir] = themedata
+
+ # Built-in themes
+ if os.path.exists(builtin_dir):
+ _install_themes_in_dir(builtin_dir)
+
+ # Installed themes
+ theme_install_dir = app_config.get('theme_install_dir')
+ if theme_install_dir and os.path.exists(theme_install_dir):
+ _install_themes_in_dir(theme_install_dir)
+
+ current_theme_name = app_config.get('theme')
+ if current_theme_name \
+ and registry.has_key(current_theme_name):
+ current_theme = registry[current_theme_name]
+ else:
+ current_theme = None
+
+ return registry, current_theme
+
diff --git a/mediagoblin/tools/timesince.py b/mediagoblin/tools/timesince.py
new file mode 100644
index 00000000..b761c1be
--- /dev/null
+++ b/mediagoblin/tools/timesince.py
@@ -0,0 +1,95 @@
+# Copyright (c) Django Software Foundation and individual contributors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of Django nor the names of its contributors may be used
+# to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import unicode_literals
+
+import datetime
+import pytz
+
+from mediagoblin.tools.translate import pass_to_ugettext, lazy_pass_to_ungettext as _
+
+"""UTC time zone as a tzinfo instance."""
+utc = pytz.utc if pytz else UTC()
+
+def is_aware(value):
+ """
+ Determines if a given datetime.datetime is aware.
+
+ The logic is described in Python's docs:
+ http://docs.python.org/library/datetime.html#datetime.tzinfo
+ """
+ return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None
+
+def timesince(d, now=None, reversed=False):
+ """
+ Takes two datetime objects and returns the time between d and now
+ as a nicely formatted string, e.g. "10 minutes". If d occurs after now,
+ then "0 minutes" is returned.
+
+ Units used are years, months, weeks, days, hours, and minutes.
+ Seconds and microseconds are ignored. Up to two adjacent units will be
+ displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are
+ possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not.
+
+ Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
+ """
+ chunks = (
+ (60 * 60 * 24 * 365, lambda n: _('year', 'years', n)),
+ (60 * 60 * 24 * 30, lambda n: _('month', 'months', n)),
+ (60 * 60 * 24 * 7, lambda n : _('week', 'weeks', n)),
+ (60 * 60 * 24, lambda n : _('day', 'days', n)),
+ (60 * 60, lambda n: _('hour', 'hours', n)),
+ (60, lambda n: _('minute', 'minutes', n))
+ )
+ # Convert datetime.date to datetime.datetime for comparison.
+ if not isinstance(d, datetime.datetime):
+ d = datetime.datetime(d.year, d.month, d.day)
+ if now and not isinstance(now, datetime.datetime):
+ now = datetime.datetime(now.year, now.month, now.day)
+
+ if not now:
+ now = datetime.datetime.now(utc if is_aware(d) else None)
+
+ delta = (d - now) if reversed else (now - d)
+ # ignore microseconds
+ since = delta.days * 24 * 60 * 60 + delta.seconds
+ if since <= 0:
+ # d is in the future compared to now, stop processing.
+ return '0 ' + pass_to_ugettext('minutes')
+ for i, (seconds, name) in enumerate(chunks):
+ count = since // seconds
+ if count != 0:
+ break
+ s = pass_to_ugettext('%(number)d %(type)s') % {'number': count, 'type': name(count)}
+ if i + 1 < len(chunks):
+ # Now get the second item
+ seconds2, name2 = chunks[i + 1]
+ count2 = (since - (seconds * count)) // seconds2
+ if count2 != 0:
+ s += pass_to_ugettext(', %(number)d %(type)s') % {'number': count2, 'type': name2(count2)}
+ return s
diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py
new file mode 100644
index 00000000..b20e57d1
--- /dev/null
+++ b/mediagoblin/tools/translate.py
@@ -0,0 +1,211 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import gettext
+import pkg_resources
+
+
+from babel import localedata
+from babel.support import LazyProxy
+
+from mediagoblin import mg_globals
+
+###################
+# Translation tools
+###################
+
+AVAILABLE_LOCALES = None
+TRANSLATIONS_PATH = pkg_resources.resource_filename(
+ 'mediagoblin', 'i18n')
+
+
+def set_available_locales():
+ """Set available locales for which we have translations"""
+ global AVAILABLE_LOCALES
+ locales=['en', 'en_US'] # these are available without translations
+ for locale in localedata.list():
+ if gettext.find('mediagoblin', TRANSLATIONS_PATH, [locale]):
+ locales.append(locale)
+ AVAILABLE_LOCALES = locales
+
+
+class ReallyLazyProxy(LazyProxy):
+ """
+ Like LazyProxy, except that it doesn't cache the value ;)
+ """
+ @property
+ def value(self):
+ return self._func(*self._args, **self._kwargs)
+
+ def __repr__(self):
+ return "<%s for %s(%r, %r)>" % (
+ self.__class__.__name__,
+ self._func,
+ self._args,
+ self._kwargs)
+
+
+def locale_to_lower_upper(locale):
+ """
+ Take a locale, regardless of style, and format it like "en_US"
+ """
+ if '-' in locale:
+ lang, country = locale.split('-', 1)
+ return '%s_%s' % (lang.lower(), country.upper())
+ elif '_' in locale:
+ lang, country = locale.split('_', 1)
+ return '%s_%s' % (lang.lower(), country.upper())
+ else:
+ return locale.lower()
+
+
+def locale_to_lower_lower(locale):
+ """
+ Take a locale, regardless of style, and format it like "en_us"
+ """
+ if '_' in locale:
+ lang, country = locale.split('_', 1)
+ return '%s-%s' % (lang.lower(), country.lower())
+ else:
+ return locale.lower()
+
+
+def get_locale_from_request(request):
+ """
+ Return most appropriate language based on prefs/request request
+ """
+ request_args = (request.args, request.form)[request.method=='POST']
+
+ if 'lang' in request_args:
+ # User explicitely demanded a language, normalize lower_uppercase
+ target_lang = locale_to_lower_upper(request_args['lang'])
+
+ elif 'target_lang' in request.session:
+ # TODO: Uh, ohh, this is never ever set anywhere?
+ target_lang = request.session['target_lang']
+ else:
+ # Pull the most acceptable language based on browser preferences
+ # This returns one of AVAILABLE_LOCALES which is aready case-normalized.
+ # Note: in our tests request.accept_languages is None, so we need
+ # to explicitely fallback to en here.
+ target_lang = request.accept_languages.best_match(AVAILABLE_LOCALES) \
+ or "en_US"
+
+ return target_lang
+
+SETUP_GETTEXTS = {}
+
+def get_gettext_translation(locale):
+ """
+ Return the gettext instance based on this locale
+ """
+ # Later on when we have plugins we may want to enable the
+ # multi-translations system they have so we can handle plugin
+ # translations too
+
+ # TODO: fallback nicely on translations from pt_PT to pt if not
+ # available, etc.
+ if locale in SETUP_GETTEXTS:
+ this_gettext = SETUP_GETTEXTS[locale]
+ else:
+ this_gettext = gettext.translation(
+ 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True)
+ if localedata.exists(locale):
+ SETUP_GETTEXTS[locale] = this_gettext
+ return this_gettext
+
+
+def set_thread_locale(locale):
+ """Set the current translation for this thread"""
+ mg_globals.thread_scope.translations = get_gettext_translation(locale)
+
+
+def pass_to_ugettext(*args, **kwargs):
+ """
+ Pass a translation on to the appropriate ugettext method.
+
+ The reason we can't have a global ugettext method is because
+ mg_globals gets swapped out by the application per-request.
+ """
+ return mg_globals.thread_scope.translations.ugettext(
+ *args, **kwargs)
+
+def pass_to_ungettext(*args, **kwargs):
+ """
+ Pass a translation on to the appropriate ungettext method.
+
+ The reason we can't have a global ugettext method is because
+ mg_globals gets swapped out by the application per-request.
+ """
+ return mg_globals.thread_scope.translations.ungettext(
+ *args, **kwargs)
+
+
+def lazy_pass_to_ugettext(*args, **kwargs):
+ """
+ Lazily pass to ugettext.
+
+ This is useful if you have to define a translation on a module
+ level but you need it to not translate until the time that it's
+ used as a string. For example, in:
+ def func(self, message=_('Hello boys and girls'))
+
+ you would want to use the lazy version for _.
+ """
+ return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs)
+
+
+def pass_to_ngettext(*args, **kwargs):
+ """
+ Pass a translation on to the appropriate ngettext method.
+
+ The reason we can't have a global ngettext method is because
+ mg_globals gets swapped out by the application per-request.
+ """
+ return mg_globals.thread_scope.translations.ngettext(
+ *args, **kwargs)
+
+
+def lazy_pass_to_ngettext(*args, **kwargs):
+ """
+ Lazily pass to ngettext.
+
+ This is useful if you have to define a translation on a module
+ level but you need it to not translate until the time that it's
+ used as a string.
+ """
+ return ReallyLazyProxy(pass_to_ngettext, *args, **kwargs)
+
+def lazy_pass_to_ungettext(*args, **kwargs):
+ """
+ Lazily pass to ungettext.
+
+ This is useful if you have to define a translation on a module
+ level but you need it to not translate until the time that it's
+ used as a string.
+ """
+ return ReallyLazyProxy(pass_to_ungettext, *args, **kwargs)
+
+
+def fake_ugettext_passthrough(string):
+ """
+ Fake a ugettext call for extraction's sake ;)
+
+ In wtforms there's a separate way to define a method to translate
+ things... so we just need to mark up the text so that it can be
+ extracted, not so that it's actually run through gettext.
+ """
+ return string
diff --git a/mediagoblin/tools/url.py b/mediagoblin/tools/url.py
new file mode 100644
index 00000000..d9179f9e
--- /dev/null
+++ b/mediagoblin/tools/url.py
@@ -0,0 +1,44 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+# This import *is* used; see word.encode('tranlit/long') below.
+from unicodedata import normalize
+
+try:
+ import translitcodec
+ USING_TRANSLITCODEC = True
+except ImportError:
+ USING_TRANSLITCODEC = False
+
+
+_punct_re = re.compile(r'[\t !"#:$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
+
+
+def slugify(text, delim=u'-'):
+ """
+ Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/
+ """
+ result = []
+ for word in _punct_re.split(text.lower()):
+ if USING_TRANSLITCODEC:
+ word = word.encode('translit/long')
+ else:
+ word = normalize('NFKD', word).encode('ascii', 'ignore')
+
+ if word:
+ result.append(word)
+ return unicode(delim.join(result))
diff --git a/mediagoblin/tools/workbench.py b/mediagoblin/tools/workbench.py
new file mode 100644
index 00000000..0bd4096b
--- /dev/null
+++ b/mediagoblin/tools/workbench.py
@@ -0,0 +1,164 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import shutil
+import tempfile
+
+
+# Actual workbench stuff
+# ----------------------
+
+class Workbench(object):
+ """
+ Represent the directory for the workbench
+
+ WARNING: DO NOT create Workbench objects on your own,
+ let the WorkbenchManager do that for you!
+ """
+ def __init__(self, dir):
+ """
+ WARNING: DO NOT create Workbench objects on your own,
+ let the WorkbenchManager do that for you!
+ """
+ self.dir = dir
+
+ def __unicode__(self):
+ return unicode(self.dir)
+
+ def __str__(self):
+ return str(self.dir)
+
+ def __repr__(self):
+ try:
+ return str(self)
+ except AttributeError:
+ return 'None'
+
+ def joinpath(self, *args):
+ return os.path.join(self.dir, *args)
+
+ def localized_file(self, storage, filepath,
+ filename_if_copying=None,
+ keep_extension_if_copying=True):
+ """
+ Possibly localize the file from this storage system (for read-only
+ purposes, modifications should be written to a new file.).
+
+ If the file is already local, just return the absolute filename of that
+ local file. Otherwise, copy the file locally to the workbench, and
+ return the absolute path of the new file.
+
+ If it is copying locally, we might want to require a filename like
+ "source.jpg" to ensure that we won't conflict with other filenames in
+ our workbench... if that's the case, make sure filename_if_copying is
+ set to something like 'source.jpg'. Relatedly, if you set
+ keep_extension_if_copying, you don't have to set an extension on
+ filename_if_copying yourself, it'll be set for you (assuming such an
+ extension can be extacted from the filename in the filepath).
+
+ Returns:
+ localized_filename
+
+ Examples:
+ >>> wb_manager.localized_file(
+ ... '/our/workbench/subdir', local_storage,
+ ... ['path', 'to', 'foobar.jpg'])
+ u'/local/storage/path/to/foobar.jpg'
+
+ >>> wb_manager.localized_file(
+ ... '/our/workbench/subdir', remote_storage,
+ ... ['path', 'to', 'foobar.jpg'])
+ '/our/workbench/subdir/foobar.jpg'
+
+ >>> wb_manager.localized_file(
+ ... '/our/workbench/subdir', remote_storage,
+ ... ['path', 'to', 'foobar.jpg'], 'source.jpeg', False)
+ '/our/workbench/subdir/foobar.jpeg'
+
+ >>> wb_manager.localized_file(
+ ... '/our/workbench/subdir', remote_storage,
+ ... ['path', 'to', 'foobar.jpg'], 'source', True)
+ '/our/workbench/subdir/foobar.jpg'
+ """
+ if storage.local_storage:
+ return storage.get_local_path(filepath)
+ else:
+ if filename_if_copying is None:
+ dest_filename = filepath[-1]
+ else:
+ orig_filename, orig_ext = os.path.splitext(filepath[-1])
+ if keep_extension_if_copying and orig_ext:
+ dest_filename = filename_if_copying + orig_ext
+ else:
+ dest_filename = filename_if_copying
+
+ full_dest_filename = os.path.join(
+ self.dir, dest_filename)
+
+ # copy it over
+ storage.copy_locally(
+ filepath, full_dest_filename)
+
+ return full_dest_filename
+
+ def destroy(self):
+ """
+ Destroy this workbench! Deletes the directory and all its contents!
+
+ WARNING: Does no checks for a sane value in self.dir!
+ """
+ # just in case
+ workbench = os.path.abspath(self.dir)
+ shutil.rmtree(workbench)
+ del self.dir
+
+ def __enter__(self):
+ """Make Workbench a context manager so we can use `with Workbench() as bench:`"""
+ return self
+
+ def __exit__(self, *args):
+ """Clean up context manager, aka ourselves, deleting the workbench"""
+ self.destroy()
+
+
+class WorkbenchManager(object):
+ """
+ A system for generating and destroying workbenches.
+
+ Workbenches are actually just subdirectories of a (local) temporary
+ storage space for during the processing stage. The preferred way to
+ create them is to use:
+
+ with workbenchmger.create() as workbench:
+ do stuff...
+
+ This will automatically clean up all temporary directories even in
+ case of an exceptions. Also check the
+ @mediagoblin.decorators.get_workbench decorator for a convenient
+ wrapper.
+ """
+
+ def __init__(self, base_workbench_dir):
+ self.base_workbench_dir = os.path.abspath(base_workbench_dir)
+ if not os.path.exists(self.base_workbench_dir):
+ os.makedirs(self.base_workbench_dir)
+
+ def create(self):
+ """
+ Create and return the path to a new workbench (directory).
+ """
+ return Workbench(tempfile.mkdtemp(dir=self.base_workbench_dir))
diff --git a/mediagoblin/user_pages/__init__.py b/mediagoblin/user_pages/__init__.py
new file mode 100644
index 00000000..621845ba
--- /dev/null
+++ b/mediagoblin/user_pages/__init__.py
@@ -0,0 +1,15 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py
new file mode 100644
index 00000000..9a193680
--- /dev/null
+++ b/mediagoblin/user_pages/forms.py
@@ -0,0 +1,51 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import wtforms
+from wtforms.ext.sqlalchemy.fields import QuerySelectField
+from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
+
+class MediaCommentForm(wtforms.Form):
+ comment_content = wtforms.TextAreaField(
+ _('Comment'),
+ [wtforms.validators.Required()],
+ description=_(u'You can use '
+ u'<a href="http://daringfireball.net/projects/markdown/basics">'
+ u'Markdown</a> for formatting.'))
+
+class ConfirmDeleteForm(wtforms.Form):
+ confirm = wtforms.BooleanField(
+ _('I am sure I want to delete this'))
+
+class ConfirmCollectionItemRemoveForm(wtforms.Form):
+ confirm = wtforms.BooleanField(
+ _('I am sure I want to remove this item from the collection'))
+
+class MediaCollectForm(wtforms.Form):
+ collection = QuerySelectField(
+ _('Collection'),
+ allow_blank=True, blank_text=_('-- Select --'), get_label='title',)
+ note = wtforms.TextAreaField(
+ _('Include a note'),
+ [wtforms.validators.Optional()],)
+ collection_title = wtforms.TextField(
+ _('Title'),
+ [wtforms.validators.Length(min=0, max=500)])
+ collection_description = wtforms.TextAreaField(
+ _('Description of this collection'),
+ description=_("""You can use
+ <a href="http://daringfireball.net/projects/markdown/basics">
+ Markdown</a> for formatting."""))
diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py
new file mode 100644
index 00000000..2f47e4b1
--- /dev/null
+++ b/mediagoblin/user_pages/lib.py
@@ -0,0 +1,77 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.mail import send_email
+from mediagoblin.tools.template import render_template
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin import mg_globals
+from mediagoblin.db.base import Session
+from mediagoblin.db.models import CollectionItem
+
+
+def send_comment_email(user, comment, media, request):
+ """
+ Sends comment email to user when a comment is made on their media.
+
+ Args:
+ - user: the user object to whom the email is sent
+ - comment: the comment object referencing user's media
+ - media: the media object the comment is about
+ - request: the request
+ """
+
+ comment_url = request.urlgen(
+ 'mediagoblin.user_pages.media_home.view_comment',
+ comment=comment.id,
+ user=media.get_uploader.username,
+ media=media.slug_or_id,
+ qualified=True) + '#comment'
+
+ comment_author = comment.get_author.username
+
+ rendered_email = render_template(
+ request, 'mediagoblin/user_pages/comment_email.txt',
+ {'username': user.username,
+ 'comment_author': comment_author,
+ 'comment_content': comment.content,
+ 'comment_url': comment_url})
+
+ send_email(
+ mg_globals.app_config['email_sender_address'],
+ [user.email],
+ '{instance_title} - {comment_author} '.format(
+ comment_author=comment_author,
+ instance_title=mg_globals.app_config['html_title']) \
+ + _('commented on your post'),
+ rendered_email)
+
+
+def add_media_to_collection(collection, media, note=None, commit=True):
+ collection_item = CollectionItem()
+ collection_item.collection = collection.id
+ collection_item.media_entry = media.id
+ if note:
+ collection_item.note = note
+ Session.add(collection_item)
+
+ collection.items = collection.items + 1
+ Session.add(collection)
+
+ media.collected = media.collected + 1
+ Session.add(media)
+
+ if commit:
+ Session.commit()
diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py
new file mode 100644
index 00000000..9cb665b5
--- /dev/null
+++ b/mediagoblin/user_pages/routing.py
@@ -0,0 +1,91 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.routing import add_route
+
+add_route('mediagoblin.user_pages.user_home',
+ '/u/<string:user>/', 'mediagoblin.user_pages.views:user_home')
+
+add_route('mediagoblin.user_pages.media_home',
+ '/u/<string:user>/m/<string:media>/',
+ 'mediagoblin.user_pages.views:media_home')
+
+add_route('mediagoblin.user_pages.media_confirm_delete',
+ '/u/<string:user>/m/<int:media_id>/confirm-delete/',
+ 'mediagoblin.user_pages.views:media_confirm_delete')
+
+# Submission handling of new comments. TODO: only allow for POST methods
+add_route('mediagoblin.user_pages.media_post_comment',
+ '/u/<string:user>/m/<int:media_id>/comment/add/',
+ 'mediagoblin.user_pages.views:media_post_comment')
+
+add_route('mediagoblin.user_pages.user_gallery',
+ '/u/<string:user>/gallery/',
+ 'mediagoblin.user_pages.views:user_gallery')
+
+add_route('mediagoblin.user_pages.media_home.view_comment',
+ '/u/<string:user>/m/<string:media>/c/<int:comment>/',
+ 'mediagoblin.user_pages.views:media_home')
+
+# User's tags gallery
+add_route('mediagoblin.user_pages.user_tag_gallery',
+ '/u/<string:user>/tag/<string:tag>/',
+ 'mediagoblin.user_pages.views:user_gallery')
+
+add_route('mediagoblin.user_pages.atom_feed',
+ '/u/<string:user>/atom/',
+ 'mediagoblin.user_pages.views:atom_feed')
+
+add_route('mediagoblin.user_pages.media_collect',
+ '/u/<string:user>/m/<int:media_id>/collect/',
+ 'mediagoblin.user_pages.views:media_collect')
+
+add_route('mediagoblin.user_pages.collection_list',
+ '/u/<string:user>/collections/',
+ 'mediagoblin.user_pages.views:collection_list')
+
+add_route('mediagoblin.user_pages.user_collection',
+ '/u/<string:user>/collection/<string:collection>/',
+ 'mediagoblin.user_pages.views:user_collection')
+
+add_route('mediagoblin.edit.edit_collection',
+ '/u/<string:user>/c/<string:collection>/edit/',
+ 'mediagoblin.edit.views:edit_collection')
+
+add_route('mediagoblin.user_pages.collection_confirm_delete',
+ '/u/<string:user>/c/<string:collection>/confirm-delete/',
+ 'mediagoblin.user_pages.views:collection_confirm_delete')
+
+add_route('mediagoblin.user_pages.collection_item_confirm_remove',
+ '/u/<string:user>/collection/<string:collection>/<string:collection_item>/confirm_remove/',
+ 'mediagoblin.user_pages.views:collection_item_confirm_remove')
+
+add_route('mediagoblin.user_pages.collection_atom_feed',
+ '/u/<string:user>/collection/<string:collection>/atom/',
+ 'mediagoblin.user_pages.views:collection_atom_feed')
+
+add_route('mediagoblin.user_pages.processing_panel',
+ '/u/<string:user>/panel/',
+ 'mediagoblin.user_pages.views:processing_panel')
+
+# Stray edit routes
+add_route('mediagoblin.edit.edit_media',
+ '/u/<string:user>/m/<int:media_id>/edit/',
+ 'mediagoblin.edit.views:edit_media')
+
+add_route('mediagoblin.edit.attachments',
+ '/u/<string:user>/m/<int:media_id>/attachments/',
+ 'mediagoblin.edit.views:edit_attachments')
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
new file mode 100644
index 00000000..738cc054
--- /dev/null
+++ b/mediagoblin/user_pages/views.py
@@ -0,0 +1,618 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import datetime
+
+from mediagoblin import messages, mg_globals
+from mediagoblin.db.models import (MediaEntry, MediaTag, Collection,
+ CollectionItem, User)
+from mediagoblin.tools.response import render_to_response, render_404, \
+ redirect, redirect_obj
+from mediagoblin.tools.translate import pass_to_ugettext as _
+from mediagoblin.tools.pagination import Pagination
+from mediagoblin.user_pages import forms as user_forms
+from mediagoblin.user_pages.lib import (send_comment_email,
+ add_media_to_collection)
+
+from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
+ get_media_entry_by_id,
+ require_active_login, user_may_delete_media, user_may_alter_collection,
+ get_user_collection, get_user_collection_item, active_user_from_url)
+
+from werkzeug.contrib.atom import AtomFeed
+
+
+_log = logging.getLogger(__name__)
+_log.setLevel(logging.DEBUG)
+
+
+@uses_pagination
+def user_home(request, page):
+ """'Homepage' of a User()"""
+ # TODO: decide if we only want homepages for active users, we can
+ # then use the @get_active_user decorator and also simplify the
+ # template html.
+ user = User.query.filter_by(username=request.matchdict['user']).first()
+ if not user:
+ return render_404(request)
+ elif user.status != u'active':
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/user.html',
+ {'user': user})
+
+ cursor = MediaEntry.query.\
+ filter_by(uploader = user.id,
+ state = u'processed').order_by(MediaEntry.created.desc())
+
+ pagination = Pagination(page, cursor)
+ media_entries = pagination()
+
+ #if no data is available, return NotFound
+ if media_entries == None:
+ return render_404(request)
+
+ user_gallery_url = request.urlgen(
+ 'mediagoblin.user_pages.user_gallery',
+ user=user.username)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/user.html',
+ {'user': user,
+ 'user_gallery_url': user_gallery_url,
+ 'media_entries': media_entries,
+ 'pagination': pagination})
+
+
+@active_user_from_url
+@uses_pagination
+def user_gallery(request, page, url_user=None):
+ """'Gallery' of a User()"""
+ tag = request.matchdict.get('tag', None)
+ cursor = MediaEntry.query.filter_by(
+ uploader=url_user.id,
+ state=u'processed').order_by(MediaEntry.created.desc())
+
+ # Filter potentially by tag too:
+ if tag:
+ cursor = cursor.filter(
+ MediaEntry.tags_helper.any(
+ MediaTag.slug == request.matchdict['tag']))
+
+ # Paginate gallery
+ pagination = Pagination(page, cursor)
+ media_entries = pagination()
+
+ #if no data is available, return NotFound
+ # TODO: Should we really also return 404 for empty galleries?
+ if media_entries == None:
+ return render_404(request)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/gallery.html',
+ {'user': url_user, 'tag': tag,
+ 'media_entries': media_entries,
+ 'pagination': pagination})
+
+MEDIA_COMMENTS_PER_PAGE = 50
+
+
+@get_user_media_entry
+@uses_pagination
+def media_home(request, media, page, **kwargs):
+ """
+ 'Homepage' of a MediaEntry()
+ """
+ comment_id = request.matchdict.get('comment', None)
+ if comment_id:
+ pagination = Pagination(
+ page, media.get_comments(
+ mg_globals.app_config['comments_ascending']),
+ MEDIA_COMMENTS_PER_PAGE,
+ comment_id)
+ else:
+ pagination = Pagination(
+ page, media.get_comments(
+ mg_globals.app_config['comments_ascending']),
+ MEDIA_COMMENTS_PER_PAGE)
+
+ comments = pagination()
+
+ comment_form = user_forms.MediaCommentForm(request.form)
+
+ media_template_name = media.media_manager['display_template']
+
+ return render_to_response(
+ request,
+ media_template_name,
+ {'media': media,
+ 'comments': comments,
+ 'pagination': pagination,
+ 'comment_form': comment_form,
+ 'app_config': mg_globals.app_config})
+
+
+@get_media_entry_by_id
+@require_active_login
+def media_post_comment(request, media):
+ """
+ recieves POST from a MediaEntry() comment form, saves the comment.
+ """
+ assert request.method == 'POST'
+
+ comment = request.db.MediaComment()
+ comment.media_entry = media.id
+ comment.author = request.user.id
+ comment.content = unicode(request.form['comment_content'])
+
+ # Show error message if commenting is disabled.
+ if not mg_globals.app_config['allow_comments']:
+ messages.add_message(
+ request,
+ messages.ERROR,
+ _("Sorry, comments are disabled."))
+ elif not comment.content.strip():
+ messages.add_message(
+ request,
+ messages.ERROR,
+ _("Oops, your comment was empty."))
+ else:
+ comment.save()
+
+ messages.add_message(
+ request, messages.SUCCESS,
+ _('Your comment has been posted!'))
+
+ media_uploader = media.get_uploader
+ #don't send email if you comment on your own post
+ if (comment.author != media_uploader and
+ media_uploader.wants_comment_notification):
+ send_comment_email(media_uploader, comment, media, request)
+
+ return redirect_obj(request, media)
+
+
+@get_media_entry_by_id
+@require_active_login
+def media_collect(request, media):
+ """Add media to collection submission"""
+
+ form = user_forms.MediaCollectForm(request.form)
+ # A user's own collections:
+ form.collection.query = Collection.query.filter_by(
+ creator = request.user.id).order_by(Collection.title)
+
+ if request.method != 'POST' or not form.validate():
+ # No POST submission, or invalid form
+ if not form.validate():
+ messages.add_message(request, messages.ERROR,
+ _('Please check your entries and try again.'))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/media_collect.html',
+ {'media': media,
+ 'form': form})
+
+ # If we are here, method=POST and the form is valid, submit things.
+ # If the user is adding a new collection, use that:
+ if form.collection_title.data:
+ # Make sure this user isn't duplicating an existing collection
+ existing_collection = Collection.query.filter_by(
+ creator=request.user.id,
+ title=form.collection_title.data).first()
+ if existing_collection:
+ messages.add_message(request, messages.ERROR,
+ _('You already have a collection called "%s"!')
+ % existing_collection.title)
+ return redirect(request, "mediagoblin.user_pages.media_home",
+ user=media.get_uploader.username,
+ media=media.slug_or_id)
+
+ collection = Collection()
+ collection.title = form.collection_title.data
+ collection.description = form.collection_description.data
+ collection.creator = request.user.id
+ collection.generate_slug()
+ collection.save()
+
+ # Otherwise, use the collection selected from the drop-down
+ else:
+ collection = form.collection.data
+ if collection and collection.creator != request.user.id:
+ collection = None
+
+ # Make sure the user actually selected a collection
+ if not collection:
+ messages.add_message(
+ request, messages.ERROR,
+ _('You have to select or add a collection'))
+ return redirect(request, "mediagoblin.user_pages.media_collect",
+ user=media.get_uploader.username,
+ media_id=media.id)
+
+
+ # Check whether media already exists in collection
+ elif CollectionItem.query.filter_by(
+ media_entry=media.id,
+ collection=collection.id).first():
+ messages.add_message(request, messages.ERROR,
+ _('"%s" already in collection "%s"')
+ % (media.title, collection.title))
+ else: # Add item to collection
+ add_media_to_collection(collection, media, form.note.data)
+
+ messages.add_message(request, messages.SUCCESS,
+ _('"%s" added to collection "%s"')
+ % (media.title, collection.title))
+
+ return redirect_obj(request, media)
+
+
+#TODO: Why does @user_may_delete_media not implicate @require_active_login?
+@get_media_entry_by_id
+@require_active_login
+@user_may_delete_media
+def media_confirm_delete(request, media):
+
+ form = user_forms.ConfirmDeleteForm(request.form)
+
+ if request.method == 'POST' and form.validate():
+ if form.confirm.data is True:
+ username = media.get_uploader.username
+ # Delete MediaEntry and all related files, comments etc.
+ media.delete()
+ messages.add_message(
+ request, messages.SUCCESS, _('You deleted the media.'))
+
+ return redirect(request, "mediagoblin.user_pages.user_home",
+ user=username)
+ else:
+ messages.add_message(
+ request, messages.ERROR,
+ _("The media was not deleted because you didn't check that you were sure."))
+ return redirect_obj(request, media)
+
+ if ((request.user.is_admin and
+ request.user.id != media.uploader)):
+ messages.add_message(
+ request, messages.WARNING,
+ _("You are about to delete another user's media. "
+ "Proceed with caution."))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/media_confirm_delete.html',
+ {'media': media,
+ 'form': form})
+
+
+@active_user_from_url
+@uses_pagination
+def user_collection(request, page, url_user=None):
+ """A User-defined Collection"""
+ collection = Collection.query.filter_by(
+ get_creator=url_user,
+ slug=request.matchdict['collection']).first()
+
+ if not collection:
+ return render_404(request)
+
+ cursor = collection.get_collection_items()
+
+ pagination = Pagination(page, cursor)
+ collection_items = pagination()
+
+ # if no data is available, return NotFound
+ # TODO: Should an empty collection really also return 404?
+ if collection_items == None:
+ return render_404(request)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/collection.html',
+ {'user': url_user,
+ 'collection': collection,
+ 'collection_items': collection_items,
+ 'pagination': pagination})
+
+
+@active_user_from_url
+def collection_list(request, url_user=None):
+ """A User-defined Collection"""
+ collections = Collection.query.filter_by(
+ get_creator=url_user)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/collection_list.html',
+ {'user': url_user,
+ 'collections': collections})
+
+
+@get_user_collection_item
+@require_active_login
+@user_may_alter_collection
+def collection_item_confirm_remove(request, collection_item):
+
+ form = user_forms.ConfirmCollectionItemRemoveForm(request.form)
+
+ if request.method == 'POST' and form.validate():
+ username = collection_item.in_collection.get_creator.username
+ collection = collection_item.in_collection
+
+ if form.confirm.data is True:
+ entry = collection_item.get_media_entry
+ entry.collected = entry.collected - 1
+ entry.save()
+
+ collection_item.delete()
+ collection.items = collection.items - 1
+ collection.save()
+
+ messages.add_message(
+ request, messages.SUCCESS, _('You deleted the item from the collection.'))
+ else:
+ messages.add_message(
+ request, messages.ERROR,
+ _("The item was not removed because you didn't check that you were sure."))
+
+ return redirect_obj(request, collection)
+
+ if ((request.user.is_admin and
+ request.user.id != collection_item.in_collection.creator)):
+ messages.add_message(
+ request, messages.WARNING,
+ _("You are about to delete an item from another user's collection. "
+ "Proceed with caution."))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/collection_item_confirm_remove.html',
+ {'collection_item': collection_item,
+ 'form': form})
+
+
+@get_user_collection
+@require_active_login
+@user_may_alter_collection
+def collection_confirm_delete(request, collection):
+
+ form = user_forms.ConfirmDeleteForm(request.form)
+
+ if request.method == 'POST' and form.validate():
+
+ username = collection.get_creator.username
+
+ if form.confirm.data is True:
+ collection_title = collection.title
+
+ # Delete all the associated collection items
+ for item in collection.get_collection_items():
+ entry = item.get_media_entry
+ entry.collected = entry.collected - 1
+ entry.save()
+ item.delete()
+
+ collection.delete()
+ messages.add_message(request, messages.SUCCESS,
+ _('You deleted the collection "%s"') % collection_title)
+
+ return redirect(request, "mediagoblin.user_pages.user_home",
+ user=username)
+ else:
+ messages.add_message(
+ request, messages.ERROR,
+ _("The collection was not deleted because you didn't check that you were sure."))
+
+ return redirect_obj(request, collection)
+
+ if ((request.user.is_admin and
+ request.user.id != collection.creator)):
+ messages.add_message(
+ request, messages.WARNING,
+ _("You are about to delete another user's collection. "
+ "Proceed with caution."))
+
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/collection_confirm_delete.html',
+ {'collection': collection,
+ 'form': form})
+
+
+ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15
+
+
+def atom_feed(request):
+ """
+ generates the atom feed with the newest images
+ """
+ user = User.query.filter_by(
+ username = request.matchdict['user'],
+ status = u'active').first()
+ if not user:
+ return render_404(request)
+
+ cursor = MediaEntry.query.filter_by(
+ uploader = user.id,
+ state = u'processed').\
+ order_by(MediaEntry.created.desc()).\
+ limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
+
+ """
+ ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
+ """
+ atomlinks = [{
+ 'href': request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ qualified=True, user=request.matchdict['user']),
+ 'rel': 'alternate',
+ 'type': 'text/html'
+ }]
+
+ if mg_globals.app_config["push_urls"]:
+ for push_url in mg_globals.app_config["push_urls"]:
+ atomlinks.append({
+ 'rel': 'hub',
+ 'href': push_url})
+
+ feed = AtomFeed(
+ "MediaGoblin: Feed for user '%s'" % request.matchdict['user'],
+ feed_url=request.url,
+ id='tag:{host},{year}:gallery.user-{user}'.format(
+ host=request.host,
+ year=datetime.datetime.today().strftime('%Y'),
+ user=request.matchdict['user']),
+ links=atomlinks)
+
+ for entry in cursor:
+ feed.add(entry.get('title'),
+ entry.description_html,
+ id=entry.url_for_self(request.urlgen, qualified=True),
+ content_type='html',
+ author={
+ 'name': entry.get_uploader.username,
+ 'uri': request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ qualified=True, user=entry.get_uploader.username)},
+ updated=entry.get('created'),
+ links=[{
+ 'href': entry.url_for_self(
+ request.urlgen,
+ qualified=True),
+ 'rel': 'alternate',
+ 'type': 'text/html'}])
+
+ return feed.get_response()
+
+
+def collection_atom_feed(request):
+ """
+ generates the atom feed with the newest images from a collection
+ """
+ user = User.query.filter_by(
+ username = request.matchdict['user'],
+ status = u'active').first()
+ if not user:
+ return render_404(request)
+
+ collection = Collection.query.filter_by(
+ creator=user.id,
+ slug=request.matchdict['collection']).first()
+ if not collection:
+ return render_404(request)
+
+ cursor = CollectionItem.query.filter_by(
+ collection=collection.id) \
+ .order_by(CollectionItem.added.desc()) \
+ .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS)
+
+ """
+ ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI)
+ """
+ atomlinks = [{
+ 'href': collection.url_for_self(request.urlgen, qualified=True),
+ 'rel': 'alternate',
+ 'type': 'text/html'
+ }]
+
+ if mg_globals.app_config["push_urls"]:
+ for push_url in mg_globals.app_config["push_urls"]:
+ atomlinks.append({
+ 'rel': 'hub',
+ 'href': push_url})
+
+ feed = AtomFeed(
+ "MediaGoblin: Feed for %s's collection %s" %
+ (request.matchdict['user'], collection.title),
+ feed_url=request.url,
+ id=u'tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\
+ .format(
+ host=request.host,
+ year=collection.created.strftime('%Y'),
+ user=request.matchdict['user'],
+ slug=collection.slug),
+ links=atomlinks)
+
+ for item in cursor:
+ entry = item.get_media_entry
+ feed.add(entry.get('title'),
+ item.note_html,
+ id=entry.url_for_self(request.urlgen, qualified=True),
+ content_type='html',
+ author={
+ 'name': entry.get_uploader.username,
+ 'uri': request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ qualified=True, user=entry.get_uploader.username)},
+ updated=item.get('added'),
+ links=[{
+ 'href': entry.url_for_self(
+ request.urlgen,
+ qualified=True),
+ 'rel': 'alternate',
+ 'type': 'text/html'}])
+
+ return feed.get_response()
+
+
+@require_active_login
+def processing_panel(request):
+ """
+ Show to the user what media is still in conversion/processing...
+ and what failed, and why!
+ """
+ user = User.query.filter_by(username=request.matchdict['user']).first()
+ # TODO: XXX: Should this be a decorator?
+ #
+ # Make sure we have permission to access this user's panel. Only
+ # admins and this user herself should be able to do so.
+ if not (user.id == request.user.id or request.user.is_admin):
+ # No? Simply redirect to this user's homepage.
+ return redirect(
+ request, 'mediagoblin.user_pages.user_home',
+ user=user.username)
+
+ # Get media entries which are in-processing
+ processing_entries = MediaEntry.query.\
+ filter_by(uploader = user.id,
+ state = u'processing').\
+ order_by(MediaEntry.created.desc())
+
+ # Get media entries which have failed to process
+ failed_entries = MediaEntry.query.\
+ filter_by(uploader = user.id,
+ state = u'failed').\
+ order_by(MediaEntry.created.desc())
+
+ processed_entries = MediaEntry.query.\
+ filter_by(uploader = user.id,
+ state = u'processed').\
+ order_by(MediaEntry.created.desc()).\
+ limit(10)
+
+ # Render to response
+ return render_to_response(
+ request,
+ 'mediagoblin/user_pages/processing_panel.html',
+ {'user': user,
+ 'processing_entries': processing_entries,
+ 'failed_entries': failed_entries,
+ 'processed_entries': processed_entries})
diff --git a/mediagoblin/views.py b/mediagoblin/views.py
new file mode 100644
index 00000000..6acd7e96
--- /dev/null
+++ b/mediagoblin/views.py
@@ -0,0 +1,46 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin import mg_globals
+from mediagoblin.db.models import MediaEntry
+from mediagoblin.tools.pagination import Pagination
+from mediagoblin.tools.response import render_to_response
+from mediagoblin.decorators import uses_pagination
+
+
+
+@uses_pagination
+def root_view(request, page):
+ cursor = MediaEntry.query.filter_by(state=u'processed').\
+ order_by(MediaEntry.created.desc())
+
+ pagination = Pagination(page, cursor)
+ media_entries = pagination()
+ return render_to_response(
+ request, 'mediagoblin/root.html',
+ {'media_entries': media_entries,
+ 'allow_registration': mg_globals.app_config["allow_registration"],
+ 'pagination': pagination})
+
+
+def simple_template_render(request):
+ """
+ A view for absolutely simple template rendering.
+ Just make sure 'template' is in the matchdict!
+ """
+ template_name = request.matchdict['template']
+ return render_to_response(
+ request, template_name, {})
diff --git a/mediagoblin/webfinger/__init__.py b/mediagoblin/webfinger/__init__.py
new file mode 100644
index 00000000..126e6ea2
--- /dev/null
+++ b/mediagoblin/webfinger/__init__.py
@@ -0,0 +1,25 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+'''
+mediagoblin.webfinger_ provides an LRDD discovery service and
+a web host meta information file
+
+Links:
+- `LRDD Discovery Draft
+ <http://tools.ietf.org/html/draft-hammer-discovery-06>`_.
+- `RFC 6415 - Web Host Metadata
+ <http://tools.ietf.org/html/rfc6415>`_.
+'''
diff --git a/mediagoblin/webfinger/routing.py b/mediagoblin/webfinger/routing.py
new file mode 100644
index 00000000..eb10509f
--- /dev/null
+++ b/mediagoblin/webfinger/routing.py
@@ -0,0 +1,23 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.tools.routing import add_route
+
+add_route('mediagoblin.webfinger.host_meta', '/.well-known/host-meta',
+ 'mediagoblin.webfinger.views:host_meta')
+
+add_route('mediagoblin.webfinger.xrd', '/webfinger/xrd',
+ 'mediagoblin.webfinger.views:xrd')
diff --git a/mediagoblin/webfinger/views.py b/mediagoblin/webfinger/views.py
new file mode 100644
index 00000000..97fc3ef7
--- /dev/null
+++ b/mediagoblin/webfinger/views.py
@@ -0,0 +1,117 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+'''
+For references, see docstring in mediagoblin/webfinger/__init__.py
+'''
+
+import re
+
+from urlparse import urlparse
+
+from mediagoblin.tools.response import render_to_response, render_404
+
+def host_meta(request):
+ '''
+ Webfinger host-meta
+ '''
+
+ placeholder = 'MG_LRDD_PLACEHOLDER'
+
+ lrdd_title = 'GNU MediaGoblin - User lookup'
+
+ lrdd_template = request.urlgen(
+ 'mediagoblin.webfinger.xrd',
+ uri=placeholder,
+ qualified=True)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/webfinger/host-meta.xml',
+ {'request': request,
+ 'lrdd_template': lrdd_template,
+ 'lrdd_title': lrdd_title,
+ 'placeholder': placeholder})
+
+MATCH_SCHEME_PATTERN = re.compile(r'^acct:')
+
+def xrd(request):
+ '''
+ Find user data based on a webfinger URI
+ '''
+ param_uri = request.GET.get('uri')
+
+ if not param_uri:
+ return render_404(request)
+
+ '''
+ :py:module:`urlparse` does not recognize usernames in URIs of the
+ form ``acct:user@example.org`` or ``user@example.org``.
+ '''
+ if not MATCH_SCHEME_PATTERN.search(param_uri):
+ # Assume the URI is in the form ``user@example.org``
+ uri = 'acct://' + param_uri
+ else:
+ # Assumes the URI looks like ``acct:user@example.org
+ uri = MATCH_SCHEME_PATTERN.sub(
+ 'acct://', param_uri)
+
+ parsed = urlparse(uri)
+
+ xrd_subject = param_uri
+
+ # TODO: Verify that the user exists
+ # Q: Does webfinger support error handling in this case?
+ # Returning 404 seems intuitive, need to check.
+ if parsed.username:
+ # The user object
+ # TODO: Fetch from database instead of using the MockUser
+ user = MockUser()
+ user.username = parsed.username
+
+ xrd_links = [
+ {'attrs': {
+ 'rel': 'http://microformats.org/profile/hcard',
+ 'href': request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username,
+ qualified=True)}},
+ {'attrs': {
+ 'rel': 'http://schemas.google.com/g/2010#updates-from',
+ 'href': request.urlgen(
+ 'mediagoblin.user_pages.atom_feed',
+ user=user.username,
+ qualified=True)}}]
+
+ xrd_alias = request.urlgen(
+ 'mediagoblin.user_pages.user_home',
+ user=user.username,
+ qualified=True)
+
+ return render_to_response(
+ request,
+ 'mediagoblin/webfinger/xrd.xml',
+ {'request': request,
+ 'subject': xrd_subject,
+ 'alias': xrd_alias,
+ 'links': xrd_links })
+ else:
+ return render_404(request)
+
+class MockUser(object):
+ '''
+ TEMPORARY user object
+ '''
+ username = None