diff options
339 files changed, 31510 insertions, 16866 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e6a7464c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "pdf.js"] + path = pdf.js + url = git://github.com/mozilla/pdf.js.git +[submodule "extlib/pdf.js"] + path = extlib/pdf.js + url = git://github.com/mozilla/pdf.js.git @@ -8,31 +8,60 @@ variety of different ways and this software wouldn't exist without them. Thank you! * Aaron Williamson +* Aeva Ntsc * Alejandro Villanueva * Aleksandar Micovic +* Aleksej Serdjukov * Alex Camelio +* András Veres-Szentkirályi +* Bassam Kurdali * Bernhard Keller +* Brett Smith * Caleb Forbes Davis V * Corey Farwell * Chris Moylan * Christopher Allan Webber * Daniel Neel * Deb Nicholson +* Derek Moore +* Duncan Paterson * Elrond of Samba TNG +* Emily O'Leary +* Greg Grossmeier * Jakob Kramer * Jef van Schendel +* Jessica Tallon +* Jim Campbell * Joar Wandborg +* Jorge Araya Navarro * Karen Rustad +* Kuno Woudt +* Larisa Hoffenbecker +* Luke Slater +* Manuel Urbano Santos * Mark Holmquist * Matt Lee +* Michele Azzolari * Nathan Yergler * Odin Hørthe Omdal * Osama Khalid * Pablo J. Urbano Santos * Rasmus Larsson +* Runar Petursson +* Sacha De'Angeli * Sam Kleinman * Sebastian Spaeth * Shawn Khan +* Stefano Zacchiroli +* Tiberiu C. Turbureanu +* Tran Thanh Bao +* Shawn Khan * Will Kahn-Greene If you think your name should be on this list, let us know! + + +We also are currently borrowing an image in +mediagoblin/static/images/media_thumbs/image.png from the wonderful +people at http://tango.freedesktop.org/ which is in the public +domain... thanks Tango folks!
\ No newline at end of file diff --git a/FOO300 b/FOO300 deleted file mode 100644 index 0acf17a8..00000000 --- a/FOO300 +++ /dev/null @@ -1,15 +0,0 @@ - -This certifies that GNU MediaGoblin has been given the designation of: - - FOO 300 - -In the Foo Communications ("FooCorp") catalogue of permanent record. - -Signed: - - - - - Matt Lee - - Foo Communications, LLC
\ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 9300c698..0a39ce84 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,11 @@ -recursive-include mediagoblin/templates *.html *.txt -recursive-include mediagoblin/static *.js *.css *.png *.svg *.ico -recursive-include mediagoblin/tests *.ini +recursive-include mediagoblin/i18n *.mo +recursive-include mediagoblin *.js *.css *.png *.svg *.ico +recursive-include mediagoblin *.ini +recursive-include mediagoblin *.html *.txt recursive-include docs *.rst *.html +include mediagoblin.ini mediagoblin/config_spec.ini paste.ini include mediagoblin/config_spec.ini +graft extlib +graft licenses +include COPYING AUTHORS +include lazyserver.sh lazystarter.sh lazycelery.sh diff --git a/api-docs/Makefile b/api-docs/Makefile index 0f667642..9ed77c61 100644 --- a/api-docs/Makefile +++ b/api-docs/Makefile @@ -8,7 +8,7 @@ SPHINXAPIDOC = sphinx-apidoc PAPER = BUILDDIR = build SOURCEDIR = source -MEDIAGOBLIN_SOURCEDIR = ../ +MEDIAGOBLIN_SOURCEDIR = ../mediagoblin # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 @@ -1,10 +1,10 @@ # Extraction from Python source files [python: mediagoblin/**.py] # Extraction from Genshi HTML and text templates -[jinja2: mediagoblin/templates/**.html] +[jinja2: mediagoblin/**/templates/**.html] # Extract jinja templates (html) encoding = utf-8 -extensions = jinja2.ext.autoescape +extensions = jinja2.ext.autoescape, mediagoblin.tools.template.TemplateHookExtension [jinja2: mediagoblin/templates/**.txt] # Extract jinja templates (text) diff --git a/devtools/maketarball.sh b/devtools/maketarball.sh index 7d88c6fd..c6c2bc2b 100755 --- a/devtools/maketarball.sh +++ b/devtools/maketarball.sh @@ -161,6 +161,9 @@ then rm -rf docs/_build/ fi + # Remove .pyc files that may have been generated by sphinx + find mediagoblin -name '*.pyc' -exec rm {} \; + popd tar -cvf $FNBASE.tar $FNBASE diff --git a/docs/source/conf.py b/docs/source/conf.py index 4209acc8..0b2bccac 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,7 +26,8 @@ sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] +intersphinx_mapping = {'python': ('http://docs.python.org/2.7', None)} # Add any paths that contain templates here, relative to this directory. templates_path = ['source/_templates'] diff --git a/docs/source/siteadmin/codebase.rst b/docs/source/devel/codebase.rst index 3ef91290..122a3297 100644 --- a/docs/source/siteadmin/codebase.rst +++ b/docs/source/devel/codebase.rst @@ -34,7 +34,81 @@ various recipes for getting things done. for where we hang out. For more information on how to get started hacking on GNU MediaGoblin, -see `the wiki <http://wiki.mediagoblin.org/>`_. +see `the wiki <http://wiki.mediagoblin.org/>`_, and specifically, go +through the +`Hacking HOWTO <http://wiki.mediagoblin.org/HackingHowto>`_ +which explains generally how to get going with running an instance for +development. + + +What's where +============ + +After you've run checked out mediagoblin and followed the virtualenv +instantiation instructions, you're faced with the following directory +tree:: + + mediagoblin/ + |- mediagoblin/ # source code + | |- db/ # database setup + | |- tools/ # various utilities + | |- init/ # "initialization" tools (arguably should be in tools/) + | |- tests/ # unit tests + | |- templates/ # templates for this application + | |- media_types/ # code for processing, displaying different media + | |- storage/ # different storage backends + | |- gmg_commands/ # command line tools (./bin/gmg) + | |- themes/ # pre-bundled themes + | | + | | # ... some submodules here as well for different sections + | | # of the application... here's just a few + | |- auth/ # authentication (login/registration) code + | |- user_dev/ # user pages (under /u/), including media pages + | \- submit/ # submitting media for processing + | + |- docs/ # documentation + |- devtools/ # some scripts for developer convenience + | + |- user_dev/ # local instance sessions, media, etc + | + | # the below directories are installed into your virtualenv checkout + | + |- bin/ # scripts + |- develop-eggs/ + |- lib/ # python libraries installed into your virtualenv + |- include/ + |- mediagoblin.egg-info/ + \- parts/ + + +As you can see, all the code for GNU MediaGoblin is in the +``mediagoblin`` directory. + +Here are some interesting files and what they do: + +:routing.py: maps url paths to views +:views.py: views handle http requests +:forms.py: wtforms stuff for this submodule + +You'll notice that there are several sub-directories: tests, +templates, auth, submit, ... + +``tests`` holds the unit test code. + +``templates`` holds all the templates for the output. + +``auth`` and ``submit`` are modules that enacpsulate authentication +and media item submission. If you look in these directories, you'll +see they have their own ``routing.py``, ``view.py``, and forms.py in +addition to some other code. + +You'll also notice that mediagoblin/db/ contains quite a few things, +including the following: + +:models.py: This is where the database is set up +:mixin.py: Certain functions appended to models from here +:migrations.py: When creating a new migration (a change to the + database structure), we put it here Software Stack @@ -45,7 +119,7 @@ Software Stack * `Python <http://python.org/>`_: the language we're using to write this - * `Nose <http://somethingaboutorange.com/mrl/projects/nose/>`_: + * `Py.Test <http://pytest.org/>`_: for unit tests * `virtualenv <http://www.virtualenv.org/>`_: for setting up an @@ -65,13 +139,11 @@ Software Stack `Paste Script <http://pythonpaste.org/script/>`_: we'll use this for configuring and launching the application - * `WebOb <http://pythonpaste.org/webob/>`_: nice abstraction layer + * `werkzeug <http://werkzeug.pocoo.org/>`_: nice abstraction layer from HTTP requests, responses and WSGI bits - * `Routes <http://routes.groovie.org/>`_: for URL routing - - * `Beaker <http://beaker.groovie.org/>`_: for handling sessions and - caching + * `itsdangerous <http://pythonhosted.org/itsdangerous/>`_: + for handling sessions * `Jinja2 <http://jinja.pocoo.org/docs/>`_: the templating engine @@ -109,52 +181,3 @@ Software Stack * `JQuery <http://jquery.com/>`_: for groovy JavaScript things - -What's where -============ - -After you've run checked out mediagoblin and followed the virtualenv -instantiation instructions, you're faced with the following directory -tree:: - - mediagoblin/ - |- mediagoblin/ # source code - | |- tests/ - | |- templates/ - | |- auth/ - | \- submit/ - |- docs/ # documentation - |- devtools/ # some scripts for developer convenience - | - | # the below directories are installed into your virtualenv checkout - | - |- bin/ # scripts - |- develop-eggs/ - |- lib/ # python libraries installed into your virtualenv - |- include/ - |- mediagoblin.egg-info/ - |- parts/ - |- user_dev/ # sessions, etc - - -As you can see, all the code for GNU MediaGoblin is in the -``mediagoblin`` directory. - -Here are some interesting files and what they do: - -:routing.py: maps url paths to views -:views.py: views handle http requests -:models.py: holds the sqlalchemy schemas---these are the data structures - we're working with - -You'll notice that there are several sub-directories: tests, -templates, auth, submit, ... - -``tests`` holds the unit test code. - -``templates`` holds all the templates for the output. - -``auth`` and ``submit`` are modules that enacpsulate authentication -and media item submission. If you look in these directories, you'll -see they have their own ``routing.py``, ``view.py``, and -``models.py`` in addition to some other code. diff --git a/docs/source/devel/originaldesigndecisions.rst b/docs/source/devel/originaldesigndecisions.rst new file mode 100644 index 00000000..2843870c --- /dev/null +++ b/docs/source/devel/originaldesigndecisions.rst @@ -0,0 +1,336 @@ +.. _original-design-decisions-chapter: + +=========================== + Original Design Decisions +=========================== + +.. contents:: Sections + :local: + + +This chapter talks a bit about design decisions. + +Note: This is an outdated document. It's more or less the historical +reasons for a lot of things. That doesn't mean these decisions have +stayed the same or we haven't changed our minds on some things! + + +Why GNU MediaGoblin? +==================== + +Chris and Will on "Why GNU MediaGoblin": + + Chris came up with the name MediaGoblin. The name is pretty fun. + It merges the idea that this is a Media hosting project with + Goblin which sort of sounds like gobbling. Here's a piece of + software that gobbles up your media for all to see. + + `According to Wikipedia <http://en.wikipedia.org/wiki/Goblin>`_, a + goblin is: + + a legendary evil or mischievous illiterate creature, described + as grotesquely evil or evil-like phantom + + So are we evil? No. Are we mischievous or illiterate? Not + really. So what kind of goblin are we thinking about? We're + thinking about these goblins: + + .. figure:: ../_static/goblin.png + :alt: Cute goblin with a beret. + + *Figure 1: Cute goblin with a beret. llustrated by Chris + Webber* + + .. figure:: ../_static/snugglygoblin.png + :scale: 50% + :alt: Snuggly goblin with a beret. + + *Figure 2: Snuggly goblin. Illustrated by Karen Rustad* + + Those are pretty cute goblins. Those are the kinds of goblins + we're thinking about. + + Chris started doing work on the project after thinking about it + for a year. Then, after talking with Matt and Rob, it became an + official GNU project. Thus we now call it GNU MediaGoblin. + + That's a lot of letters, though, so in the interest of brevity and + facilitating easier casual conversation and balancing that with + what's important to us, we have the following rules: + + 1. "GNU MediaGoblin" is the name we're going to use in all official + capacities: web site, documentation, press releases, ... + + 2. In casual conversation, it's ok to use more casual names. + + 3. If you're writing about the project, we ask that you call it GNU + MediaGoblin. + + 4. If you don't like the name, we kindly ask you to take a deep + breath, think a happy thought about cute little goblins playing + on a playground and taking cute pictures of themselves, and let + it go. (Will added this one.) + + +Why Python +========== + +Chris Webber on "Why Python": + + Because I know Python, love Python, am capable of actually making + this thing happen in Python (I've worked on a lot of large free + software web applications before in Python, including `Miro + Community`_, the `Miro Guide`_, a large portion of `Creative + Commons`_, and a whole bunch of things while working at `Imaginary + Landscape`_). Me starting a project like this makes sense if it's + done in Python. + + You might say that PHP is way more deployable, that Rails has way + more cool developers riding around on fixie bikes---and all of + those things are true. But I know Python, like Python, and think + that Python is pretty great. I do think that deployment in Python + is not as good as with PHP, but I think the days of shared hosting + are (thankfully) coming to an end, and will probably be replaced + by cheap virtual machines spun up on the fly for people who want + that sort of stuff, and Python will be a huge part of that future, + maybe even more than PHP will. The deployment tools are getting + better. Maybe we can use something like Silver Lining. Maybe we + can just distribute as ``.debs`` or ``.rpms``. We'll figure it + out when we get there. + + Regardless, if I'm starting this project, which I am, it's gonna + be in Python. + +.. _Miro Community: http://mirocommunity.org/ +.. _Miro Guide: http://miroguide.org/ +.. _Creative Commons: http://creativecommons.org/ +.. _Imaginary Landscape: http://www.imagescape.com/ + + +Why WSGI Minimalism +=================== + +Chris Webber on "Why WSGI Minimalism": + + If you notice in the technology list I list a lot of components + that are very "django-like", but not actually `Django`_ + components. What can I say, I really like a lot of the ideas in + Django! Which leads to the question: why not just use Django? + + While I really like Django's ideas and a lot of its components, I + also feel that most of the best ideas in Django I want have been + implemented as good or even better outside of Django. I could + just use Django and replace the templating system with Jinja2, and + the form system with wtforms, and the database with MongoDB and + MongoKit, but at that point, how much of Django is really left? + + I also am sometimes saddened and irritated by how coupled all of + Django's components are. Loosely coupled yes, but still coupled. + WSGI has done a good job of providing a base layer for running + applications on and if you know how to do it yourself [1]_, it's + not hard or many lines of code at all to bind them together + without any framework at all (not even say `Pylons`_, `Pyramid`_ + or `Flask`_ which I think are still great projects, especially for + people who want this sort of thing but have no idea how to get + started). And even at this already really early stage of writing + MediaGoblin, that glue work is mostly done. + + Not to say I don't think Django isn't great for a lot of things. + For a lot of stuff, it's still the best, but not for MediaGoblin, + I think. + + One thing that Django does super well though is documentation. It + still has some faults, but even with those considered I can hardly + think of any other project in Python that has as nice of + documentation as Django. It may be worth learning some lessons on + documentation from Django [2]_, on that note. + + I'd really like to have a good, thorough hacking-howto and + deployment-howto, especially in the former making some notes on + how to make it easier for Django hackers to get started. + +.. _Django: http://www.djangoproject.com/ +.. _Pylons: http://pylonshq.com/ +.. _Pyramid: http://docs.pylonsproject.org/projects/pyramid/dev/ +.. _Flask: http://flask.pocoo.org/ + +.. [1] http://pythonpaste.org/webob/do-it-yourself.html +.. [2] http://pycon.blip.tv/file/4881071/ + + +Why MongoDB +=========== + +(Note: We don't use MongoDB anymore. This is the original rationale, +however.) + +Chris Webber on "Why MongoDB": + + In case you were wondering, I am not a NOSQL fanboy, I do not go + around telling people that MongoDB is web scale. Actually my + choice for MongoDB isn't scalability, though scaling up really + nicely is a pretty good feature and sets us up well in case large + volume sites eventually do use MediaGoblin. But there's another + side of scalability, and that's scaling down, which is important + for federation, maybe even more important than scaling up in an + ideal universe where everyone ran servers out of their own + housing. As a memory-mapped database, MongoDB is pretty hungry, + so actually I spent a lot of time debating whether the inability + to scale down as nicely as something like SQL has with sqlite + meant that it was out. + + But I decided in the end that I really want MongoDB, not for + scalability, but for flexibility. Schema evolution pains in SQL + are almost enough reason for me to want MongoDB, but not quite. + The real reason is because I want the ability to eventually handle + multiple media types through MediaGoblin, and also allow for + plugins, without the rigidity of tables making that difficult. In + other words, something like:: + + {"title": "Me talking until you are bored", + "description": "blah blah blah", + "media_type": "audio", + "media_data": { + "length": "2:30", + "codec": "OGG Vorbis"}, + "plugin_data": { + "licensing": { + "license": "http://creativecommons.org/licenses/by-sa/3.0/"}}} + + + Being able to just dump media-specific information in a media_data + hashtable is pretty great, and even better is having a plugin + system where you can just let plugins have their own entire + key-value space cleanly inside the document that doesn't interfere + with anyone else's stuff. If we were to let plugins to deposit + their own information inside the database, either we'd let plugins + create their own tables which makes SQL migrations even harder + than they already are, or we'd probably end up creating a table + with a column for key, a column for value, and a column for type + in one huge table called "plugin_data" or something similar. (Yo + dawg, I heard you liked plugins, so I put a database in your + database so you can query while you query.) Gross. + + I also don't want things to be too loose so that we forget or lose + the structure of things, and that's one reason why I want to use + MongoKit, because we can cleanly define a much structure as we + want and verify that documents match that structure generally + without adding too much bloat or overhead (MongoKit is a pretty + lightweight wrapper and doesn't inject extra MongoKit-specific + stuff into the database, which is nice and nicer than many other + ORMs in that way). + + +Why Sphinx for documentation +============================ + +Will Kahn-Greene on "Why Sphinx": + + `Sphinx`_ is a fantastic tool for organizing documentation for a + Python-based project that makes it pretty easy to write docs that + are readable in source form and can be "compiled" into HTML, LaTeX + and other formats. + + There are other doc systems out there, but given that GNU + MediaGoblin is being written in Python and I've done a ton of + documentation using Sphinx, it makes sense to use Sphinx for now. + +.. _Sphinx: http://sphinx.pocoo.org/ + + +Why AGPLv3 and CC0? +=================== + +Chris, Brett, Will, Rob, Matt, et al curated into a story where +everyone is the hero by Will on "Why AGPLv3 and CC0": + + The `AGPL v3`_ preserves the freedoms guaranteed by the GPL v3 in + the context of software as a service. Using this license ensures + that users of the service have the ability to examine the source, + deploy their own instance, and implement their own version. This + is really important to us and a core mission component of this + project. Thus we decided that the software parts should be under + this license. + + However, the project is made up of more than just software: + there's CSS, images, and other output-related things. We wanted + the templates/images/css side of the project all permissive and + permissive in the same absolutely permissive way. We're waiving + our copyrights to non-software things under the CC0 waiver. + + That brings us to the templates where there's some code and some + output. The template engine we're using is called Jinja2. It + mixes HTML markup with Python code to render the output of the + software. We decided the templates are part of the output of the + software and not the software itself. We wanted the output of the + software to be licensed in a hassle-free way so that when someone + deploys their own GNU MediaGoblin instance with their own + templates, they don't have to deal with the copyleft aspects of + the AGPLv3 and we'd be fine with that because the changes they're + making are identity-related. So at first we decided to waive our + copyrights to the templates with a CC0 waiver and then add an + exception to the AGPLv3 for the software such that the templates + can make calls into the software and yet be a separately licensed + work. However, Brett brought up the question of whether this + allows some unscrupulous person to make changes to the software + through the templates in such a way that they're not bound by the + AGPLv3: i.e. a loophole. We thought about this loophole and + between this and the extra legalese involved in the exception to + the AGPLv3, we decided that it's just way simpler if the templates + were also licensed under the AGPLv3. + + Then we have the licensing for the documentation. Given that the + documentation is tied to the software content-wise, we don't feel + like we have to worry about ensuring freedom of the documentation + or worry about attribution concerns. Thus we're waiving our + copyrights to the documentation under CC0 as well. + + Lastly, we have branding. This covers logos and other things that + are distinctive to GNU MediaGoblin that we feel represents this + project. Since we don't currently have any branding, this is an + open issue, but we're thinking we'll go with a CC BY-SA license. + + By licensing in this way, we make sure that users of the software + receive the freedoms that the AGPLv3 ensures regardless of what + fate befalls this project. + + So to summarize: + + * software (Python, JavaScript, HTML templates): licensed + under AGPLv3 + * non-software things (CSS, images, video): copyrights waived + under CC0 because this is output of the software + * documentation: copyrights waived under CC0 because it's not part + of the software + * branding assets: we're kicking this can down the road, but + probably CC BY-SA + + This is all codified in the ``COPYING`` file. + +.. _AGPL v3: http://www.gnu.org/licenses/agpl.html +.. _CC0 v1: http://creativecommons.org/publicdomain/zero/1.0/ + + +Why (non-mandatory) copyright assignment? +========================================= + +Chris Webber on "Why copyright assignment?": + + GNU MediaGoblin is a GNU project with non-mandatory but heavily + encouraged copyright assignment to the FSF. Most, if not all, of + the core contributors to GNU MediaGoblin will have done a + copyright assignment, but unlike some other GNU projects, it isn't + required here. We think this is the best choice for GNU + MediaGoblin: it ensures that the Free Software Foundation may + protect the software by enforcing the AGPL if the FSF sees fit, + but it also means that we can immediately merge in changes from a + new contributor. It also means that some significant non-FSF + contributors might also be able to enforce the AGPL if seen fit. + + Again, assignment is not mandatory, but it is heavily encouraged, + even incentivized: significant contributors who do a copyright + assignment to the FSF are eligible to have a unique goblin drawing + produced for them by the project's main founder, Christopher Allan + Webber. See `the wiki <http://wiki.mediagoblin.org/>`_ for details. + + diff --git a/docs/source/devel/storage.rst b/docs/source/devel/storage.rst new file mode 100644 index 00000000..215f9579 --- /dev/null +++ b/docs/source/devel/storage.rst @@ -0,0 +1,125 @@ +========= + Storage +========= + +The storage systems attached to your app +---------------------------------------- + +Dynamic content: queue_store and public_store +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Two instances of the StorageInterface come attached to your app. These +are: + ++ **queue_store:** When a user submits a fresh piece of media for + their gallery, before the Processing stage, that piece of media sits + here in the queue_store. (It's possible that we'll rename this to + "private_store" and start storing more non-publicly-stored stuff in + the future...). This is a StorageInterface implementation + instance. Visitors to your site probably cannot see it... it isn't + designed to be seen, anyway. + ++ **public_store:** After your media goes through processing it gets + moved to the public store. This is also a StorageInterface + implelementation, and is for stuff that's intended to be seen by + site visitors. + +The workbench +~~~~~~~~~~~~~ + +In addition, there's a "workbench" used during +processing... it's just for temporary files during +processing, and also for making local copies of stuff that +might be on remote storage interfaces while transitionally +moving/converting from the queue_store to the public store. +See the workbench module documentation for more. + +.. automodule:: mediagoblin.tools.workbench + :members: + :show-inheritance: + + +Static assets / staticdirect +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On top of all that, there is some static media that comes bundled with your +application. This stuff is kept in: + + mediagoblin/static/ + +These files are for mediagoblin base assets. Things like the CSS files, +logos, etc. You can mount these at whatever location is appropriate to you +(see the direct_remote_path option in the config file) so if your users +are keeping their static assets at http://static.mgoblin.example.org/ but +their actual site is at http://mgoblin.example.org/, you need to be able +to get your static files in a where-it's-mounted agnostic way. There's a +"staticdirector" attached to the request object. It's pretty easy to use; +just look at this bit taken from the +mediagoblin/templates/mediagoblin/base.html main template: + + <link rel="stylesheet" type="text/css" + href="Template:Request.staticdirect('/css/extlib/text.css')"/> + +see? Not too hard. As expected, if you configured direct_remote_path to be +http://static.mgoblin.example.org/ you'll get back +http://static.mgoblin.example.org/css/extlib/text.css just as you'd +probably expect. + +StorageInterface and implementations +------------------------------------ + +The guts of StorageInterface and friends +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So, the StorageInterface! + +So, the public and queue stores both use StorageInterface implementations +... but what does that mean? It's not too hard. + +Open up: + + mediagoblin/storage.py + +In here you'll see a couple of things. First of all, there's the +StorageInterface class. What you'll see is that this is just a very simple +python class. A few of the methods actually implement things, but for the +most part, they don't. What really matters about this class is the +docstrings. Each expected method is documented as to how it should be +constructed. Want to make a new StorageInterface? Simply subclass it. Want +to know how to use the methods of your storage system? Read these docs, +they span all implementations. + +There are a couple of implementations of these classes bundled in +storage.py as well. The most simple of these is BasicFileStorage, which is +also the default storage system used. As expected, this stores files +locally on your machine. + +There's also a CloudFileStorage system. This provides a mapping to +[OpenStack's swift http://swift.openstack.org/] storage system (used by +RackSpace Cloud files and etc). + +Between these two examples you should be able to get a pretty good idea of +how to write your own storage systems, for storing data across your +beowulf cluster of radioactive monkey brains, whatever. + +Writing code to store stuff +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So what does coding for StorageInterface implementations actually look +like? It's pretty simple, really. For one thing, the design is fairly +inspired by [Django's file storage API +https://docs.djangoproject.com/en/dev/ref/files/storage/]... with some +differences. + +Basically, you access files on "file paths", which aren't exactly like +unix file paths, but are close. If you wanted to store a file on a path +like dir1/dir2/filename.jpg you'd actually write that file path like: + +['dir1', 'dir2', 'filename.jpg'] + +This way we can be *sure* that each component is actually a component of +the path that's expected... we do some filename cleaning on each component. + +Your StorageInterface should pass in and out "file like objects". In other +words, they should provide .read() and .write() at minimum, and probably +also .seek() and .close(). diff --git a/docs/source/index.rst b/docs/source/index.rst index ac8bd110..7f692d57 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -44,7 +44,6 @@ MediaGoblin website. It is written for site administrators. siteadmin/relnotes siteadmin/theming siteadmin/plugins - siteadmin/codebase .. _core-plugin-section: @@ -58,6 +57,8 @@ Part 2: Core plugin documentation plugindocs/flatpagesfile plugindocs/sampleplugin plugindocs/oauth + plugindocs/trim_whitespace + plugindocs/raven Part 3: Plugin Writer's Guide @@ -70,6 +71,21 @@ This guide covers writing new GNU MediaGoblin plugins. pluginwriter/foreward pluginwriter/quickstart + pluginwriter/database + pluginwriter/api + + +Part 4: Developer's Zone +======================== + +This chapter contains various information for developers. + +.. toctree:: + :maxdepth: 1 + + devel/codebase + devel/storage + devel/originaldesigndecisions Indices and tables diff --git a/docs/source/plugindocs/raven.rst b/docs/source/plugindocs/raven.rst new file mode 100644 index 00000000..71e284d0 --- /dev/null +++ b/docs/source/plugindocs/raven.rst @@ -0,0 +1,2 @@ +.. _raven-setup: Set up the raven plugin +.. include:: ../../../mediagoblin/plugins/raven/README.rst diff --git a/docs/source/plugindocs/trim_whitespace.rst b/docs/source/plugindocs/trim_whitespace.rst new file mode 100644 index 00000000..eb38e0e5 --- /dev/null +++ b/docs/source/plugindocs/trim_whitespace.rst @@ -0,0 +1 @@ +.. include:: ../../../mediagoblin/plugins/trim_whitespace/README.rst diff --git a/docs/source/pluginwriter/api.rst b/docs/source/pluginwriter/api.rst new file mode 100644 index 00000000..5e0568fd --- /dev/null +++ b/docs/source/pluginwriter/api.rst @@ -0,0 +1,127 @@ +.. MediaGoblin Documentation + + Written in 2013 by MediaGoblin contributors + + To the extent possible under law, the author(s) have dedicated all + copyright and related and neighboring rights to this software to + the public domain worldwide. This software is distributed without + any warranty. + + You should have received a copy of the CC0 Public Domain + Dedication along with this software. If not, see + <http://creativecommons.org/publicdomain/zero/1.0/>. + + +========== +Plugin API +========== + +This documents the general plugin API. + +Please note, at this point OUR PLUGIN HOOKS MAY AND WILL CHANGE. +Authors are encouraged to develop plugins and work with the +MediaGoblin community to keep them up to date, but this API will be a +moving target for a few releases. + +Please check the release notes for updates! + +:mod:`pluginapi` Module +----------------------- + +.. automodule:: mediagoblin.tools.pluginapi + :members: get_config, register_routes, register_template_path, + register_template_hooks, get_hook_templates, + hook_handle, hook_runall, hook_transform + +Configuration +------------- + +Your plugin may define its own configuration defaults. + +Simply add to the directory of your plugin a config_spec.ini file. An +example might look like:: + + [plugin_spec] + some_string = string(default="blork") + some_int = integer(default=50) + +This means that when people enable your plugin in their config you'll +be able to provide defaults as well as type validation. + + +Context Hooks +------------- + +View specific hooks ++++++++++++++++++++ + +You can hook up to almost any template called by any specific view +fairly easily. As long as the view directly or indirectly uses the +method ``render_to_response`` you can access the context via a hook +that has a key in the format of the tuple:: + + (view_symbolic_name, view_template_path) + +Where the "view symbolic name" is the same parameter used in +``request.urlgen()`` to look up the view. So say we're wanting to add +something to the context of the user's homepage. We look in +mediagoblin/user_pages/routing.py and see:: + + add_route('mediagoblin.user_pages.user_home', + '/u/<string:user>/', + 'mediagoblin.user_pages.views:user_home') + +Aha! That means that the name is ``mediagoblin.user_pages.user_home``. +Okay, so then we look at the view at the +``mediagoblin.user_pages.user_home`` method:: + + @uses_pagination + def user_home(request, page): + # [...] whole bunch of stuff here + return render_to_response( + request, + 'mediagoblin/user_pages/user.html', + {'user': user, + 'user_gallery_url': user_gallery_url, + 'media_entries': media_entries, + 'pagination': pagination}) + +Nice! So the template appears to be +``mediagoblin/user_pages/user.html``. Cool, that means that the key +is:: + + ("mediagoblin.user_pages.user_home", + "mediagoblin/user_pages/user.html") + +The context hook uses ``hook_transform()`` so that means that if we're +hooking into it, our hook will both accept one argument, ``context``, +and should return that modified object, like so:: + + def add_to_user_home_context(context): + context['foo'] = 'bar' + return context + + hooks = { + ("mediagoblin.user_pages.user_home", + "mediagoblin/user_pages/user.html"): add_to_user_home_context} + + +Global context hooks +++++++++++++++++++++ + +If you need to add something to the context of *every* view, it is not +hard; there are two hooks hook that also uses hook_transform (like the +above) but make available what you are providing to *every* view. + +Note that there is a slight, but critical, difference between the two. + +The most general one is the ``'template_global_context'`` hook. This +one is run only once, and is read into the global context... all views +will get access to what are in this dict. + +The slightly more expensive but more powerful one is +``'template_context_prerender'``. This one is not added to the global +context... it is added to the actual context of each individual +template render right before it is run! Because of this you also can +do some powerful and crazy things, such as checking the request object +or other parts of the context before passing them on. diff --git a/docs/source/pluginwriter/database.rst b/docs/source/pluginwriter/database.rst new file mode 100644 index 00000000..58edf3a0 --- /dev/null +++ b/docs/source/pluginwriter/database.rst @@ -0,0 +1,111 @@ +.. MediaGoblin Documentation + + Written in 2013 by MediaGoblin contributors + + To the extent possible under law, the author(s) have dedicated all + copyright and related and neighboring rights to this software to + the public domain worldwide. This software is distributed without + any warranty. + + You should have received a copy of the CC0 Public Domain + Dedication along with this software. If not, see + <http://creativecommons.org/publicdomain/zero/1.0/>. + + +======== +Database +======== + + +Accessing Existing Data +======================= + +If your plugin wants to access existing data, this is quite +straight forward. Just import the appropiate models and use +the full power of SQLAlchemy. Take a look at the (upcoming) +database section in the Developer's Chapter. + + +Creating new Tables +=================== + +If your plugin needs some new space to store data, you +should create a new table. Please do not modify core +tables. Not doing so might seem inefficient and possibly +is. It will help keep things sane and easier to upgrade +versions later. + +So if you create a new plugin and need new tables, create a +file named ``models.py`` in your plugin directory. You +might take a look at the core's db.models for some ideas. +Here's a simple one: + +.. code-block:: python + + from mediagoblin.db.base import Base + from sqlalchemy import Column, Integer, Unicode, ForeignKey + + class MediaSecurity(Base): + __tablename__ = "yourplugin__media_security" + + # 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("security_rating", cascade="all, delete-orphan")) + + rating = Column(Unicode) + + MODELS = [MediaSecurity] + +That's it. + +Some notes: + +* Make sure all your ``__tablename__`` start with your + plugin's name so the tables of various plugins can't + conflict in the database. (Conflicts in python naming are + much easier to fix later). +* Try to get your database design as good as possible in + the first attempt. Changing the database design later, + when people already have data using the old design, is + possible (see next chapter), but it's not easy. + + +Changing the Database Schema Later +================================== + +If your plugin is in use and instances use it to store some +data, changing the database design is a tricky thing. + +1. Make up your mind how the new schema should look like. +2. Change ``models.py`` to contain the new schema. Keep a + copy of the old version around for your personal + reference later. +3. Now make up your mind (possibly using your old and new + ``models.py``) what steps in SQL are needed to convert + the old schema to the new one. + This is called a "migration". +4. Create a file ``migrations.py`` that will contain all + your migrations and add your new migration. + +Take a look at the core's ``db/migrations.py`` for some +good examples on what you might be able to do. Here's a +simple one to add one column: + +.. code-block:: python + + from mediagoblin.db.migration_tools import RegisterMigration, inspect_table + from sqlalchemy import MetaData, Column, Integer + + MIGRATIONS = {} + + @RegisterMigration(1, MIGRATIONS) + def add_license_preference(db): + metadata = MetaData(bind=db.bind) + + security_table = inspect_table(metadata, 'yourplugin__media_security') + + col = Column('security_level', Integer) + col.create(security_table) + db.commit() diff --git a/docs/source/siteadmin/deploying.rst b/docs/source/siteadmin/deploying.rst index a270c723..9bcc0637 100644 --- a/docs/source/siteadmin/deploying.rst +++ b/docs/source/siteadmin/deploying.rst @@ -32,6 +32,11 @@ GNU/Linux distro. install. If instead you want to join in as a contributor, see our `Hacking HOWTO <http://wiki.mediagoblin.org/HackingHowto>`_ instead. + There are also many ways to install servers... for the sake of + simplicity, our instructions below describe installing with nginx. + For more recipes, including Apache, see + `our wiki <http://wiki.mediagoblin.org/Deployment>`_. + Prepare System -------------- @@ -165,7 +170,7 @@ And set up the in-package virtualenv:: If you have problems here, consider trying to install virtualenv with the ``--distribute`` or ``--no-site-packages`` options. If - your system's default Python is in the 3.x series you man need to + your system's default Python is in the 3.x series you may need to run ``virtualenv`` with the ``--python=python2.7`` or ``--python=python2.6`` options. @@ -173,22 +178,50 @@ The above provides an in-package install of ``virtualenv``. While this is counter to the conventional ``virtualenv`` configuration, it is more reliable and considerably easier to configure and illustrate. If you're familiar with Python packaging you may consider deploying with -your preferred the method. +your preferred method. Assuming you are going to deploy with FastCGI, you should also install flup:: ./bin/easy_install flup +(Sometimes this breaks because flup's site is flakey. If it does for +you, try):: + + ./bin/easy_install https://pypi.python.org/pypi/flup/1.0.3.dev-20110405 + This concludes the initial configuration of the development environment. In the future, when you update your codebase, you should also run:: ./bin/python setup.py develop --upgrade && ./bin/gmg dbupdate +Note: If you are running an active site, depending on your server +configuration, you may need to stop it first or the dbupdate command +may hang (and it's certainly a good idea to restart it after the +update) + + Deploy MediaGoblin Services --------------------------- +Edit site configuration +~~~~~~~~~~~~~~~~~~~~~~~ + +A few basic properties must be set before MediaGoblin will work. First +make a copy of ``mediagoblin.ini`` for editing so the original config +file isn't lost:: + + cp mediagoblin.ini mediagoblin_local.ini + +Then: + - Set ``email_sender_address`` to the address you wish to be used as + the sender for system-generated emails + - Edit ``direct_remote_path``, ``base_dir``, and ``base_url`` if + your mediagoblin directory is not the root directory of your + vhost. + + Configure MediaGoblin to use the PostgreSQL database ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -224,11 +257,11 @@ browser to confirm that the service is operable. .. _webserver-config: -Connect the Webserver to MediaGoblin with FastCGI -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This section describes how to configure MediaGoblin to work via -FastCGI. Our configuration example will use nginx, however, you may +FastCGI and nginx +~~~~~~~~~~~~~~~~~ + +This configuration example will use nginx, however, you may use any webserver of your choice as long as it supports the FastCGI protocol. If you do not already have a web server, consider nginx, as the configuration files may be more clear than the @@ -271,6 +304,10 @@ this ``nginx.conf`` file should be modeled on the following:: # Change this to update the upload size limit for your users client_max_body_size 8m; + # prevent attacks (someone uploading a .txt file that the browser + # interprets as an HTML file, etc.) + add_header X-Content-Type-Options nosniff; + server_name mediagoblin.example.org www.mediagoblin.example.org; access_log /var/log/nginx/mediagoblin.example.access.log; error_log /var/log/nginx/mediagoblin.example.error.log; @@ -325,3 +362,24 @@ Visit the site you've set up in your browser by visiting smaller deployments. However, for larger production deployments with larger processing requirements, see the ":doc:`production-deployments`" documentation. + + +Apache +~~~~~~ + +Instructions and scripts for running MediaGoblin on an Apache server +can be found on the `MediaGoblin wiki <http://wiki.mediagoblin.org/Deployment>`_. + + +Security Considerations +~~~~~~~~~~~~~~~~~~~~~~~ + +.. warning:: + + The directory ``user_dev/crypto/`` contains some very + sensitive files. + Especially the ``itsdangeroussecret.bin`` is very important + for session security. Make sure not to leak its contents anywhere. + If the contents gets leaked nevertheless, delete your file + and restart the server, so that it creates a new secret key. + All previous sessions will be invalifated then. diff --git a/docs/source/siteadmin/media-types.rst b/docs/source/siteadmin/media-types.rst index 5653217f..210094b9 100644 --- a/docs/source/siteadmin/media-types.rst +++ b/docs/source/siteadmin/media-types.rst @@ -43,6 +43,15 @@ video media types, then the list would look like this:: media_types = mediagoblin.media_types.image, mediagoblin.media_types.video +Note that after enabling new media types, you must run dbupdate like so:: + + ./bin/gmg dbupdate + +If you are running an active site, depending on your server +configuration, you may need to stop it first (and it's certainly a +good idea to restart it after the update). + + How does MediaGoblin decide which media type to use for a file? =============================================================== @@ -62,12 +71,27 @@ Video To enable video, first install gstreamer and the python-gstreamer bindings (as well as whatever gstremaer extensions you want, -good/bad/ugly). On Debianoid systems:: +good/bad/ugly). On Debianoid systems - sudo apt-get install python-gst0.10 gstreamer0.10-plugins-{base,bad,good,ugly} \ +.. code-block:: bash + + sudo apt-get install python-gst0.10 \ + gstreamer0.10-plugins-base \ + gstreamer0.10-plugins-bad \ + gstreamer0.10-plugins-good \ + gstreamer0.10-plugins-ugly \ gstreamer0.10-ffmpeg +Add ``mediagoblin.media_types.video`` to the ``media_types`` list in your +``mediagoblin_local.ini`` and restart MediaGoblin. + +Run + +.. code-block:: bash + + ./bin/gmg dbupdate + Now you should be able to submit videos, and mediagoblin should transcode them. @@ -92,7 +116,9 @@ To install these on Debianoid systems, run:: The ``scikits.audiolab`` package you will install in the next step depends on the ``libsndfile1-dev`` package, so we should install it. -On Debianoid systems, run:: +On Debianoid systems, run + +.. code-block:: bash sudo apt-get install libsndfile1-dev @@ -108,8 +134,15 @@ Then install ``scikits.audiolab`` for the spectrograms:: ./bin/pip install scikits.audiolab Add ``mediagoblin.media_types.audio`` to the ``media_types`` list in your -``mediagoblin_local.ini`` and restart MediaGoblin. You should now be able to -upload and listen to audio files! +``mediagoblin_local.ini`` and restart MediaGoblin. + +Run + +.. code-block:: bash + + ./bin/gmg dbupdate + +You should now be able to upload and listen to audio files! Ascii art @@ -117,7 +150,9 @@ Ascii art To enable ascii art support, first install the `chardet <http://pypi.python.org/pypi/chardet>`_ -library, which is necessary for creating thumbnails of ascii art:: +library, which is necessary for creating thumbnails of ascii art + +.. code-block:: bash ./bin/easy_install chardet @@ -131,4 +166,69 @@ the list would look like this:: media_types = mediagoblin.media_types.image, mediagoblin.media_types.ascii +Run + +.. code-block:: bash + + ./bin/gmg dbupdate + Now any .txt file you uploaded will be processed as ascii art! + + +STL / 3d model support +====================== + +To enable the "STL" 3d model support plugin, first make sure you have +a recentish `Blender <http://blender.org>`_ installed and available on +your execution path. This feature has been tested with Blender 2.63. +It may work on some earlier versions, but that is not guaranteed (and +is surely not to work prior to Blender 2.5X). + +Add ``mediagoblin.media_types.stl`` to the ``media_types`` list in your +``mediagoblin_local.ini`` and restart MediaGoblin. + +Run + +.. code-block:: bash + + ./bin/gmg dbupdate + +You should now be able to upload .obj and .stl files and MediaGoblin +will be able to present them to your wide audience of admirers! + +PDF and Document +================ + +To enable the "PDF and Document" support plugin, you need pdftocairo, pdfinfo, +unoconv with headless support. All executables must be on your execution path. + +To install this on Fedora: + +.. code-block:: bash + + sudo yum install -y poppler-utils unoconv libreoffice-headless + +pdf.js relies on git submodules, so be sure you have fetched them: + +.. code-block:: bash + + git submodule init + git submodule update + +This feature has been tested on Fedora with: + poppler-utils-0.20.2-9.fc18.x86_64 + unoconv-0.5-2.fc18.noarch + libreoffice-headless-3.6.5.2-8.fc18.x86_64 + +It may work on some earlier versions, but that is not guaranteed. + +Add ``mediagoblin.media_types.pdf`` to the ``media_types`` list in your +``mediagoblin_local.ini`` and restart MediaGoblin. + +Run + +.. code-block:: bash + + ./bin/gmg dbupdate + + diff --git a/docs/source/siteadmin/plugins.rst b/docs/source/siteadmin/plugins.rst index 75562d4b..baca381d 100644 --- a/docs/source/siteadmin/plugins.rst +++ b/docs/source/siteadmin/plugins.rst @@ -44,29 +44,33 @@ If the plugin is available on the `Python Package Index pip install <plugin-name> For example, if we wanted to install the plugin named -"mediagoblin-restrictfive", we would do:: +"mediagoblin-licenses" (which allows you to customize the licenses you +offer for your media), we would do:: - pip install mediagoblin-restrictfive + pip install mediagoblin-licenses .. Note:: If you're using a virtual environment, make sure to activate the - virtual environment before installing with pip. Otherwise the - plugin may get installed in a different environment than the one - MediaGoblin is installed in. + virtual environment before installing with pip. Otherwise the plugin + may get installed in a different environment than the one MediaGoblin + is installed in. Also make sure, you use e.g. pip-2.7 if your default + python (and thus pip) is python 3 (e.g. in Ubuntu). Once you've installed the plugin software, you need to tell MediaGoblin that this is a plugin you want MediaGoblin to use. To do that, you edit the ``mediagoblin.ini`` file and add the plugin as a subsection of the plugin section. -For example, say the "mediagoblin-restrictfive" plugin had the Python -package path ``restrictfive``, then you would add ``restrictfive`` to +For example, say the "mediagoblin-licenses" plugin has the Python +package path ``mediagoblin_licenses``, then you would add ``mediagoblin_licenses`` to the ``plugins`` section as a subsection:: [plugins] - [[restrictfive]] + [[mediagoblin_licenses]] + license_01=abbrev1, name1, http://url1 + license_02=abbrev2, name1, http://url2 Configuring plugins @@ -112,7 +116,7 @@ Removing plugins To remove a plugin, use ``pip uninstall``. For example:: - pip uninstall mediagoblin-restrictfive + pip uninstall mediagoblin-licenses .. Note:: diff --git a/docs/source/siteadmin/production-deployments.rst b/docs/source/siteadmin/production-deployments.rst index 356fab7f..1a32d95e 100644 --- a/docs/source/siteadmin/production-deployments.rst +++ b/docs/source/siteadmin/production-deployments.rst @@ -52,7 +52,7 @@ as the basis for your script: :: Separate Celery --------------- -While the ``./lazyserer.sh`` configuration provides an efficient way to +While the ``./lazyserver.sh`` configuration provides an efficient way to start using a MediaGoblin instance, it is not suitable for production deployments for several reasons: @@ -77,6 +77,17 @@ Modify your existing MediaGoblin and application init scripts, if necessary, to prevent them from starting their own ``celeryd`` processes. +.. _sentry: + +Set up sentry to monitor exceptions +----------------------------------- + +We have a plugin for `raven`_ integration, see the ":doc:`/plugindocs/raven`" +documentation. + +.. _`raven`: http://raven.readthedocs.org + + .. _init-script: Use an Init Script diff --git a/docs/source/siteadmin/relnotes.rst b/docs/source/siteadmin/relnotes.rst index 1b70b31c..04863ec6 100644 --- a/docs/source/siteadmin/relnotes.rst +++ b/docs/source/siteadmin/relnotes.rst @@ -19,6 +19,166 @@ This chapter has important information for releases in it. If you're upgrading from a previous release, please read it carefully, or at least skim over it. +0.3.3 +===== + +**Do this to upgrade** + +1. Make sure to run ``bin/gmg dbupdate`` after upgrading. +2. OpenStreetMap is now a plugin, so if you want to use it, add the + following to your config file: + + .. code-block:: ini + + [plugins] + [[mediagoblin.plugins.geolocation]] + +If you have your own theme, you may need to make some adjustments to +it as some theme related things may have changed in this release. If +you run into problems, don't hesitate to +`contact us <http://mediagoblin.org/pages/join.html>`_ +(IRC is often best). + +**New features** + +* New dropdown menu for accessing various features. + +* Significantly improved URL generation. Now mediagoblin won't give + up on making a slug if it looks like there will be a duplicate; + it'll try extra hard to generate a meaningful one instead. + + Similarly, linking to an id no longer can possibly conflict with + linking to a slug; /u/username/m/id:35/ is the kind of reference we + now use to linking to entries with ids. However, old links with + entries that linked to ids should work just fine with our migration. + The only urls that might break in this release are ones using colons + or equal signs. + +* New template hooks for plugin authoring. + +* As a demonstration of new template hooks for plugin authoring, + openstreetmap support now moved to a plugin! + +* Method to add media to collections switched from icon of paperclip + to button with "add to collection" text. + +* Bug where videos often failed to produce a proper thumbnail fixed! + +* Copying around files in MediaGoblin now much more efficient, doesn't + waste gobs of memory. + +* Video transcoding now optional for videos that meet certain + criteria. By default, MediaGoblin will not transcode webm videos + that are smaller in resolution than the MediaGoblin defaults, and + MediaGoblin can also be configured to allow theora files to not be + transcoded as well. + +* Per-user license preference option; always want your uploads to be + BY-SA and tired of changing that field? You can now set your + license preference in your user settings. + +* Video player now responsive; better for mobile! + +* You can now delete your account from the user preferences page if + you so wish. + +**Other changes** + +* Plugin writers: Internal restructuring led to mediagoblin.db.sql* be + mediagoblin.db.* starting from 0.3.3 + +* Dependency list has been reduced not requiring the "webob" package anymore. + +* And many small fixes/improvements, too numerous to list! + + +0.3.2 +===== + +This will be the last release that is capable of converting from an earlier +MongoDB-based MediaGoblin instance to the newer SQL-based system. + +**Do this to upgrade** + + # directory of your mediagoblin install + cd /srv/mediagoblin.example.org + + # copy source for this release + git fetch + git checkout tags/v0.3.2 + + # perform any needed database updates + bin/gmg dbupdate + + # restart your servers however you do that, e.g., + sudo service mediagoblin-paster restart + sudo service mediagoblin-celeryd restart + + +**New features** + +* **3d model support!** + + You can now upload STL and OBJ files and display them in + MediaGoblin. Requires a recent-ish Blender; for details see: + :ref:`deploying-chapter` + +* **trim_whitespace** + + We bundle the optional plugin trim_whitespace which reduces the size + of the delivered html output by reducing redundant whitespace. + + See :ref:`core-plugin-section` for plugin documentation + +* **A new API!** + + It isn't well documented yet but we do have an API. There is an + `android application in progress <https://gitorious.org/mediagoblin/mediagoblin-android>`_ + which makes use of it, and there are some demo applications between + `automgtic <https://github.com/jwandborg/automgtic>`_, an + automatic media uploader for your desktop + and `OMGMG <https://github.com/jwandborg/omgmg>`_, an example of + a web application hooking up to the API. + + This is a plugin, so you have to enable it in your mediagoblin + config file by adding a section under [plugins] like:: + + [plugins] + [[mediagoblin.plugins.api]] + + Note that the API works but is not nailed down... the way it is + called may change in future releases. + +* **OAuth login support** + + For applications that use OAuth to connect to the API. + + This is a plugin, so you have to enable it in your mediagoblin + config file by adding a section under [plugins] like:: + + [plugins] + [[mediagoblin.plugins.oauth]] + +* **Collections** + + We now have user-curated collections support. These are arbitrary + galleries that are customizable by users. You can add media to + these by clicking on the paperclip icon when logged in and looking + at a media entry. + +* **OpenStreetMap licensing display improvements** + + More accurate display of OSM licensing, and less disruptive: you + click to "expand" the display of said licensing. + + Geolocation is also now on by default. + +* **Miscelaneous visual improvements** + + We've made a number of small visual improvements including newer and + nicer looking thumbnails and improved checkbox placement. + + 0.3.1 ===== diff --git a/extlib/README b/extlib/README index c690beac..45ee5b46 100644 --- a/extlib/README +++ b/extlib/README @@ -17,7 +17,7 @@ unwittingly interfere with other software that depends on the canonical release versions of those same libraries! Forking upstream software for trivial reasons makes us bad citizens in -the Open Source community and adds unnecessary heartache for our +the Free Software community and adds unnecessary heartache for our users. Don't make us "that" project. @@ -63,6 +63,19 @@ FAQ This is a last resort; consult with the rest of the dev group before taking this radical step. +:Q: What about submodules? + +:A: pdf.js is supplied as a submodule, and other software may use that too, + to add a new submodule: + git submodule add <git-repo-of-fun-project> extlib/fun-project + + Use it just like a snapshotted extlib directory. When a new clone of mediagoblin + is made you need to run + + git submodule init + git submodule update + + As noted in HackingHowto Thanks ====== diff --git a/extlib/exif/EXIF.py b/extlib/exif/EXIF.py index ed4192af..a188154e 100755 --- a/extlib/exif/EXIF.py +++ b/extlib/exif/EXIF.py @@ -1,8 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Library to extract EXIF information from digital camera image files -# http://sourceforge.net/projects/exif-py/ +# +# Library to extract EXIF information from digital camera image files. +# https://github.com/ianare/exif-py +# # # VERSION 1.1.0 # @@ -22,7 +24,6 @@ # # These 2 are useful when you are retrieving a large list of images # -# # To return an error on invalid tags, # pass the -s or --strict argument, or as # tags = EXIF.process_file(f, strict=True) @@ -48,7 +49,7 @@ # 'EXIF DateTimeOriginal', 'Image Orientation', 'MakerNote FocusMode' # # Copyright (c) 2002-2007 Gene Cash All rights reserved -# Copyright (c) 2007-2008 Ianaré Sévi All rights reserved +# Copyright (c) 2007-2012 Ianaré Sévi All rights reserved # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -102,7 +103,7 @@ def make_string_uc(seq): seq = seq[8:] # Of course, this is only correct if ASCII, and the standard explicitly # allows JIS and Unicode. - return make_string(seq) + return make_string( make_string(seq) ) # field type descriptions as (length, abbreviation, full name) tuples FIELD_TYPES = ( @@ -171,9 +172,9 @@ EXIF_TAGS = { 3: 'Rotated 180', 4: 'Mirrored vertical', 5: 'Mirrored horizontal then rotated 90 CCW', - 6: 'Rotated 90 CW', + 6: 'Rotated 90 CCW', 7: 'Mirrored horizontal then rotated 90 CW', - 8: 'Rotated 90 CCW'}), + 8: 'Rotated 90 CW'}), 0x0115: ('SamplesPerPixel', ), 0x0116: ('RowsPerStrip', ), 0x0117: ('StripByteCounts', ), @@ -251,40 +252,54 @@ EXIF_TAGS = { 2: 'CenterWeightedAverage', 3: 'Spot', 4: 'MultiSpot', - 5: 'Pattern'}), + 5: 'Pattern', + 6: 'Partial', + 255: 'other'}), 0x9208: ('LightSource', {0: 'Unknown', 1: 'Daylight', 2: 'Fluorescent', - 3: 'Tungsten', - 9: 'Fine Weather', - 10: 'Flash', + 3: 'Tungsten (incandescent light)', + 4: 'Flash', + 9: 'Fine weather', + 10: 'Cloudy weather', 11: 'Shade', - 12: 'Daylight Fluorescent', - 13: 'Day White Fluorescent', - 14: 'Cool White Fluorescent', - 15: 'White Fluorescent', - 17: 'Standard Light A', - 18: 'Standard Light B', - 19: 'Standard Light C', + 12: 'Daylight fluorescent (D 5700 - 7100K)', + 13: 'Day white fluorescent (N 4600 - 5400K)', + 14: 'Cool white fluorescent (W 3900 - 4500K)', + 15: 'White fluorescent (WW 3200 - 3700K)', + 17: 'Standard light A', + 18: 'Standard light B', + 19: 'Standard light C', 20: 'D55', 21: 'D65', 22: 'D75', - 255: 'Other'}), + 23: 'D50', + 24: 'ISO studio tungsten', + 255: 'other light source',}), 0x9209: ('Flash', - {0: 'No', - 1: 'Fired', - 5: 'Fired (?)', # no return sensed - 7: 'Fired (!)', # return sensed - 9: 'Fill Fired', - 13: 'Fill Fired (?)', - 15: 'Fill Fired (!)', - 16: 'Off', - 24: 'Auto Off', - 25: 'Auto Fired', - 29: 'Auto Fired (?)', - 31: 'Auto Fired (!)', - 32: 'Not Available'}), + {0: 'Flash did not fire', + 1: 'Flash fired', + 5: 'Strobe return light not detected', + 7: 'Strobe return light detected', + 9: 'Flash fired, compulsory flash mode', + 13: 'Flash fired, compulsory flash mode, return light not detected', + 15: 'Flash fired, compulsory flash mode, return light detected', + 16: 'Flash did not fire, compulsory flash mode', + 24: 'Flash did not fire, auto mode', + 25: 'Flash fired, auto mode', + 29: 'Flash fired, auto mode, return light not detected', + 31: 'Flash fired, auto mode, return light detected', + 32: 'No flash function', + 65: 'Flash fired, red-eye reduction mode', + 69: 'Flash fired, red-eye reduction mode, return light not detected', + 71: 'Flash fired, red-eye reduction mode, return light detected', + 73: 'Flash fired, compulsory flash mode, red-eye reduction mode', + 77: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected', + 79: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected', + 89: 'Flash fired, auto mode, red-eye reduction mode', + 93: 'Flash fired, auto mode, return light not detected, red-eye reduction mode', + 95: 'Flash fired, auto mode, return light detected, red-eye reduction mode'}), 0x920A: ('FocalLength', ), 0x9214: ('SubjectArea', ), 0x927C: ('MakerNote', ), @@ -410,7 +425,10 @@ GPS_TAGS = { 0x0018: ('GPSDestBearing', ), 0x0019: ('GPSDestDistanceRef', ), 0x001A: ('GPSDestDistance', ), + 0x001B: ('GPSProcessingMethod', ), + 0x001C: ('GPSAreaInformation', ), 0x001D: ('GPSDate', ), + 0x001E: ('GPSDifferential', ), } # Ignore these tags when quick processing @@ -1231,10 +1249,17 @@ class IFD_Tag: return self.printable def __repr__(self): - return '(0x%04X) %s=%s @ %d' % (self.tag, + try: + s= '(0x%04X) %s=%s @ %d' % (self.tag, FIELD_TYPES[self.field_type][2], self.printable, self.field_offset) + except: + s= '(%s) %s=%s @ %s' % (str(self.tag), + FIELD_TYPES[self.field_type][2], + self.printable, + str(self.field_offset)) + return s # class that handles an EXIF header class EXIF_header: @@ -1283,7 +1308,11 @@ class EXIF_header: # return pointer to next IFD def next_IFD(self, ifd): entries=self.s2n(ifd, 2) - return self.s2n(ifd+2+12*entries, 4) + next_ifd = self.s2n(ifd+2+12*entries, 4) + if next_ifd == ifd: + return 0 + else: + return next_ifd # return list of IFDs in header def list_IFDs(self): @@ -1348,14 +1377,15 @@ class EXIF_header: # special case: null-terminated ASCII string # XXX investigate # sometimes gets too big to fit in int value - if count != 0 and count < (2**31): - self.file.seek(self.offset + offset) - values = self.file.read(count) - #print values - # Drop any garbage after a null. - values = values.split('\x00', 1)[0] - else: - values = '' + if count != 0: # and count < (2**31): # 2E31 is hardware dependant. --gd + try: + self.file.seek(self.offset + offset) + values = self.file.read(count) + #print values + # Drop any garbage after a null. + values = values.split('\x00', 1)[0] + except OverflowError: + values = '' else: values = [] signed = (field_type in [6, 8, 9, 10]) @@ -1567,7 +1597,8 @@ class EXIF_header: dict=MAKERNOTE_CANON_TAGS) for i in (('MakerNote Tag 0x0001', MAKERNOTE_CANON_TAG_0x001), ('MakerNote Tag 0x0004', MAKERNOTE_CANON_TAG_0x004)): - self.canon_decode_tag(self.tags[i[0]].values, i[1]) + if i[0] in self.tags: + self.canon_decode_tag(self.tags[i[0]].values, i[1]) return @@ -1613,26 +1644,124 @@ def process_file(f, stop_tag='UNDEF', details=True, strict=False, debug=False): offset = 0 elif data[0:2] == '\xFF\xD8': # it's a JPEG file + if debug: print "JPEG format recognized data[0:2] == '0xFFD8'." + base = 2 while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'): + if debug: print "data[2] == 0xxFF data[3]==%x and data[6:10] = %s"%(ord(data[3]),data[6:10]) length = ord(data[4])*256+ord(data[5]) + if debug: print "Length offset is",length f.read(length-8) # fake an EXIF beginning of file + # I don't think this is used. --gd data = '\xFF\x00'+f.read(10) fake_exif = 1 - if data[2] == '\xFF' and data[6:10] == 'Exif': + if base>2: + if debug: print "added to base " + base = base + length + 4 -2 + else: + if debug: print "added to zero " + base = length + 4 + if debug: print "Set segment base to",base + + # Big ugly patch to deal with APP2 (or other) data coming before APP1 + f.seek(0) + data = f.read(base+4000) # in theory, this could be insufficient since 64K is the maximum size--gd + # base = 2 + while 1: + if debug: print "Segment base 0x%X" % base + if data[base:base+2]=='\xFF\xE1': + # APP1 + if debug: print "APP1 at base",hex(base) + if debug: print "Length",hex(ord(data[base+2])), hex(ord(data[base+3])) + if debug: print "Code",data[base+4:base+8] + if data[base+4:base+8] == "Exif": + if debug: print "Decrement base by",2,"to get to pre-segment header (for compatibility with later code)" + base = base-2 + break + if debug: print "Increment base by",ord(data[base+2])*256+ord(data[base+3])+2 + base=base+ord(data[base+2])*256+ord(data[base+3])+2 + elif data[base:base+2]=='\xFF\xE0': + # APP0 + if debug: print "APP0 at base",hex(base) + if debug: print "Length",hex(ord(data[base+2])), hex(ord(data[base+3])) + if debug: print "Code",data[base+4:base+8] + if debug: print "Increment base by",ord(data[base+2])*256+ord(data[base+3])+2 + base=base+ord(data[base+2])*256+ord(data[base+3])+2 + elif data[base:base+2]=='\xFF\xE2': + # APP2 + if debug: print "APP2 at base",hex(base) + if debug: print "Length",hex(ord(data[base+2])), hex(ord(data[base+3])) + if debug: print "Code",data[base+4:base+8] + if debug: print "Increment base by",ord(data[base+2])*256+ord(data[base+3])+2 + base=base+ord(data[base+2])*256+ord(data[base+3])+2 + elif data[base:base+2]=='\xFF\xEE': + # APP14 + if debug: print "APP14 Adobe segment at base",hex(base) + if debug: print "Length",hex(ord(data[base+2])), hex(ord(data[base+3])) + if debug: print "Code",data[base+4:base+8] + if debug: print "Increment base by",ord(data[base+2])*256+ord(data[base+3])+2 + print "There is useful EXIF-like data here, but we have no parser for it." + base=base+ord(data[base+2])*256+ord(data[base+3])+2 + elif data[base:base+2]=='\xFF\xDB': + if debug: print "JPEG image data at base",hex(base),"No more segments are expected." + # sys.exit(0) + break + elif data[base:base+2]=='\xFF\xD8': + # APP12 + if debug: print "FFD8 segment at base",hex(base) + if debug: print "Got",hex(ord(data[base])), hex(ord(data[base+1])),"and", data[4+base:10+base], "instead." + if debug: print "Length",hex(ord(data[base+2])), hex(ord(data[base+3])) + if debug: print "Code",data[base+4:base+8] + if debug: print "Increment base by",ord(data[base+2])*256+ord(data[base+3])+2 + base=base+ord(data[base+2])*256+ord(data[base+3])+2 + elif data[base:base+2]=='\xFF\xEC': + # APP12 + if debug: print "APP12 XMP (Ducky) or Pictureinfo segment at base",hex(base) + if debug: print "Got",hex(ord(data[base])), hex(ord(data[base+1])),"and", data[4+base:10+base], "instead." + if debug: print "Length",hex(ord(data[base+2])), hex(ord(data[base+3])) + if debug: print "Code",data[base+4:base+8] + if debug: print "Increment base by",ord(data[base+2])*256+ord(data[base+3])+2 + print "There is useful EXIF-like data here (quality, comment, copyright), but we have no parser for it." + base=base+ord(data[base+2])*256+ord(data[base+3])+2 + else: + try: + if debug: print "Unexpected/unhandled segment type or file content." + if debug: print "Got",hex(ord(data[base])), hex(ord(data[base+1])),"and", data[4+base:10+base], "instead." + if debug: print "Increment base by",ord(data[base+2])*256+ord(data[base+3])+2 + except: pass + try: base=base+ord(data[base+2])*256+ord(data[base+3])+2 + except: pass + + f.seek(base+12) + if data[2+base] == '\xFF' and data[6+base:10+base] == 'Exif': # detected EXIF header offset = f.tell() endian = f.read(1) + #HACK TEST: endian = 'M' + elif data[2+base] == '\xFF' and data[6+base:10+base+1] == 'Ducky': + # detected Ducky header. + if debug: print "EXIF-like header (normally 0xFF and code):",hex(ord(data[2+base])) , "and", data[6+base:10+base+1] + offset = f.tell() + endian = f.read(1) + elif data[2+base] == '\xFF' and data[6+base:10+base+1] == 'Adobe': + # detected APP14 (Adobe) + if debug: print "EXIF-like header (normally 0xFF and code):",hex(ord(data[2+base])) , "and", data[6+base:10+base+1] + offset = f.tell() + endian = f.read(1) else: # no EXIF information + if debug: print "No EXIF header expected data[2+base]==0xFF and data[6+base:10+base]===Exif (or Duck)" + if debug: print " but got",hex(ord(data[2+base])) , "and", data[6+base:10+base+1] return {} else: # file format not recognized + if debug: print "file format not recognized" return {} # deal with the EXIF info we found if debug: - print {'I': 'Intel', 'M': 'Motorola'}[endian], 'format' + print "Endian format is ",endian + print {'I': 'Intel', 'M': 'Motorola', '\x01':'Adobe Ducky', 'd':'XMP/Adobe unknown' }[endian], 'format' hdr = EXIF_header(f, endian, offset, fake_exif, strict, debug) ifd_list = hdr.list_IFDs() ctr = 0 diff --git a/extlib/exif/changes.txt b/extlib/exif/changes.txt new file mode 100644 index 00000000..d1b18e6c --- /dev/null +++ b/extlib/exif/changes.txt @@ -0,0 +1,131 @@ +~ EXIF.py Changelog ~ + +2012-11-30 - Gregory Dudek (date of merge). +Patches and changes: + Overflow error fixes added (related to 2**31 size) + GPS tags added. + +2012-09-26 - Ianaré Sévi +Merge patches: + Add GPS tags + Add better endian debug info + +2012-06-13 - Ianaré Sévi +Merge patches: + Support malformed last IFD by fhats + Light source, Flash and Metering mode dictionaries update by gryfik + +2008-07-31 - Ianaré Sévi +Wikipedia Commons hunt for suitable test case images, +testing new code additions. + +2008-07-09 - Stephen H. Olson +Fix a problem with reading MakerNotes out of NEF files. +Add some more Nikon MakerNote tags. + +2008-07-08 - Stephen H. Olson +An error check for large tags totally borked MakerNotes. + With Nikon anyway, valid MakerNotes can be pretty big. +Add error check for a crash caused by nikon_ev_bias being + called with the wrong args. +Drop any garbage after a null character in string + (patch from Andrew McNabb <amcnabb@google.com>). + +2008-02-12 - Ianaré Sévi +Fix crash on invalid MakerNote +Fix crash on huge Makernote (temp fix) +Add printIM tag 0xC4A5, needs decoding info +Add 0x9C9B-F range of tags +Add a bunch of tag definitions from: + http://owl.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html +Add 'strict' variable and command line option + +2008-01-18 - Gunter Ohrner +Add 'GPSDate' tag + +2007-12-12 - Ianaré Sévi +Fix quick option on certain image types +Add note on tag naming in documentation + +2007-11-30 - Ianaré Sévi +Changed -s option to -t +Put changelog into separate file + +2007-10-28 - Ianaré Sévi +Merged changes from MoinMoin:ReimarBauer +Added command line option for debug, stop +processing on tag. + +2007-09-27 - Ianaré Sévi +Add some Olympus Makernote tags. + +2007-09-26 - Stephen H. Olson +Don't error out on invalid Olympus 'SpecialMode'. +Add a few more Olympus/Minolta tags. + +2007-09-22 - Stephen H. Olson +Don't error on invalid string +Improved Nikon MakerNote support + +2007-05-03 - Martin Stone <mj_stone@users.sourceforge.net> +Fix for inverted detailed flag and Photoshop header + +2007-03-24 - Ianaré Sévi +Can now ignore MakerNotes Tags for faster processing. + +2007-01-18 - Ianaré Sévi <ianare@gmail.com> +Fixed a couple errors and assuming maintenance of the library. + +2006-08-04 MoinMoin:ReimarBauer +Added an optional parameter name to process_file and dump_IFD. Using this parameter the +loop is breaked after that tag_name is processed. +some PEP8 changes + +---------------------------- original notices ------------------------- + +Contains code from "exifdump.py" originally written by Thierry Bousch +<bousch@topo.math.u-psud.fr> and released into the public domain. + +Updated and turned into general-purpose library by Gene Cash + +Patch Contributors: +* Simon J. Gerraty <sjg@crufty.net> +s2n fix & orientation decode +* John T. Riedl <riedl@cs.umn.edu> +Added support for newer Nikon type 3 Makernote format for D70 and some +other Nikon cameras. +* Joerg Schaefer <schaeferj@gmx.net> +Fixed subtle bug when faking an EXIF header, which affected maker notes +using relative offsets, and a fix for Nikon D100. + +1999-08-21 TB Last update by Thierry Bousch to his code. + +2002-01-17 CEC Discovered code on web. + Commented everything. + Made small code improvements. + Reformatted for readability. + +2002-01-19 CEC Added ability to read TIFFs and JFIF-format JPEGs. + Added ability to extract JPEG formatted thumbnail. + Added ability to read GPS IFD (not tested). + Converted IFD data structure to dictionaries indexed by + tag name. + Factored into library returning dictionary of IFDs plus + thumbnail, if any. + +2002-01-20 CEC Added MakerNote processing logic. + Added Olympus MakerNote. + Converted data structure to single-level dictionary, avoiding + tag name collisions by prefixing with IFD name. This makes + it much easier to use. +2002-01-23 CEC Trimmed nulls from end of string values. + +2002-01-25 CEC Discovered JPEG thumbnail in Olympus TIFF MakerNote. + +2002-01-26 CEC Added ability to extract TIFF thumbnails. + Added Nikon, Fujifilm, Casio MakerNotes. + +2003-11-30 CEC Fixed problem with canon_decode_tag() not creating an + IFD_Tag() object. + +2004-02-15 CEC Finally fixed bit shift warning by converting Y to 0L. diff --git a/extlib/freesound/audioprocessing.py b/extlib/freesound/audioprocessing.py index c1dfe2eb..2c2b35b5 100644 --- a/extlib/freesound/audioprocessing.py +++ b/extlib/freesound/audioprocessing.py @@ -20,7 +20,7 @@ # Bram de Jong <bram.dejong at domain.com where domain in gmail> # 2012, Joar Wandborg <first name at last name dot se> -import Image, ImageDraw, ImageColor #@UnresolvedImport +from PIL import Image, ImageDraw, ImageColor #@UnresolvedImport from functools import partial import math import numpy diff --git a/extlib/html5shiv/MIT.txt b/extlib/html5shiv/MIT.txt deleted file mode 100644 index 5a2aeb47..00000000 --- a/extlib/html5shiv/MIT.txt +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) <year> <copyright holders> - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/extlib/html5shiv/html5shiv.js b/extlib/html5shiv/html5shiv.js deleted file mode 100644 index 8de0ff54..00000000 --- a/extlib/html5shiv/html5shiv.js +++ /dev/null @@ -1,3 +0,0 @@ -// HTML5 Shiv v3 | @jon_neal @afarkas @rem | MIT/GPL2 Licensed -// Uncompressed source: https://github.com/aFarkas/html5shiv -(function(a,b){var c=function(a){return a.innerHTML="<x-element></x-element>",a.childNodes.length===1}(b.createElement("a")),d=function(a,b,c){return b.appendChild(a),(c=(c?c(a):a.currentStyle).display)&&b.removeChild(a)&&c==="block"}(b.createElement("nav"),b.documentElement,a.getComputedStyle),e={elements:"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video".split(" "),shivDocument:function(a){a=a||b;if(a.documentShived)return;a.documentShived=!0;var f=a.createElement,g=a.createDocumentFragment,h=a.getElementsByTagName("head")[0],i=function(a){f(a)};c||(e.elements.join(" ").replace(/\w+/g,i),a.createElement=function(a){var b=f(a);return b.canHaveChildren&&e.shivDocument(b.document),b},a.createDocumentFragment=function(){return e.shivDocument(g())});if(!d&&h){var j=f("div");j.innerHTML=["x<style>","article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}","audio{display:none}","canvas,video{display:inline-block;*display:inline;*zoom:1}","[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}","mark{background:#FF0;color:#000}","</style>"].join(""),h.insertBefore(j.lastChild,h.firstChild)}return a}};e.shivDocument(b),a.html5=e})(this,document)
\ No newline at end of file diff --git a/extlib/jquery/MIT.txt b/extlib/jquery/MIT-LICENSE.txt index 5a2aeb47..957f26d3 100644 --- a/extlib/jquery/MIT.txt +++ b/extlib/jquery/MIT-LICENSE.txt @@ -1,4 +1,5 @@ -Copyright (c) <year> <copyright holders> +Copyright 2013 jQuery Foundation and other contributors +http://jquery.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/extlib/pdf.js b/extlib/pdf.js new file mode 160000 +Subproject 369b81b63f560b5d729da26752ca541503d8151 diff --git a/extlib/thingiview.js/LICENSE b/extlib/thingiview.js/LICENSE new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/extlib/thingiview.js/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 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. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/extlib/thingiview.js/Three.js b/extlib/thingiview.js/Three.js new file mode 100644 index 00000000..5c21380c --- /dev/null +++ b/extlib/thingiview.js/Three.js @@ -0,0 +1,202 @@ +// Three.js r32 - http://github.com/mrdoob/three.js +var THREE=THREE||{};THREE.Color=function(a){this.autoUpdate=true;this.setHex(a)}; +THREE.Color.prototype={setRGB:function(a,c,d){this.r=a;this.g=c;this.b=d;if(this.autoUpdate){this.updateHex();this.updateStyleString()}},setHex:function(a){this.hex=~~a&16777215;if(this.autoUpdate){this.updateRGBA();this.updateStyleString()}},updateHex:function(){this.hex=~~(this.r*255)<<16^~~(this.g*255)<<8^~~(this.b*255)},updateRGBA:function(){this.r=(this.hex>>16&255)/255;this.g=(this.hex>>8&255)/255;this.b=(this.hex&255)/255},updateStyleString:function(){this.__styleString="rgb("+~~(this.r*255)+ +","+~~(this.g*255)+","+~~(this.b*255)+")"},clone:function(){return new THREE.Color(this.hex)},toString:function(){return"THREE.Color ( r: "+this.r+", g: "+this.g+", b: "+this.b+", hex: "+this.hex+" )"}};THREE.Vector2=function(a,c){this.x=a||0;this.y=c||0}; +THREE.Vector2.prototype={set:function(a,c){this.x=a;this.y=c;return this},copy:function(a){this.x=a.x;this.y=a.y;return this},addSelf:function(a){this.x+=a.x;this.y+=a.y;return this},add:function(a,c){this.x=a.x+c.x;this.y=a.y+c.y;return this},subSelf:function(a){this.x-=a.x;this.y-=a.y;return this},sub:function(a,c){this.x=a.x-c.x;this.y=a.y-c.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},unit:function(){this.multiplyScalar(1/this.length());return this},length:function(){return Math.sqrt(this.x* +this.x+this.y*this.y)},lengthSq:function(){return this.x*this.x+this.y*this.y},negate:function(){this.x=-this.x;this.y=-this.y;return this},clone:function(){return new THREE.Vector2(this.x,this.y)},toString:function(){return"THREE.Vector2 ("+this.x+", "+this.y+")"}};THREE.Vector3=function(a,c,d){this.x=a||0;this.y=c||0;this.z=d||0}; +THREE.Vector3.prototype={set:function(a,c,d){this.x=a;this.y=c;this.z=d;return this},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,c){this.x=a.x+c.x;this.y=a.y+c.y;this.z=a.z+c.z;return this},addSelf:function(a){this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;return this},sub:function(a,c){this.x=a.x-c.x;this.y=a.y-c.y;this.z=a.z-c.z;return this},subSelf:function(a){this.x-=a.x;this.y-=a.y;this.z-=a.z;return this}, +cross:function(a,c){this.x=a.y*c.z-a.z*c.y;this.y=a.z*c.x-a.x*c.z;this.z=a.x*c.y-a.y*c.x;return this},crossSelf:function(a){var c=this.x,d=this.y,e=this.z;this.x=d*a.z-e*a.y;this.y=e*a.x-c*a.z;this.z=c*a.y-d*a.x;return this},multiply:function(a,c){this.x=a.x*c.x;this.y=a.y*c.y;this.z=a.z*c.z;return this},multiplySelf:function(a){this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;return this},divideSelf:function(a){this.x/=a.x;this.y/=a.y;this.z/= +a.z;return this},divideScalar:function(a){this.x/=a;this.y/=a;this.z/=a;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z},distanceTo:function(a){var c=this.x-a.x,d=this.y-a.y;a=this.z-a.z;return Math.sqrt(c*c+d*d+a*a)},distanceToSquared:function(a){var c=this.x-a.x,d=this.y-a.y;a=this.z-a.z;return c*c+d*d+a*a},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z},negate:function(){this.x= +-this.x;this.y=-this.y;this.z=-this.z;return this},normalize:function(){var a=Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z);a>0?this.multiplyScalar(1/a):this.set(0,0,0);return this},setLength:function(a){return this.normalize().multiplyScalar(a)},isZero:function(){return Math.abs(this.x)<1.0E-4&&Math.abs(this.y)<1.0E-4&&Math.abs(this.z)<1.0E-4},clone:function(){return new THREE.Vector3(this.x,this.y,this.z)},toString:function(){return"THREE.Vector3 ( "+this.x+", "+this.y+", "+this.z+" )"}}; +THREE.Vector4=function(a,c,d,e){this.x=a||0;this.y=c||0;this.z=d||0;this.w=e||1}; +THREE.Vector4.prototype={set:function(a,c,d,e){this.x=a;this.y=c;this.z=d;this.w=e;return this},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=a.w||1;return this},add:function(a,c){this.x=a.x+c.x;this.y=a.y+c.y;this.z=a.z+c.z;this.w=a.w+c.w;return this},addSelf:function(a){this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},sub:function(a,c){this.x=a.x-c.x;this.y=a.y-c.y;this.z=a.z-c.z;this.w=a.w-c.w;return this},subSelf:function(a){this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.w; +return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;this.w*=a;return this},divideScalar:function(a){this.x/=a;this.y/=a;this.z/=a;this.w/=a;return this},lerpSelf:function(a,c){this.x+=(a.x-this.x)*c;this.y+=(a.y-this.y)*c;this.z+=(a.z-this.z)*c;this.w+=(a.w-this.w)*c},clone:function(){return new THREE.Vector4(this.x,this.y,this.z,this.w)},toString:function(){return"THREE.Vector4 ("+this.x+", "+this.y+", "+this.z+", "+this.w+")"}}; +THREE.Ray=function(a,c){this.origin=a||new THREE.Vector3;this.direction=c||new THREE.Vector3}; +THREE.Ray.prototype={intersectScene:function(a){var c,d,e=a.objects,g=[];a=0;for(c=e.length;a<c;a++){d=e[a];if(d instanceof THREE.Mesh)g=g.concat(this.intersectObject(d))}g.sort(function(h,o){return h.distance-o.distance});return g},intersectObject:function(a){function c(K,p,U,F){F=F.clone().subSelf(p);U=U.clone().subSelf(p);var f=K.clone().subSelf(p);K=F.dot(F);p=F.dot(U);F=F.dot(f);var j=U.dot(U);U=U.dot(f);f=1/(K*j-p*p);j=(j*F-p*U)*f;K=(K*U-p*F)*f;return j>0&&K>0&&j+K<1}var d,e,g,h,o,b,i,k,y,z, +u,x=a.geometry,H=x.vertices,J=[];d=0;for(e=x.faces.length;d<e;d++){g=x.faces[d];z=this.origin.clone();u=this.direction.clone();h=a.matrix.multiplyVector3(H[g.a].position.clone());o=a.matrix.multiplyVector3(H[g.b].position.clone());b=a.matrix.multiplyVector3(H[g.c].position.clone());i=g instanceof THREE.Face4?a.matrix.multiplyVector3(H[g.d].position.clone()):null;k=a.rotationMatrix.multiplyVector3(g.normal.clone());y=u.dot(k);if(y<0){k=k.dot((new THREE.Vector3).sub(h,z))/y;z=z.addSelf(u.multiplyScalar(k)); +if(g instanceof THREE.Face3){if(c(z,h,o,b)){g={distance:this.origin.distanceTo(z),point:z,face:g,object:a};J.push(g)}}else if(g instanceof THREE.Face4)if(c(z,h,o,i)||c(z,o,b,i)){g={distance:this.origin.distanceTo(z),point:z,face:g,object:a};J.push(g)}}}return J}}; +THREE.Rectangle=function(){function a(){h=e-c;o=g-d}var c,d,e,g,h,o,b=true;this.getX=function(){return c};this.getY=function(){return d};this.getWidth=function(){return h};this.getHeight=function(){return o};this.getLeft=function(){return c};this.getTop=function(){return d};this.getRight=function(){return e};this.getBottom=function(){return g};this.set=function(i,k,y,z){b=false;c=i;d=k;e=y;g=z;a()};this.addPoint=function(i,k){if(b){b=false;c=i;d=k;e=i;g=k}else{c=c<i?c:i;d=d<k?d:k;e=e>i?e:i;g=g>k? +g:k}a()};this.add3Points=function(i,k,y,z,u,x){if(b){b=false;c=i<y?i<u?i:u:y<u?y:u;d=k<z?k<x?k:x:z<x?z:x;e=i>y?i>u?i:u:y>u?y:u;g=k>z?k>x?k:x:z>x?z:x}else{c=i<y?i<u?i<c?i:c:u<c?u:c:y<u?y<c?y:c:u<c?u:c;d=k<z?k<x?k<d?k:d:x<d?x:d:z<x?z<d?z:d:x<d?x:d;e=i>y?i>u?i>e?i:e:u>e?u:e:y>u?y>e?y:e:u>e?u:e;g=k>z?k>x?k>g?k:g:x>g?x:g:z>x?z>g?z:g:x>g?x:g}a()};this.addRectangle=function(i){if(b){b=false;c=i.getLeft();d=i.getTop();e=i.getRight();g=i.getBottom()}else{c=c<i.getLeft()?c:i.getLeft();d=d<i.getTop()?d:i.getTop(); +e=e>i.getRight()?e:i.getRight();g=g>i.getBottom()?g:i.getBottom()}a()};this.inflate=function(i){c-=i;d-=i;e+=i;g+=i;a()};this.minSelf=function(i){c=c>i.getLeft()?c:i.getLeft();d=d>i.getTop()?d:i.getTop();e=e<i.getRight()?e:i.getRight();g=g<i.getBottom()?g:i.getBottom();a()};this.instersects=function(i){return Math.min(e,i.getRight())-Math.max(c,i.getLeft())>=0&&Math.min(g,i.getBottom())-Math.max(d,i.getTop())>=0};this.empty=function(){b=true;g=e=d=c=0;a()};this.isEmpty=function(){return b};this.toString= +function(){return"THREE.Rectangle ( left: "+c+", right: "+e+", top: "+d+", bottom: "+g+", width: "+h+", height: "+o+" )"}};THREE.Matrix3=function(){this.m=[]};THREE.Matrix3.prototype={transpose:function(){var a,c=this.m;a=c[1];c[1]=c[3];c[3]=a;a=c[2];c[2]=c[6];c[6]=a;a=c[5];c[5]=c[7];c[7]=a;return this}}; +THREE.Matrix4=function(a,c,d,e,g,h,o,b,i,k,y,z,u,x,H,J){this.n11=a||1;this.n12=c||0;this.n13=d||0;this.n14=e||0;this.n21=g||0;this.n22=h||1;this.n23=o||0;this.n24=b||0;this.n31=i||0;this.n32=k||0;this.n33=y||1;this.n34=z||0;this.n41=u||0;this.n42=x||0;this.n43=H||0;this.n44=J||1;this.flat=Array(16);this.m33=new THREE.Matrix3}; +THREE.Matrix4.prototype={identity:function(){this.n11=1;this.n21=this.n14=this.n13=this.n12=0;this.n22=1;this.n32=this.n31=this.n24=this.n23=0;this.n33=1;this.n43=this.n42=this.n41=this.n34=0;this.n44=1;return this},set:function(a,c,d,e,g,h,o,b,i,k,y,z,u,x,H,J){this.n11=a;this.n12=c;this.n13=d;this.n14=e;this.n21=g;this.n22=h;this.n23=o;this.n24=b;this.n31=i;this.n32=k;this.n33=y;this.n34=z;this.n41=u;this.n42=x;this.n43=H;this.n44=J;return this},copy:function(a){this.n11=a.n11;this.n12=a.n12;this.n13= +a.n13;this.n14=a.n14;this.n21=a.n21;this.n22=a.n22;this.n23=a.n23;this.n24=a.n24;this.n31=a.n31;this.n32=a.n32;this.n33=a.n33;this.n34=a.n34;this.n41=a.n41;this.n42=a.n42;this.n43=a.n43;this.n44=a.n44;return this},lookAt:function(a,c,d){var e=THREE.Matrix4.__tmpVec1,g=THREE.Matrix4.__tmpVec2,h=THREE.Matrix4.__tmpVec3;h.sub(a,c).normalize();e.cross(d,h).normalize();g.cross(h,e).normalize();this.n11=e.x;this.n12=e.y;this.n13=e.z;this.n14=-e.dot(a);this.n21=g.x;this.n22=g.y;this.n23=g.z;this.n24=-g.dot(a); +this.n31=h.x;this.n32=h.y;this.n33=h.z;this.n34=-h.dot(a);this.n43=this.n42=this.n41=0;this.n44=1;return this},multiplyVector3:function(a){var c=a.x,d=a.y,e=a.z,g=1/(this.n41*c+this.n42*d+this.n43*e+this.n44);a.x=(this.n11*c+this.n12*d+this.n13*e+this.n14)*g;a.y=(this.n21*c+this.n22*d+this.n23*e+this.n24)*g;a.z=(this.n31*c+this.n32*d+this.n33*e+this.n34)*g;return a},multiplyVector4:function(a){var c=a.x,d=a.y,e=a.z,g=a.w;a.x=this.n11*c+this.n12*d+this.n13*e+this.n14*g;a.y=this.n21*c+this.n22*d+this.n23* +e+this.n24*g;a.z=this.n31*c+this.n32*d+this.n33*e+this.n34*g;a.w=this.n41*c+this.n42*d+this.n43*e+this.n44*g;return a},crossVector:function(a){var c=new THREE.Vector4;c.x=this.n11*a.x+this.n12*a.y+this.n13*a.z+this.n14*a.w;c.y=this.n21*a.x+this.n22*a.y+this.n23*a.z+this.n24*a.w;c.z=this.n31*a.x+this.n32*a.y+this.n33*a.z+this.n34*a.w;c.w=a.w?this.n41*a.x+this.n42*a.y+this.n43*a.z+this.n44*a.w:1;return c},multiply:function(a,c){var d=a.n11,e=a.n12,g=a.n13,h=a.n14,o=a.n21,b=a.n22,i=a.n23,k=a.n24,y=a.n31, +z=a.n32,u=a.n33,x=a.n34,H=a.n41,J=a.n42,K=a.n43,p=a.n44,U=c.n11,F=c.n12,f=c.n13,j=c.n14,q=c.n21,l=c.n22,r=c.n23,C=c.n24,m=c.n31,t=c.n32,v=c.n33,s=c.n34,n=c.n41,E=c.n42,A=c.n43,O=c.n44;this.n11=d*U+e*q+g*m+h*n;this.n12=d*F+e*l+g*t+h*E;this.n13=d*f+e*r+g*v+h*A;this.n14=d*j+e*C+g*s+h*O;this.n21=o*U+b*q+i*m+k*n;this.n22=o*F+b*l+i*t+k*E;this.n23=o*f+b*r+i*v+k*A;this.n24=o*j+b*C+i*s+k*O;this.n31=y*U+z*q+u*m+x*n;this.n32=y*F+z*l+u*t+x*E;this.n33=y*f+z*r+u*v+x*A;this.n34=y*j+z*C+u*s+x*O;this.n41=H*U+J*q+ +K*m+p*n;this.n42=H*F+J*l+K*t+p*E;this.n43=H*f+J*r+K*v+p*A;this.n44=H*j+J*C+K*s+p*O;return this},multiplySelf:function(a){var c=this.n11,d=this.n12,e=this.n13,g=this.n14,h=this.n21,o=this.n22,b=this.n23,i=this.n24,k=this.n31,y=this.n32,z=this.n33,u=this.n34,x=this.n41,H=this.n42,J=this.n43,K=this.n44,p=a.n11,U=a.n21,F=a.n31,f=a.n41,j=a.n12,q=a.n22,l=a.n32,r=a.n42,C=a.n13,m=a.n23,t=a.n33,v=a.n43,s=a.n14,n=a.n24,E=a.n34;a=a.n44;this.n11=c*p+d*U+e*F+g*f;this.n12=c*j+d*q+e*l+g*r;this.n13=c*C+d*m+e*t+g* +v;this.n14=c*s+d*n+e*E+g*a;this.n21=h*p+o*U+b*F+i*f;this.n22=h*j+o*q+b*l+i*r;this.n23=h*C+o*m+b*t+i*v;this.n24=h*s+o*n+b*E+i*a;this.n31=k*p+y*U+z*F+u*f;this.n32=k*j+y*q+z*l+u*r;this.n33=k*C+y*m+z*t+u*v;this.n34=k*s+y*n+z*E+u*a;this.n41=x*p+H*U+J*F+K*f;this.n42=x*j+H*q+J*l+K*r;this.n43=x*C+H*m+J*t+K*v;this.n44=x*s+H*n+J*E+K*a;return this},multiplyScalar:function(a){this.n11*=a;this.n12*=a;this.n13*=a;this.n14*=a;this.n21*=a;this.n22*=a;this.n23*=a;this.n24*=a;this.n31*=a;this.n32*=a;this.n33*=a;this.n34*= +a;this.n41*=a;this.n42*=a;this.n43*=a;this.n44*=a;return this},determinant:function(){var a=this.n11,c=this.n12,d=this.n13,e=this.n14,g=this.n21,h=this.n22,o=this.n23,b=this.n24,i=this.n31,k=this.n32,y=this.n33,z=this.n34,u=this.n41,x=this.n42,H=this.n43,J=this.n44;return e*o*k*u-d*b*k*u-e*h*y*u+c*b*y*u+d*h*z*u-c*o*z*u-e*o*i*x+d*b*i*x+e*g*y*x-a*b*y*x-d*g*z*x+a*o*z*x+e*h*i*H-c*b*i*H-e*g*k*H+a*b*k*H+c*g*z*H-a*h*z*H-d*h*i*J+c*o*i*J+d*g*k*J-a*o*k*J-c*g*y*J+a*h*y*J},transpose:function(){function a(c,d, +e){var g=c[d];c[d]=c[e];c[e]=g}a(this,"n21","n12");a(this,"n31","n13");a(this,"n32","n23");a(this,"n41","n14");a(this,"n42","n24");a(this,"n43","n34");return this},clone:function(){var a=new THREE.Matrix4;a.n11=this.n11;a.n12=this.n12;a.n13=this.n13;a.n14=this.n14;a.n21=this.n21;a.n22=this.n22;a.n23=this.n23;a.n24=this.n24;a.n31=this.n31;a.n32=this.n32;a.n33=this.n33;a.n34=this.n34;a.n41=this.n41;a.n42=this.n42;a.n43=this.n43;a.n44=this.n44;return a},flatten:function(){var a=this.flat;a[0]=this.n11; +a[1]=this.n21;a[2]=this.n31;a[3]=this.n41;a[4]=this.n12;a[5]=this.n22;a[6]=this.n32;a[7]=this.n42;a[8]=this.n13;a[9]=this.n23;a[10]=this.n33;a[11]=this.n43;a[12]=this.n14;a[13]=this.n24;a[14]=this.n34;a[15]=this.n44;return a},setTranslation:function(a,c,d){this.set(1,0,0,a,0,1,0,c,0,0,1,d,0,0,0,1);return this},setScale:function(a,c,d){this.set(a,0,0,0,0,c,0,0,0,0,d,0,0,0,0,1);return this},setRotX:function(a){var c=Math.cos(a);a=Math.sin(a);this.set(1,0,0,0,0,c,-a,0,0,a,c,0,0,0,0,1);return this},setRotY:function(a){var c= +Math.cos(a);a=Math.sin(a);this.set(c,0,a,0,0,1,0,0,-a,0,c,0,0,0,0,1);return this},setRotZ:function(a){var c=Math.cos(a);a=Math.sin(a);this.set(c,-a,0,0,a,c,0,0,0,0,1,0,0,0,0,1);return this},setRotAxis:function(a,c){var d=Math.cos(c),e=Math.sin(c),g=1-d,h=a.x,o=a.y,b=a.z,i=g*h,k=g*o;this.set(i*h+d,i*o-e*b,i*b+e*o,0,i*o+e*b,k*o+d,k*b-e*h,0,i*b-e*o,k*b+e*h,g*b*b+d,0,0,0,0,1);return this},toString:function(){return"| "+this.n11+" "+this.n12+" "+this.n13+" "+this.n14+" |\n| "+this.n21+" "+this.n22+" "+ +this.n23+" "+this.n24+" |\n| "+this.n31+" "+this.n32+" "+this.n33+" "+this.n34+" |\n| "+this.n41+" "+this.n42+" "+this.n43+" "+this.n44+" |"}};THREE.Matrix4.translationMatrix=function(a,c,d){var e=new THREE.Matrix4;e.setTranslation(a,c,d);return e};THREE.Matrix4.scaleMatrix=function(a,c,d){var e=new THREE.Matrix4;e.setScale(a,c,d);return e};THREE.Matrix4.rotationXMatrix=function(a){var c=new THREE.Matrix4;c.setRotX(a);return c}; +THREE.Matrix4.rotationYMatrix=function(a){var c=new THREE.Matrix4;c.setRotY(a);return c};THREE.Matrix4.rotationZMatrix=function(a){var c=new THREE.Matrix4;c.setRotZ(a);return c};THREE.Matrix4.rotationAxisAngleMatrix=function(a,c){var d=new THREE.Matrix4;d.setRotAxis(a,c);return d}; +THREE.Matrix4.makeInvert=function(a){var c=a.n11,d=a.n12,e=a.n13,g=a.n14,h=a.n21,o=a.n22,b=a.n23,i=a.n24,k=a.n31,y=a.n32,z=a.n33,u=a.n34,x=a.n41,H=a.n42,J=a.n43,K=a.n44,p=new THREE.Matrix4;p.n11=b*u*H-i*z*H+i*y*J-o*u*J-b*y*K+o*z*K;p.n12=g*z*H-e*u*H-g*y*J+d*u*J+e*y*K-d*z*K;p.n13=e*i*H-g*b*H+g*o*J-d*i*J-e*o*K+d*b*K;p.n14=g*b*y-e*i*y-g*o*z+d*i*z+e*o*u-d*b*u;p.n21=i*z*x-b*u*x-i*k*J+h*u*J+b*k*K-h*z*K;p.n22=e*u*x-g*z*x+g*k*J-c*u*J-e*k*K+c*z*K;p.n23=g*b*x-e*i*x-g*h*J+c*i*J+e*h*K-c*b*K;p.n24=e*i*k-g*b*k+ +g*h*z-c*i*z-e*h*u+c*b*u;p.n31=o*u*x-i*y*x+i*k*H-h*u*H-o*k*K+h*y*K;p.n32=g*y*x-d*u*x-g*k*H+c*u*H+d*k*K-c*y*K;p.n33=e*i*x-g*o*x+g*h*H-c*i*H-d*h*K+c*o*K;p.n34=g*o*k-d*i*k-g*h*y+c*i*y+d*h*u-c*o*u;p.n41=b*y*x-o*z*x-b*k*H+h*z*H+o*k*J-h*y*J;p.n42=d*z*x-e*y*x+e*k*H-c*z*H-d*k*J+c*y*J;p.n43=e*o*x-d*b*x-e*h*H+c*b*H+d*h*J-c*o*J;p.n44=d*b*k-e*o*k+e*h*y-c*b*y-d*h*z+c*o*z;p.multiplyScalar(1/a.determinant());return p}; +THREE.Matrix4.makeInvert3x3=function(a){var c=a.flatten();a=a.m33;var d=a.m,e=c[10]*c[5]-c[6]*c[9],g=-c[10]*c[1]+c[2]*c[9],h=c[6]*c[1]-c[2]*c[5],o=-c[10]*c[4]+c[6]*c[8],b=c[10]*c[0]-c[2]*c[8],i=-c[6]*c[0]+c[2]*c[4],k=c[9]*c[4]-c[5]*c[8],y=-c[9]*c[0]+c[1]*c[8],z=c[5]*c[0]-c[1]*c[4];c=c[0]*e+c[1]*o+c[2]*k;if(c==0)throw"matrix not invertible";c=1/c;d[0]=c*e;d[1]=c*g;d[2]=c*h;d[3]=c*o;d[4]=c*b;d[5]=c*i;d[6]=c*k;d[7]=c*y;d[8]=c*z;return a}; +THREE.Matrix4.makeFrustum=function(a,c,d,e,g,h){var o,b,i;o=new THREE.Matrix4;b=2*g/(c-a);i=2*g/(e-d);a=(c+a)/(c-a);d=(e+d)/(e-d);e=-(h+g)/(h-g);g=-2*h*g/(h-g);o.n11=b;o.n12=0;o.n13=a;o.n14=0;o.n21=0;o.n22=i;o.n23=d;o.n24=0;o.n31=0;o.n32=0;o.n33=e;o.n34=g;o.n41=0;o.n42=0;o.n43=-1;o.n44=0;return o};THREE.Matrix4.makePerspective=function(a,c,d,e){var g;a=d*Math.tan(a*Math.PI/360);g=-a;return THREE.Matrix4.makeFrustum(g*c,a*c,g,a,d,e)}; +THREE.Matrix4.makeOrtho=function(a,c,d,e,g,h){var o,b,i,k;o=new THREE.Matrix4;b=c-a;i=d-e;k=h-g;a=(c+a)/b;d=(d+e)/i;g=(h+g)/k;o.n11=2/b;o.n12=0;o.n13=0;o.n14=-a;o.n21=0;o.n22=2/i;o.n23=0;o.n24=-d;o.n31=0;o.n32=0;o.n33=-2/k;o.n34=-g;o.n41=0;o.n42=0;o.n43=0;o.n44=1;return o};THREE.Matrix4.__tmpVec1=new THREE.Vector3;THREE.Matrix4.__tmpVec2=new THREE.Vector3;THREE.Matrix4.__tmpVec3=new THREE.Vector3; +THREE.Vertex=function(a,c){this.position=a||new THREE.Vector3;this.positionWorld=new THREE.Vector3;this.positionScreen=new THREE.Vector4;this.normal=c||new THREE.Vector3;this.normalWorld=new THREE.Vector3;this.normalScreen=new THREE.Vector3;this.tangent=new THREE.Vector4;this.__visible=true};THREE.Vertex.prototype={toString:function(){return"THREE.Vertex ( position: "+this.position+", normal: "+this.normal+" )"}}; +THREE.Face3=function(a,c,d,e,g){this.a=a;this.b=c;this.c=d;this.centroid=new THREE.Vector3;this.normal=e instanceof THREE.Vector3?e:new THREE.Vector3;this.vertexNormals=e instanceof Array?e:[];this.materials=g instanceof Array?g:[g]};THREE.Face3.prototype={toString:function(){return"THREE.Face3 ( "+this.a+", "+this.b+", "+this.c+" )"}}; +THREE.Face4=function(a,c,d,e,g,h){this.a=a;this.b=c;this.c=d;this.d=e;this.centroid=new THREE.Vector3;this.normal=g instanceof THREE.Vector3?g:new THREE.Vector3;this.vertexNormals=g instanceof Array?g:[];this.materials=h instanceof Array?h:[h]};THREE.Face4.prototype={toString:function(){return"THREE.Face4 ( "+this.a+", "+this.b+", "+this.c+" "+this.d+" )"}};THREE.UV=function(a,c){this.u=a||0;this.v=c||0}; +THREE.UV.prototype={copy:function(a){this.u=a.u;this.v=a.v},toString:function(){return"THREE.UV ("+this.u+", "+this.v+")"}};THREE.Geometry=function(){this.vertices=[];this.faces=[];this.uvs=[];this.boundingSphere=this.boundingBox=null;this.geometryChunks={};this.hasTangents=false}; +THREE.Geometry.prototype={computeCentroids:function(){var a,c,d;a=0;for(c=this.faces.length;a<c;a++){d=this.faces[a];d.centroid.set(0,0,0);if(d instanceof THREE.Face3){d.centroid.addSelf(this.vertices[d.a].position);d.centroid.addSelf(this.vertices[d.b].position);d.centroid.addSelf(this.vertices[d.c].position);d.centroid.divideScalar(3)}else if(d instanceof THREE.Face4){d.centroid.addSelf(this.vertices[d.a].position);d.centroid.addSelf(this.vertices[d.b].position);d.centroid.addSelf(this.vertices[d.c].position); +d.centroid.addSelf(this.vertices[d.d].position);d.centroid.divideScalar(4)}}},computeFaceNormals:function(a){var c,d,e,g,h,o,b=new THREE.Vector3,i=new THREE.Vector3;e=0;for(g=this.vertices.length;e<g;e++){h=this.vertices[e];h.normal.set(0,0,0)}e=0;for(g=this.faces.length;e<g;e++){h=this.faces[e];if(a&&h.vertexNormals.length){b.set(0,0,0);c=0;for(d=h.normal.length;c<d;c++)b.addSelf(h.vertexNormals[c]);b.divideScalar(3)}else{c=this.vertices[h.a];d=this.vertices[h.b];o=this.vertices[h.c];b.sub(o.position, +d.position);i.sub(c.position,d.position);b.crossSelf(i)}b.isZero()||b.normalize();h.normal.copy(b)}},computeVertexNormals:function(){var a,c,d,e;if(this.__tmpVertices==undefined){e=this.__tmpVertices=Array(this.vertices.length);a=0;for(c=this.vertices.length;a<c;a++)e[a]=new THREE.Vector3;a=0;for(c=this.faces.length;a<c;a++){d=this.faces[a];if(d instanceof THREE.Face3)d.vertexNormals=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];else if(d instanceof THREE.Face4)d.vertexNormals=[new THREE.Vector3, +new THREE.Vector3,new THREE.Vector3,new THREE.Vector3]}}else{e=this.__tmpVertices;a=0;for(c=this.vertices.length;a<c;a++)e[a].set(0,0,0)}a=0;for(c=this.faces.length;a<c;a++){d=this.faces[a];if(d instanceof THREE.Face3){e[d.a].addSelf(d.normal);e[d.b].addSelf(d.normal);e[d.c].addSelf(d.normal)}else if(d instanceof THREE.Face4){e[d.a].addSelf(d.normal);e[d.b].addSelf(d.normal);e[d.c].addSelf(d.normal);e[d.d].addSelf(d.normal)}}a=0;for(c=this.vertices.length;a<c;a++)e[a].normalize();a=0;for(c=this.faces.length;a< +c;a++){d=this.faces[a];if(d instanceof THREE.Face3){d.vertexNormals[0].copy(e[d.a]);d.vertexNormals[1].copy(e[d.b]);d.vertexNormals[2].copy(e[d.c])}else if(d instanceof THREE.Face4){d.vertexNormals[0].copy(e[d.a]);d.vertexNormals[1].copy(e[d.b]);d.vertexNormals[2].copy(e[d.c]);d.vertexNormals[3].copy(e[d.d])}}},computeTangents:function(){function a(s,n,E,A,O,N,G){h=s.vertices[n].position;o=s.vertices[E].position;b=s.vertices[A].position;i=g[O];k=g[N];y=g[G];z=o.x-h.x;u=b.x-h.x;x=o.y-h.y;H=b.y-h.y; +J=o.z-h.z;K=b.z-h.z;p=k.u-i.u;U=y.u-i.u;F=k.v-i.v;f=y.v-i.v;j=1/(p*f-U*F);r.set((f*z-F*u)*j,(f*x-F*H)*j,(f*J-F*K)*j);C.set((p*u-U*z)*j,(p*H-U*x)*j,(p*K-U*J)*j);q[n].addSelf(r);q[E].addSelf(r);q[A].addSelf(r);l[n].addSelf(C);l[E].addSelf(C);l[A].addSelf(C)}var c,d,e,g,h,o,b,i,k,y,z,u,x,H,J,K,p,U,F,f,j,q=[],l=[],r=new THREE.Vector3,C=new THREE.Vector3,m=new THREE.Vector3,t=new THREE.Vector3,v=new THREE.Vector3;c=0;for(d=this.vertices.length;c<d;c++){q[c]=new THREE.Vector3;l[c]=new THREE.Vector3}c=0; +for(d=this.faces.length;c<d;c++){e=this.faces[c];g=this.uvs[c];if(e instanceof THREE.Face3){a(this,e.a,e.b,e.c,0,1,2);this.vertices[e.a].normal.copy(e.vertexNormals[0]);this.vertices[e.b].normal.copy(e.vertexNormals[1]);this.vertices[e.c].normal.copy(e.vertexNormals[2])}else if(e instanceof THREE.Face4){a(this,e.a,e.b,e.c,0,1,2);a(this,e.a,e.b,e.d,0,1,3);this.vertices[e.a].normal.copy(e.vertexNormals[0]);this.vertices[e.b].normal.copy(e.vertexNormals[1]);this.vertices[e.c].normal.copy(e.vertexNormals[2]); +this.vertices[e.d].normal.copy(e.vertexNormals[3])}}c=0;for(d=this.vertices.length;c<d;c++){v.copy(this.vertices[c].normal);e=q[c];m.copy(e);m.subSelf(v.multiplyScalar(v.dot(e))).normalize();t.cross(this.vertices[c].normal,e);e=t.dot(l[c]);e=e<0?-1:1;this.vertices[c].tangent.set(m.x,m.y,m.z,e)}this.hasTangents=true},computeBoundingBox:function(){var a;if(this.vertices.length>0){this.boundingBox={x:[this.vertices[0].position.x,this.vertices[0].position.x],y:[this.vertices[0].position.y,this.vertices[0].position.y], +z:[this.vertices[0].position.z,this.vertices[0].position.z]};for(var c=1,d=this.vertices.length;c<d;c++){a=this.vertices[c];if(a.position.x<this.boundingBox.x[0])this.boundingBox.x[0]=a.position.x;else if(a.position.x>this.boundingBox.x[1])this.boundingBox.x[1]=a.position.x;if(a.position.y<this.boundingBox.y[0])this.boundingBox.y[0]=a.position.y;else if(a.position.y>this.boundingBox.y[1])this.boundingBox.y[1]=a.position.y;if(a.position.z<this.boundingBox.z[0])this.boundingBox.z[0]=a.position.z;else if(a.position.z> +this.boundingBox.z[1])this.boundingBox.z[1]=a.position.z}}},computeBoundingSphere:function(){for(var a=this.boundingSphere===null?0:this.boundingSphere.radius,c=0,d=this.vertices.length;c<d;c++)a=Math.max(a,this.vertices[c].position.length());this.boundingSphere={radius:a}},sortFacesByMaterial:function(){function a(y){var z=[];c=0;for(d=y.length;c<d;c++)y[c]==undefined?z.push("undefined"):z.push(y[c].toString());return z.join("_")}var c,d,e,g,h,o,b,i,k={};e=0;for(g=this.faces.length;e<g;e++){h=this.faces[e]; +o=h.materials;b=a(o);if(k[b]==undefined)k[b]={hash:b,counter:0};i=k[b].hash+"_"+k[b].counter;if(this.geometryChunks[i]==undefined)this.geometryChunks[i]={faces:[],materials:o,vertices:0};h=h instanceof THREE.Face3?3:4;if(this.geometryChunks[i].vertices+h>65535){k[b].counter+=1;i=k[b].hash+"_"+k[b].counter;if(this.geometryChunks[i]==undefined)this.geometryChunks[i]={faces:[],materials:o,vertices:0}}this.geometryChunks[i].faces.push(e);this.geometryChunks[i].vertices+=h}},toString:function(){return"THREE.Geometry ( vertices: "+ +this.vertices+", faces: "+this.faces+", uvs: "+this.uvs+" )"}}; +THREE.Camera=function(a,c,d,e){this.fov=a;this.aspect=c;this.near=d;this.far=e;this.position=new THREE.Vector3;this.target={position:new THREE.Vector3};this.autoUpdateMatrix=true;this.projectionMatrix=null;this.matrix=new THREE.Matrix4;this.up=new THREE.Vector3(0,1,0);this.tmpVec=new THREE.Vector3;this.translateX=function(g){this.tmpVec.sub(this.target.position,this.position).normalize().multiplyScalar(g);this.tmpVec.crossSelf(this.up);this.position.addSelf(this.tmpVec);this.target.position.addSelf(this.tmpVec)}; +this.translateZ=function(g){this.tmpVec.sub(this.target.position,this.position).normalize().multiplyScalar(g);this.position.subSelf(this.tmpVec);this.target.position.subSelf(this.tmpVec)};this.updateMatrix=function(){this.matrix.lookAt(this.position,this.target.position,this.up)};this.updateProjectionMatrix=function(){this.projectionMatrix=THREE.Matrix4.makePerspective(this.fov,this.aspect,this.near,this.far)};this.updateProjectionMatrix()}; +THREE.Camera.prototype={toString:function(){return"THREE.Camera ( "+this.position+", "+this.target.position+" )"}};THREE.Light=function(a){this.color=new THREE.Color(a)};THREE.AmbientLight=function(a){THREE.Light.call(this,a)};THREE.AmbientLight.prototype=new THREE.Light;THREE.AmbientLight.prototype.constructor=THREE.AmbientLight;THREE.DirectionalLight=function(a,c){THREE.Light.call(this,a);this.position=new THREE.Vector3(0,1,0);this.intensity=c||1};THREE.DirectionalLight.prototype=new THREE.Light; +THREE.DirectionalLight.prototype.constructor=THREE.DirectionalLight;THREE.PointLight=function(a,c){THREE.Light.call(this,a);this.position=new THREE.Vector3;this.intensity=c||1};THREE.PointLight.prototype=new THREE.Light;THREE.PointLight.prototype.constructor=THREE.PointLight; +THREE.Object3D=function(){this.id=THREE.Object3DCounter.value++;this.position=new THREE.Vector3;this.rotation=new THREE.Vector3;this.scale=new THREE.Vector3(1,1,1);this.matrix=new THREE.Matrix4;this.rotationMatrix=new THREE.Matrix4;this.tmpMatrix=new THREE.Matrix4;this.screen=new THREE.Vector3;this.visible=this.autoUpdateMatrix=true}; +THREE.Object3D.prototype={updateMatrix:function(){var a=this.position,c=this.rotation,d=this.scale,e=this.tmpMatrix;this.matrix.setTranslation(a.x,a.y,a.z);this.rotationMatrix.setRotX(c.x);if(c.y!=0){e.setRotY(c.y);this.rotationMatrix.multiplySelf(e)}if(c.z!=0){e.setRotZ(c.z);this.rotationMatrix.multiplySelf(e)}this.matrix.multiplySelf(this.rotationMatrix);if(d.x!=0||d.y!=0||d.z!=0){e.setScale(d.x,d.y,d.z);this.matrix.multiplySelf(e)}}};THREE.Object3DCounter={value:0}; +THREE.Particle=function(a){THREE.Object3D.call(this);this.materials=a instanceof Array?a:[a];this.autoUpdateMatrix=false};THREE.Particle.prototype=new THREE.Object3D;THREE.Particle.prototype.constructor=THREE.Particle;THREE.ParticleSystem=function(a,c){THREE.Object3D.call(this);this.geometry=a;this.materials=c instanceof Array?c:[c];this.autoUpdateMatrix=false};THREE.ParticleSystem.prototype=new THREE.Object3D;THREE.ParticleSystem.prototype.constructor=THREE.ParticleSystem; +THREE.Line=function(a,c,d){THREE.Object3D.call(this);this.geometry=a;this.materials=c instanceof Array?c:[c];this.type=d!=undefined?d:THREE.LineStrip};THREE.LineStrip=0;THREE.LinePieces=1;THREE.Line.prototype=new THREE.Object3D;THREE.Line.prototype.constructor=THREE.Line;THREE.Mesh=function(a,c){THREE.Object3D.call(this);this.geometry=a;this.materials=c instanceof Array?c:[c];this.overdraw=this.doubleSided=this.flipSided=false;this.geometry.boundingSphere||this.geometry.computeBoundingSphere()}; +THREE.Mesh.prototype=new THREE.Object3D;THREE.Mesh.prototype.constructor=THREE.Mesh;THREE.FlatShading=0;THREE.SmoothShading=1;THREE.NormalBlending=0;THREE.AdditiveBlending=1;THREE.SubtractiveBlending=2; +THREE.LineBasicMaterial=function(a){this.color=new THREE.Color(16777215);this.opacity=1;this.blending=THREE.NormalBlending;this.linewidth=1;this.linejoin=this.linecap="round";if(a){a.color!==undefined&&this.color.setHex(a.color);if(a.opacity!==undefined)this.opacity=a.opacity;if(a.blending!==undefined)this.blending=a.blending;if(a.linewidth!==undefined)this.linewidth=a.linewidth;if(a.linecap!==undefined)this.linecap=a.linecap;if(a.linejoin!==undefined)this.linejoin=a.linejoin}}; +THREE.LineBasicMaterial.prototype={toString:function(){return"THREE.LineBasicMaterial (<br/>color: "+this.color+"<br/>opacity: "+this.opacity+"<br/>blending: "+this.blending+"<br/>linewidth: "+this.linewidth+"<br/>linecap: "+this.linecap+"<br/>linejoin: "+this.linejoin+"<br/>)"}}; +THREE.MeshBasicMaterial=function(a){this.id=THREE.MeshBasicMaterialCounter.value++;this.color=new THREE.Color(16777215);this.env_map=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refraction_ratio=0.98;this.fog=true;this.opacity=1;this.shading=THREE.SmoothShading;this.blending=THREE.NormalBlending;this.wireframe=false;this.wireframe_linewidth=1;this.wireframe_linejoin=this.wireframe_linecap="round";if(a){a.color!==undefined&&this.color.setHex(a.color);if(a.map!==undefined)this.map= +a.map;if(a.env_map!==undefined)this.env_map=a.env_map;if(a.combine!==undefined)this.combine=a.combine;if(a.reflectivity!==undefined)this.reflectivity=a.reflectivity;if(a.refraction_ratio!==undefined)this.refraction_ratio=a.refraction_ratio;if(a.fog!==undefined)this.fog=a.fog;if(a.opacity!==undefined)this.opacity=a.opacity;if(a.shading!==undefined)this.shading=a.shading;if(a.blending!==undefined)this.blending=a.blending;if(a.wireframe!==undefined)this.wireframe=a.wireframe;if(a.wireframe_linewidth!== +undefined)this.wireframe_linewidth=a.wireframe_linewidth;if(a.wireframe_linecap!==undefined)this.wireframe_linecap=a.wireframe_linecap;if(a.wireframe_linejoin!==undefined)this.wireframe_linejoin=a.wireframe_linejoin}}; +THREE.MeshBasicMaterial.prototype={toString:function(){return"THREE.MeshBasicMaterial (<br/>id: "+this.id+"<br/>color: "+this.color+"<br/>map: "+this.map+"<br/>env_map: "+this.env_map+"<br/>combine: "+this.combine+"<br/>reflectivity: "+this.reflectivity+"<br/>refraction_ratio: "+this.refraction_ratio+"<br/>opacity: "+this.opacity+"<br/>blending: "+this.blending+"<br/>wireframe: "+this.wireframe+"<br/>wireframe_linewidth: "+this.wireframe_linewidth+"<br/>wireframe_linecap: "+this.wireframe_linecap+ +"<br/>wireframe_linejoin: "+this.wireframe_linejoin+"<br/>)"}};THREE.MeshBasicMaterialCounter={value:0}; +THREE.MeshLambertMaterial=function(a){this.id=THREE.MeshLambertMaterialCounter.value++;this.color=new THREE.Color(16777215);this.env_map=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refraction_ratio=0.98;this.fog=true;this.opacity=1;this.shading=THREE.SmoothShading;this.blending=THREE.NormalBlending;this.wireframe=false;this.wireframe_linewidth=1;this.wireframe_linejoin=this.wireframe_linecap="round";if(a){a.color!==undefined&&this.color.setHex(a.color);if(a.map!==undefined)this.map= +a.map;if(a.env_map!==undefined)this.env_map=a.env_map;if(a.combine!==undefined)this.combine=a.combine;if(a.reflectivity!==undefined)this.reflectivity=a.reflectivity;if(a.refraction_ratio!==undefined)this.refraction_ratio=a.refraction_ratio;if(a.fog!==undefined)this.fog=a.fog;if(a.opacity!==undefined)this.opacity=a.opacity;if(a.shading!==undefined)this.shading=a.shading;if(a.blending!==undefined)this.blending=a.blending;if(a.wireframe!==undefined)this.wireframe=a.wireframe;if(a.wireframe_linewidth!== +undefined)this.wireframe_linewidth=a.wireframe_linewidth;if(a.wireframe_linecap!==undefined)this.wireframe_linecap=a.wireframe_linecap;if(a.wireframe_linejoin!==undefined)this.wireframe_linejoin=a.wireframe_linejoin}}; +THREE.MeshLambertMaterial.prototype={toString:function(){return"THREE.MeshLambertMaterial (<br/>id: "+this.id+"<br/>color: "+this.color+"<br/>map: "+this.map+"<br/>env_map: "+this.env_map+"<br/>combine: "+this.combine+"<br/>reflectivity: "+this.reflectivity+"<br/>refraction_ratio: "+this.refraction_ratio+"<br/>opacity: "+this.opacity+"<br/>shading: "+this.shading+"<br/>blending: "+this.blending+"<br/>wireframe: "+this.wireframe+"<br/>wireframe_linewidth: "+this.wireframe_linewidth+"<br/>wireframe_linecap: "+ +this.wireframe_linecap+"<br/>wireframe_linejoin: "+this.wireframe_linejoin+"<br/> )"}};THREE.MeshLambertMaterialCounter={value:0}; +THREE.MeshPhongMaterial=function(a){this.id=THREE.MeshPhongMaterialCounter.value++;this.color=new THREE.Color(16777215);this.ambient=new THREE.Color(328965);this.specular=new THREE.Color(1118481);this.shininess=30;this.env_map=this.specular_map=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refraction_ratio=0.98;this.fog=true;this.opacity=1;this.shading=THREE.SmoothShading;this.blending=THREE.NormalBlending;this.wireframe=false;this.wireframe_linewidth=1;this.wireframe_linejoin= +this.wireframe_linecap="round";if(a){if(a.color!==undefined)this.color=new THREE.Color(a.color);if(a.ambient!==undefined)this.ambient=new THREE.Color(a.ambient);if(a.specular!==undefined)this.specular=new THREE.Color(a.specular);if(a.shininess!==undefined)this.shininess=a.shininess;if(a.map!==undefined)this.map=a.map;if(a.specular_map!==undefined)this.specular_map=a.specular_map;if(a.env_map!==undefined)this.env_map=a.env_map;if(a.combine!==undefined)this.combine=a.combine;if(a.reflectivity!==undefined)this.reflectivity= +a.reflectivity;if(a.refraction_ratio!==undefined)this.refraction_ratio=a.refraction_ratio;if(a.fog!==undefined)this.fog=a.fog;if(a.opacity!==undefined)this.opacity=a.opacity;if(a.shading!==undefined)this.shading=a.shading;if(a.blending!==undefined)this.blending=a.blending;if(a.wireframe!==undefined)this.wireframe=a.wireframe;if(a.wireframe_linewidth!==undefined)this.wireframe_linewidth=a.wireframe_linewidth;if(a.wireframe_linecap!==undefined)this.wireframe_linecap=a.wireframe_linecap;if(a.wireframe_linejoin!== +undefined)this.wireframe_linejoin=a.wireframe_linejoin}}; +THREE.MeshPhongMaterial.prototype={toString:function(){return"THREE.MeshPhongMaterial (<br/>id: "+this.id+"<br/>color: "+this.color+"<br/>ambient: "+this.ambient+"<br/>specular: "+this.specular+"<br/>shininess: "+this.shininess+"<br/>map: "+this.map+"<br/>specular_map: "+this.specular_map+"<br/>env_map: "+this.env_map+"<br/>combine: "+this.combine+"<br/>reflectivity: "+this.reflectivity+"<br/>refraction_ratio: "+this.refraction_ratio+"<br/>opacity: "+this.opacity+"<br/>shading: "+this.shading+"<br/>wireframe: "+ +this.wireframe+"<br/>wireframe_linewidth: "+this.wireframe_linewidth+"<br/>wireframe_linecap: "+this.wireframe_linecap+"<br/>wireframe_linejoin: "+this.wireframe_linejoin+"<br/>)"}};THREE.MeshPhongMaterialCounter={value:0}; +THREE.MeshDepthMaterial=function(a){this.opacity=1;this.shading=THREE.SmoothShading;this.blending=THREE.NormalBlending;this.wireframe=false;this.wireframe_linewidth=1;this.wireframe_linejoin=this.wireframe_linecap="round";if(a){if(a.opacity!==undefined)this.opacity=a.opacity;if(a.blending!==undefined)this.blending=a.blending}};THREE.MeshDepthMaterial.prototype={toString:function(){return"THREE.MeshDepthMaterial"}}; +THREE.MeshNormalMaterial=function(a){this.opacity=1;this.shading=THREE.FlatShading;this.blending=THREE.NormalBlending;if(a){if(a.opacity!==undefined)this.opacity=a.opacity;if(a.shading!==undefined)this.shading=a.shading;if(a.blending!==undefined)this.blending=a.blending}};THREE.MeshNormalMaterial.prototype={toString:function(){return"THREE.MeshNormalMaterial"}};THREE.MeshFaceMaterial=function(){};THREE.MeshFaceMaterial.prototype={toString:function(){return"THREE.MeshFaceMaterial"}}; +THREE.MeshShaderMaterial=function(a){this.id=THREE.MeshShaderMaterialCounter.value++;this.vertex_shader=this.fragment_shader="void main() {}";this.uniforms={};this.opacity=1;this.shading=THREE.SmoothShading;this.blending=THREE.NormalBlending;this.wireframe=false;this.wireframe_linewidth=1;this.wireframe_linejoin=this.wireframe_linecap="round";if(a){if(a.fragment_shader!==undefined)this.fragment_shader=a.fragment_shader;if(a.vertex_shader!==undefined)this.vertex_shader=a.vertex_shader;if(a.uniforms!== +undefined)this.uniforms=a.uniforms;if(a.shading!==undefined)this.shading=a.shading;if(a.blending!==undefined)this.blending=a.blending;if(a.wireframe!==undefined)this.wireframe=a.wireframe;if(a.wireframe_linewidth!==undefined)this.wireframe_linewidth=a.wireframe_linewidth;if(a.wireframe_linecap!==undefined)this.wireframe_linecap=a.wireframe_linecap;if(a.wireframe_linejoin!==undefined)this.wireframe_linejoin=a.wireframe_linejoin}}; +THREE.MeshShaderMaterial.prototype={toString:function(){return"THREE.MeshShaderMaterial (<br/>id: "+this.id+"<br/>blending: "+this.blending+"<br/>wireframe: "+this.wireframe+"<br/>wireframe_linewidth: "+this.wireframe_linewidth+"<br/>wireframe_linecap: "+this.wireframe_linecap+"<br/>wireframe_linejoin: "+this.wireframe_linejoin+"<br/>)"}};THREE.MeshShaderMaterialCounter={value:0}; +THREE.ParticleBasicMaterial=function(a){this.color=new THREE.Color(16777215);this.map=null;this.opacity=1;this.blending=THREE.NormalBlending;this.offset=new THREE.Vector2;if(a){a.color!==undefined&&this.color.setHex(a.color);if(a.map!==undefined)this.map=a.map;if(a.opacity!==undefined)this.opacity=a.opacity;if(a.blending!==undefined)this.blending=a.blending}}; +THREE.ParticleBasicMaterial.prototype={toString:function(){return"THREE.ParticleBasicMaterial (<br/>color: "+this.color+"<br/>map: "+this.map+"<br/>opacity: "+this.opacity+"<br/>blending: "+this.blending+"<br/>)"}};THREE.ParticleCircleMaterial=function(a){this.color=new THREE.Color(16777215);this.opacity=1;this.blending=THREE.NormalBlending;if(a){a.color!==undefined&&this.color.setHex(a.color);if(a.opacity!==undefined)this.opacity=a.opacity;if(a.blending!==undefined)this.blending=a.blending}}; +THREE.ParticleCircleMaterial.prototype={toString:function(){return"THREE.ParticleCircleMaterial (<br/>color: "+this.color+"<br/>opacity: "+this.opacity+"<br/>blending: "+this.blending+"<br/>)"}};THREE.ParticleDOMMaterial=function(a){this.domElement=a};THREE.ParticleDOMMaterial.prototype={toString:function(){return"THREE.ParticleDOMMaterial ( domElement: "+this.domElement+" )"}}; +THREE.Texture=function(a,c,d,e,g,h){this.image=a;this.mapping=c!==undefined?c:new THREE.UVMapping;this.wrap_s=d!==undefined?d:THREE.ClampToEdgeWrapping;this.wrap_t=e!==undefined?e:THREE.ClampToEdgeWrapping;this.mag_filter=g!==undefined?g:THREE.LinearFilter;this.min_filter=h!==undefined?h:THREE.LinearMipMapLinearFilter}; +THREE.Texture.prototype={clone:function(){return new THREE.Texture(this.image,this.mapping,this.wrap_s,this.wrap_t,this.mag_filter,this.min_filter)},toString:function(){return"THREE.Texture (<br/>image: "+this.image+"<br/>wrap_s: "+this.wrap_s+"<br/>wrap_t: "+this.wrap_t+"<br/>mag_filter: "+this.mag_filter+"<br/>min_filter: "+this.min_filter+"<br/>)"}};THREE.MultiplyOperation=0;THREE.MixOperation=1;THREE.RepeatWrapping=0;THREE.ClampToEdgeWrapping=1;THREE.MirroredRepeatWrapping=2; +THREE.NearestFilter=3;THREE.NearestMipMapNearestFilter=4;THREE.NearestMipMapLinearFilter=5;THREE.LinearFilter=6;THREE.LinearMipMapNearestFilter=7;THREE.LinearMipMapLinearFilter=8;THREE.ByteType=9;THREE.UnsignedByteType=10;THREE.ShortType=11;THREE.UnsignedShortType=12;THREE.IntType=13;THREE.UnsignedIntType=14;THREE.FloatType=15;THREE.AlphaFormat=16;THREE.RGBFormat=17;THREE.RGBAFormat=18;THREE.LuminanceFormat=19;THREE.LuminanceAlphaFormat=20; +THREE.RenderTarget=function(a,c,d){this.width=a;this.height=c;d=d||{};this.wrap_s=d.wrap_s!==undefined?d.wrap_s:THREE.ClampToEdgeWrapping;this.wrap_t=d.wrap_t!==undefined?d.wrap_t:THREE.ClampToEdgeWrapping;this.mag_filter=d.mag_filter!==undefined?d.mag_filter:THREE.LinearFilter;this.min_filter=d.min_filter!==undefined?d.min_filter:THREE.LinearMipMapLinearFilter;this.format=d.format!==undefined?d.format:THREE.RGBFormat;this.type=d.type!==undefined?d.type:THREE.UnsignedByteType}; +var Uniforms={clone:function(a){var c,d,e,g={};for(c in a){g[c]={};for(d in a[c]){e=a[c][d];g[c][d]=e instanceof THREE.Color||e instanceof THREE.Vector3||e instanceof THREE.Texture?e.clone():e}}return g},merge:function(a){var c,d,e,g={};for(c=0;c<a.length;c++){e=this.clone(a[c]);for(d in e)g[d]=e[d]}return g}};THREE.CubeReflectionMapping=function(){};THREE.CubeRefractionMapping=function(){};THREE.LatitudeReflectionMapping=function(){};THREE.LatitudeRefractionMapping=function(){}; +THREE.SphericalReflectionMapping=function(){};THREE.SphericalRefractionMapping=function(){};THREE.UVMapping=function(){}; +THREE.Scene=function(){this.objects=[];this.lights=[];this.fog=null;this.addObject=function(a){this.objects.indexOf(a)===-1&&this.objects.push(a)};this.removeObject=function(a){a=this.objects.indexOf(a);a!==-1&&this.objects.splice(a,1)};this.addLight=function(a){this.lights.indexOf(a)===-1&&this.lights.push(a)};this.removeLight=function(a){a=this.lights.indexOf(a);a!==-1&&this.lights.splice(a,1)};this.toString=function(){return"THREE.Scene ( "+this.objects+" )"}}; +THREE.Fog=function(a,c,d){this.color=new THREE.Color(a);this.near=c||1;this.far=d||1E3};THREE.FogExp2=function(a,c){this.color=new THREE.Color(a);this.density=c||2.5E-4}; +THREE.Projector=function(){function a(l,r){return r.z-l.z}function c(l,r){var C=0,m=1,t=l.z+l.w,v=r.z+r.w,s=-l.z+l.w,n=-r.z+r.w;if(t>=0&&v>=0&&s>=0&&n>=0)return true;else if(t<0&&v<0||s<0&&n<0)return false;else{if(t<0)C=Math.max(C,t/(t-v));else if(v<0)m=Math.min(m,t/(t-v));if(s<0)C=Math.max(C,s/(s-n));else if(n<0)m=Math.min(m,s/(s-n));if(m<C)return false;else{l.lerpSelf(r,C);r.lerpSelf(l,1-m);return true}}}var d,e,g=[],h,o,b,i=[],k,y,z=[],u,x,H=[],J=new THREE.Vector4,K=new THREE.Vector4,p=new THREE.Matrix4, +U=new THREE.Matrix4,F=[],f=new THREE.Vector4,j=new THREE.Vector4,q;this.projectObjects=function(l,r,C){var m=[],t,v;e=0;p.multiply(r.projectionMatrix,r.matrix);F[0]=new THREE.Vector4(p.n41-p.n11,p.n42-p.n12,p.n43-p.n13,p.n44-p.n14);F[1]=new THREE.Vector4(p.n41+p.n11,p.n42+p.n12,p.n43+p.n13,p.n44+p.n14);F[2]=new THREE.Vector4(p.n41+p.n21,p.n42+p.n22,p.n43+p.n23,p.n44+p.n24);F[3]=new THREE.Vector4(p.n41-p.n21,p.n42-p.n22,p.n43-p.n23,p.n44-p.n24);F[4]=new THREE.Vector4(p.n41-p.n31,p.n42-p.n32,p.n43- +p.n33,p.n44-p.n34);F[5]=new THREE.Vector4(p.n41+p.n31,p.n42+p.n32,p.n43+p.n33,p.n44+p.n34);r=0;for(t=F.length;r<t;r++){v=F[r];v.divideScalar(Math.sqrt(v.x*v.x+v.y*v.y+v.z*v.z))}t=l.objects;l=0;for(r=t.length;l<r;l++){v=t[l];var s;if(!(s=!v.visible)){if(s=v instanceof THREE.Mesh){a:{s=void 0;for(var n=v.position,E=-v.geometry.boundingSphere.radius*Math.max(v.scale.x,Math.max(v.scale.y,v.scale.z)),A=0;A<6;A++){s=F[A].x*n.x+F[A].y*n.y+F[A].z*n.z+F[A].w;if(s<=E){s=false;break a}}s=true}s=!s}s=s}if(!s){d= +g[e]=g[e]||new THREE.RenderableObject;J.copy(v.position);p.multiplyVector3(J);d.object=v;d.z=J.z;m.push(d);e++}}C&&m.sort(a);return m};this.projectScene=function(l,r,C){var m=[],t=r.near,v=r.far,s,n,E,A,O,N,G,W,P,I,L,V,S,w,M,Q;b=y=x=0;r.autoUpdateMatrix&&r.updateMatrix();p.multiply(r.projectionMatrix,r.matrix);N=this.projectObjects(l,r,true);l=0;for(s=N.length;l<s;l++){G=N[l].object;if(G.visible){G.autoUpdateMatrix&&G.updateMatrix();W=G.matrix;P=G.rotationMatrix;I=G.materials;L=G.overdraw;if(G instanceof +THREE.Mesh){V=G.geometry;S=V.vertices;n=0;for(E=S.length;n<E;n++){w=S[n];w.positionWorld.copy(w.position);W.multiplyVector3(w.positionWorld);A=w.positionScreen;A.copy(w.positionWorld);p.multiplyVector4(A);A.x/=A.w;A.y/=A.w;w.__visible=A.z>t&&A.z<v}V=V.faces;n=0;for(E=V.length;n<E;n++){w=V[n];if(w instanceof THREE.Face3){A=S[w.a];O=S[w.b];M=S[w.c];if(A.__visible&&O.__visible&&M.__visible)if(G.doubleSided||G.flipSided!=(M.positionScreen.x-A.positionScreen.x)*(O.positionScreen.y-A.positionScreen.y)- +(M.positionScreen.y-A.positionScreen.y)*(O.positionScreen.x-A.positionScreen.x)<0){h=i[b]=i[b]||new THREE.RenderableFace3;h.v1.positionWorld.copy(A.positionWorld);h.v2.positionWorld.copy(O.positionWorld);h.v3.positionWorld.copy(M.positionWorld);h.v1.positionScreen.copy(A.positionScreen);h.v2.positionScreen.copy(O.positionScreen);h.v3.positionScreen.copy(M.positionScreen);h.normalWorld.copy(w.normal);P.multiplyVector3(h.normalWorld);h.centroidWorld.copy(w.centroid);W.multiplyVector3(h.centroidWorld); +h.centroidScreen.copy(h.centroidWorld);p.multiplyVector3(h.centroidScreen);M=w.vertexNormals;q=h.vertexNormalsWorld;A=0;for(O=M.length;A<O;A++){Q=q[A]=q[A]||new THREE.Vector3;Q.copy(M[A]);P.multiplyVector3(Q)}h.z=h.centroidScreen.z;h.meshMaterials=I;h.faceMaterials=w.materials;h.overdraw=L;if(G.geometry.uvs[n]){h.uvs[0]=G.geometry.uvs[n][0];h.uvs[1]=G.geometry.uvs[n][1];h.uvs[2]=G.geometry.uvs[n][2]}m.push(h);b++}}else if(w instanceof THREE.Face4){A=S[w.a];O=S[w.b];M=S[w.c];Q=S[w.d];if(A.__visible&& +O.__visible&&M.__visible&&Q.__visible)if(G.doubleSided||G.flipSided!=((Q.positionScreen.x-A.positionScreen.x)*(O.positionScreen.y-A.positionScreen.y)-(Q.positionScreen.y-A.positionScreen.y)*(O.positionScreen.x-A.positionScreen.x)<0||(O.positionScreen.x-M.positionScreen.x)*(Q.positionScreen.y-M.positionScreen.y)-(O.positionScreen.y-M.positionScreen.y)*(Q.positionScreen.x-M.positionScreen.x)<0)){h=i[b]=i[b]||new THREE.RenderableFace3;h.v1.positionWorld.copy(A.positionWorld);h.v2.positionWorld.copy(O.positionWorld); +h.v3.positionWorld.copy(Q.positionWorld);h.v1.positionScreen.copy(A.positionScreen);h.v2.positionScreen.copy(O.positionScreen);h.v3.positionScreen.copy(Q.positionScreen);h.normalWorld.copy(w.normal);P.multiplyVector3(h.normalWorld);h.centroidWorld.copy(w.centroid);W.multiplyVector3(h.centroidWorld);h.centroidScreen.copy(h.centroidWorld);p.multiplyVector3(h.centroidScreen);h.z=h.centroidScreen.z;h.meshMaterials=I;h.faceMaterials=w.materials;h.overdraw=L;if(G.geometry.uvs[n]){h.uvs[0]=G.geometry.uvs[n][0]; +h.uvs[1]=G.geometry.uvs[n][1];h.uvs[2]=G.geometry.uvs[n][3]}m.push(h);b++;o=i[b]=i[b]||new THREE.RenderableFace3;o.v1.positionWorld.copy(O.positionWorld);o.v2.positionWorld.copy(M.positionWorld);o.v3.positionWorld.copy(Q.positionWorld);o.v1.positionScreen.copy(O.positionScreen);o.v2.positionScreen.copy(M.positionScreen);o.v3.positionScreen.copy(Q.positionScreen);o.normalWorld.copy(h.normalWorld);o.centroidWorld.copy(h.centroidWorld);o.centroidScreen.copy(h.centroidScreen);o.z=o.centroidScreen.z;o.meshMaterials= +I;o.faceMaterials=w.materials;o.overdraw=L;if(G.geometry.uvs[n]){o.uvs[0]=G.geometry.uvs[n][1];o.uvs[1]=G.geometry.uvs[n][2];o.uvs[2]=G.geometry.uvs[n][3]}m.push(o);b++}}}}else if(G instanceof THREE.Line){U.multiply(p,W);S=G.geometry.vertices;w=S[0];w.positionScreen.copy(w.position);U.multiplyVector4(w.positionScreen);n=1;for(E=S.length;n<E;n++){A=S[n];A.positionScreen.copy(A.position);U.multiplyVector4(A.positionScreen);O=S[n-1];f.copy(A.positionScreen);j.copy(O.positionScreen);if(c(f,j)){f.multiplyScalar(1/ +f.w);j.multiplyScalar(1/j.w);k=z[y]=z[y]||new THREE.RenderableLine;k.v1.positionScreen.copy(f);k.v2.positionScreen.copy(j);k.z=Math.max(f.z,j.z);k.materials=G.materials;m.push(k);y++}}}else if(G instanceof THREE.Particle){K.set(G.position.x,G.position.y,G.position.z,1);p.multiplyVector4(K);K.z/=K.w;if(K.z>0&&K.z<1){u=H[x]=H[x]||new THREE.RenderableParticle;u.x=K.x/K.w;u.y=K.y/K.w;u.z=K.z;u.rotation=G.rotation.z;u.scale.x=G.scale.x*Math.abs(u.x-(K.x+r.projectionMatrix.n11)/(K.w+r.projectionMatrix.n14)); +u.scale.y=G.scale.y*Math.abs(u.y-(K.y+r.projectionMatrix.n22)/(K.w+r.projectionMatrix.n24));u.materials=G.materials;m.push(u);x++}}}}C&&m.sort(a);return m};this.unprojectVector=function(l,r){var C=THREE.Matrix4.makeInvert(r.matrix);C.multiplySelf(THREE.Matrix4.makeInvert(r.projectionMatrix));C.multiplyVector3(l);return l}}; +THREE.DOMRenderer=function(){THREE.Renderer.call(this);var a=null,c=new THREE.Projector,d,e,g,h;this.domElement=document.createElement("div");this.setSize=function(o,b){d=o;e=b;g=d/2;h=e/2};this.render=function(o,b){var i,k,y,z,u,x,H,J;a=c.projectScene(o,b);i=0;for(k=a.length;i<k;i++){u=a[i];if(u instanceof THREE.RenderableParticle){H=u.x*g+g;J=u.y*h+h;y=0;for(z=u.material.length;y<z;y++){x=u.material[y];if(x instanceof THREE.ParticleDOMMaterial){x=x.domElement;x.style.left=H+"px";x.style.top=J+"px"}}}}}}; +THREE.CanvasRenderer=function(){function a(ea){if(u!=ea)k.globalAlpha=u=ea}function c(ea){if(x!=ea){switch(ea){case THREE.NormalBlending:k.globalCompositeOperation="source-over";break;case THREE.AdditiveBlending:k.globalCompositeOperation="lighter";break;case THREE.SubtractiveBlending:k.globalCompositeOperation="darker"}x=ea}}var d=null,e=new THREE.Projector,g=document.createElement("canvas"),h,o,b,i,k=g.getContext("2d"),y=new THREE.Color(0),z=0,u=1,x=0,H=null,J=null,K=1,p,U,F,f,j,q,l,r,C,m=new THREE.Color, +t=new THREE.Color,v=new THREE.Color,s=new THREE.Color,n=new THREE.Color,E,A,O,N,G,W,P,I,L,V=new THREE.Rectangle,S=new THREE.Rectangle,w=new THREE.Rectangle,M=false,Q=new THREE.Color,da=new THREE.Color,ba=new THREE.Color,Z=new THREE.Color,ja=Math.PI*2,Y=new THREE.Vector3,qa,ka,fa,ha,sa,ua,va=16;qa=document.createElement("canvas");qa.width=qa.height=2;ka=qa.getContext("2d");ka.fillStyle="rgba(0,0,0,1)";ka.fillRect(0,0,2,2);fa=ka.getImageData(0,0,2,2);ha=fa.data;sa=document.createElement("canvas");sa.width= +sa.height=va;ua=sa.getContext("2d");ua.translate(-va/2,-va/2);ua.scale(va,va);va--;this.domElement=g;this.sortElements=this.sortObjects=this.autoClear=true;this.setSize=function(ea,ra){h=ea;o=ra;b=h/2;i=o/2;g.width=h;g.height=o;V.set(-b,-i,b,i);u=1;x=0;J=H=null;K=1};this.setClearColor=function(ea,ra){y.setHex(ea);z=ra;S.set(-b,-i,b,i);k.setTransform(1,0,0,-1,b,i);this.clear()};this.clear=function(){if(!S.isEmpty()){S.inflate(1);S.minSelf(V);if(y.hex==0&&z==0)k.clearRect(S.getX(),S.getY(),S.getWidth(), +S.getHeight());else{c(THREE.NormalBlending);a(1);k.fillStyle="rgba("+Math.floor(y.r*255)+","+Math.floor(y.g*255)+","+Math.floor(y.b*255)+","+z+")";k.fillRect(S.getX(),S.getY(),S.getWidth(),S.getHeight())}S.empty()}};this.render=function(ea,ra){function Ma(B){var X,T,D,R=B.lights;da.setRGB(0,0,0);ba.setRGB(0,0,0);Z.setRGB(0,0,0);B=0;for(X=R.length;B<X;B++){T=R[B];D=T.color;if(T instanceof THREE.AmbientLight){da.r+=D.r;da.g+=D.g;da.b+=D.b}else if(T instanceof THREE.DirectionalLight){ba.r+=D.r;ba.g+= +D.g;ba.b+=D.b}else if(T instanceof THREE.PointLight){Z.r+=D.r;Z.g+=D.g;Z.b+=D.b}}}function Aa(B,X,T,D){var R,$,ca,ga,ia=B.lights;B=0;for(R=ia.length;B<R;B++){$=ia[B];ca=$.color;ga=$.intensity;if($ instanceof THREE.DirectionalLight){$=T.dot($.position)*ga;if($>0){D.r+=ca.r*$;D.g+=ca.g*$;D.b+=ca.b*$}}else if($ instanceof THREE.PointLight){Y.sub($.position,X);Y.normalize();$=T.dot(Y)*ga;if($>0){D.r+=ca.r*$;D.g+=ca.g*$;D.b+=ca.b*$}}}}function Na(B,X,T){if(T.opacity!=0){a(T.opacity);c(T.blending);var D, +R,$,ca,ga,ia;if(T instanceof THREE.ParticleBasicMaterial){if(T.map){ca=T.map;ga=ca.width>>1;ia=ca.height>>1;R=X.scale.x*b;$=X.scale.y*i;T=R*ga;D=$*ia;w.set(B.x-T,B.y-D,B.x+T,B.y+D);if(V.instersects(w)){k.save();k.translate(B.x,B.y);k.rotate(-X.rotation);k.scale(R,-$);k.translate(-ga,-ia);k.drawImage(ca,0,0);k.restore()}}}else if(T instanceof THREE.ParticleCircleMaterial){if(M){Q.r=da.r+ba.r+Z.r;Q.g=da.g+ba.g+Z.g;Q.b=da.b+ba.b+Z.b;m.r=T.color.r*Q.r;m.g=T.color.g*Q.g;m.b=T.color.b*Q.b;m.updateStyleString()}else m.__styleString= +T.color.__styleString;T=X.scale.x*b;D=X.scale.y*i;w.set(B.x-T,B.y-D,B.x+T,B.y+D);if(V.instersects(w)){R=m.__styleString;if(J!=R)k.fillStyle=J=R;k.save();k.translate(B.x,B.y);k.rotate(-X.rotation);k.scale(T,D);k.beginPath();k.arc(0,0,1,0,ja,true);k.closePath();k.fill();k.restore()}}}}function Oa(B,X,T,D){if(D.opacity!=0){a(D.opacity);c(D.blending);k.beginPath();k.moveTo(B.positionScreen.x,B.positionScreen.y);k.lineTo(X.positionScreen.x,X.positionScreen.y);k.closePath();if(D instanceof THREE.LineBasicMaterial){m.__styleString= +D.color.__styleString;B=D.linewidth;if(K!=B)k.lineWidth=K=B;B=m.__styleString;if(H!=B)k.strokeStyle=H=B;k.stroke();w.inflate(D.linewidth*2)}}}function Ia(B,X,T,D,R,$){if(R.opacity!=0){a(R.opacity);c(R.blending);f=B.positionScreen.x;j=B.positionScreen.y;q=X.positionScreen.x;l=X.positionScreen.y;r=T.positionScreen.x;C=T.positionScreen.y;k.beginPath();k.moveTo(f,j);k.lineTo(q,l);k.lineTo(r,C);k.lineTo(f,j);k.closePath();if(R instanceof THREE.MeshBasicMaterial)if(R.map)R.map.image.loaded&&R.map.mapping instanceof +THREE.UVMapping&&xa(f,j,q,l,r,C,R.map.image,D.uvs[0].u,D.uvs[0].v,D.uvs[1].u,D.uvs[1].v,D.uvs[2].u,D.uvs[2].v);else if(R.env_map){if(R.env_map.image.loaded)if(R.env_map.mapping instanceof THREE.SphericalReflectionMapping){B=ra.matrix;Y.copy(D.vertexNormalsWorld[0]);N=(Y.x*B.n11+Y.y*B.n12+Y.z*B.n13)*0.5+0.5;G=-(Y.x*B.n21+Y.y*B.n22+Y.z*B.n23)*0.5+0.5;Y.copy(D.vertexNormalsWorld[1]);W=(Y.x*B.n11+Y.y*B.n12+Y.z*B.n13)*0.5+0.5;P=-(Y.x*B.n21+Y.y*B.n22+Y.z*B.n23)*0.5+0.5;Y.copy(D.vertexNormalsWorld[2]);I= +(Y.x*B.n11+Y.y*B.n12+Y.z*B.n13)*0.5+0.5;L=-(Y.x*B.n21+Y.y*B.n22+Y.z*B.n23)*0.5+0.5;xa(f,j,q,l,r,C,R.env_map.image,N,G,W,P,I,L)}}else R.wireframe?Ba(R.color.__styleString,R.wireframe_linewidth):Ca(R.color.__styleString);else if(R instanceof THREE.MeshLambertMaterial){if(R.map&&!R.wireframe){R.map.mapping instanceof THREE.UVMapping&&xa(f,j,q,l,r,C,R.map.image,D.uvs[0].u,D.uvs[0].v,D.uvs[1].u,D.uvs[1].v,D.uvs[2].u,D.uvs[2].v);c(THREE.SubtractiveBlending)}if(M)if(!R.wireframe&&R.shading==THREE.SmoothShading&& +D.vertexNormalsWorld.length==3){t.r=v.r=s.r=da.r;t.g=v.g=s.g=da.g;t.b=v.b=s.b=da.b;Aa($,D.v1.positionWorld,D.vertexNormalsWorld[0],t);Aa($,D.v2.positionWorld,D.vertexNormalsWorld[1],v);Aa($,D.v3.positionWorld,D.vertexNormalsWorld[2],s);n.r=(v.r+s.r)*0.5;n.g=(v.g+s.g)*0.5;n.b=(v.b+s.b)*0.5;O=Ja(t,v,s,n);xa(f,j,q,l,r,C,O,0,0,1,0,0,1)}else{Q.r=da.r;Q.g=da.g;Q.b=da.b;Aa($,D.centroidWorld,D.normalWorld,Q);m.r=R.color.r*Q.r;m.g=R.color.g*Q.g;m.b=R.color.b*Q.b;m.updateStyleString();R.wireframe?Ba(m.__styleString, +R.wireframe_linewidth):Ca(m.__styleString)}else R.wireframe?Ba(R.color.__styleString,R.wireframe_linewidth):Ca(R.color.__styleString)}else if(R instanceof THREE.MeshDepthMaterial){E=ra.near;A=ra.far;t.r=t.g=t.b=1-Ea(B.positionScreen.z,E,A);v.r=v.g=v.b=1-Ea(X.positionScreen.z,E,A);s.r=s.g=s.b=1-Ea(T.positionScreen.z,E,A);n.r=(v.r+s.r)*0.5;n.g=(v.g+s.g)*0.5;n.b=(v.b+s.b)*0.5;O=Ja(t,v,s,n);xa(f,j,q,l,r,C,O,0,0,1,0,0,1)}else if(R instanceof THREE.MeshNormalMaterial){m.r=Fa(D.normalWorld.x);m.g=Fa(D.normalWorld.y); +m.b=Fa(D.normalWorld.z);m.updateStyleString();R.wireframe?Ba(m.__styleString,R.wireframe_linewidth):Ca(m.__styleString)}}}function Ba(B,X){if(H!=B)k.strokeStyle=H=B;if(K!=X)k.lineWidth=K=X;k.stroke();w.inflate(X*2)}function Ca(B){if(J!=B)k.fillStyle=J=B;k.fill()}function xa(B,X,T,D,R,$,ca,ga,ia,na,la,oa,ya){var ta,pa;ta=ca.width-1;pa=ca.height-1;ga*=ta;ia*=pa;na*=ta;la*=pa;oa*=ta;ya*=pa;T-=B;D-=X;R-=B;$-=X;na-=ga;la-=ia;oa-=ga;ya-=ia;pa=1/(na*ya-oa*la);ta=(ya*T-la*R)*pa;la=(ya*D-la*$)*pa;T=(na*R- +oa*T)*pa;D=(na*$-oa*D)*pa;B=B-ta*ga-T*ia;X=X-la*ga-D*ia;k.save();k.transform(ta,la,T,D,B,X);k.clip();k.drawImage(ca,0,0);k.restore()}function Ja(B,X,T,D){var R=~~(B.r*255),$=~~(B.g*255);B=~~(B.b*255);var ca=~~(X.r*255),ga=~~(X.g*255);X=~~(X.b*255);var ia=~~(T.r*255),na=~~(T.g*255);T=~~(T.b*255);var la=~~(D.r*255),oa=~~(D.g*255);D=~~(D.b*255);ha[0]=R<0?0:R>255?255:R;ha[1]=$<0?0:$>255?255:$;ha[2]=B<0?0:B>255?255:B;ha[4]=ca<0?0:ca>255?255:ca;ha[5]=ga<0?0:ga>255?255:ga;ha[6]=X<0?0:X>255?255:X;ha[8]=ia< +0?0:ia>255?255:ia;ha[9]=na<0?0:na>255?255:na;ha[10]=T<0?0:T>255?255:T;ha[12]=la<0?0:la>255?255:la;ha[13]=oa<0?0:oa>255?255:oa;ha[14]=D<0?0:D>255?255:D;ka.putImageData(fa,0,0);ua.drawImage(qa,0,0);return sa}function Ea(B,X,T){B=(B-X)/(T-X);return B*B*(3-2*B)}function Fa(B){B=(B+1)*0.5;return B<0?0:B>1?1:B}function Ga(B,X){var T=X.x-B.x,D=X.y-B.y,R=1/Math.sqrt(T*T+D*D);T*=R;D*=R;X.x+=T;X.y+=D;B.x-=T;B.y-=D}var Da,Ka,aa,ma,wa,Ha,La,za;k.setTransform(1,0,0,-1,b,i);this.autoClear&&this.clear();d=e.projectScene(ea, +ra,this.sortElements);(M=ea.lights.length>0)&&Ma(ea);Da=0;for(Ka=d.length;Da<Ka;Da++){aa=d[Da];w.empty();if(aa instanceof THREE.RenderableParticle){p=aa;p.x*=b;p.y*=i;ma=0;for(wa=aa.materials.length;ma<wa;ma++)Na(p,aa,aa.materials[ma],ea)}else if(aa instanceof THREE.RenderableLine){p=aa.v1;U=aa.v2;p.positionScreen.x*=b;p.positionScreen.y*=i;U.positionScreen.x*=b;U.positionScreen.y*=i;w.addPoint(p.positionScreen.x,p.positionScreen.y);w.addPoint(U.positionScreen.x,U.positionScreen.y);if(V.instersects(w)){ma= +0;for(wa=aa.materials.length;ma<wa;)Oa(p,U,aa,aa.materials[ma++],ea)}}else if(aa instanceof THREE.RenderableFace3){p=aa.v1;U=aa.v2;F=aa.v3;p.positionScreen.x*=b;p.positionScreen.y*=i;U.positionScreen.x*=b;U.positionScreen.y*=i;F.positionScreen.x*=b;F.positionScreen.y*=i;if(aa.overdraw){Ga(p.positionScreen,U.positionScreen);Ga(U.positionScreen,F.positionScreen);Ga(F.positionScreen,p.positionScreen)}w.add3Points(p.positionScreen.x,p.positionScreen.y,U.positionScreen.x,U.positionScreen.y,F.positionScreen.x, +F.positionScreen.y);if(V.instersects(w)){ma=0;for(wa=aa.meshMaterials.length;ma<wa;){za=aa.meshMaterials[ma++];if(za instanceof THREE.MeshFaceMaterial){Ha=0;for(La=aa.faceMaterials.length;Ha<La;)(za=aa.faceMaterials[Ha++])&&Ia(p,U,F,aa,za,ea)}else Ia(p,U,F,aa,za,ea)}}}S.addRectangle(w)}k.setTransform(1,0,0,1,0,0)}}; +THREE.SVGRenderer=function(){function a(N,G,W){var P,I,L,V;P=0;for(I=N.lights.length;P<I;P++){L=N.lights[P];if(L instanceof THREE.DirectionalLight){V=G.normalWorld.dot(L.position)*L.intensity;if(V>0){W.r+=L.color.r*V;W.g+=L.color.g*V;W.b+=L.color.b*V}}else if(L instanceof THREE.PointLight){C.sub(L.position,G.centroidWorld);C.normalize();V=G.normalWorld.dot(C)*L.intensity;if(V>0){W.r+=L.color.r*V;W.g+=L.color.g*V;W.b+=L.color.b*V}}}}function c(N,G,W,P,I,L){s=e(n++);s.setAttribute("d","M "+N.positionScreen.x+ +" "+N.positionScreen.y+" L "+G.positionScreen.x+" "+G.positionScreen.y+" L "+W.positionScreen.x+","+W.positionScreen.y+"z");if(I instanceof THREE.MeshBasicMaterial)F.__styleString=I.color.__styleString;else if(I instanceof THREE.MeshLambertMaterial)if(U){f.r=j.r;f.g=j.g;f.b=j.b;a(L,P,f);F.r=I.color.r*f.r;F.g=I.color.g*f.g;F.b=I.color.b*f.b;F.updateStyleString()}else F.__styleString=I.color.__styleString;else if(I instanceof THREE.MeshDepthMaterial){r=1-I.__2near/(I.__farPlusNear-P.z*I.__farMinusNear); +F.setRGB(r,r,r)}else I instanceof THREE.MeshNormalMaterial&&F.setRGB(g(P.normalWorld.x),g(P.normalWorld.y),g(P.normalWorld.z));I.wireframe?s.setAttribute("style","fill: none; stroke: "+F.__styleString+"; stroke-width: "+I.wireframe_linewidth+"; stroke-opacity: "+I.opacity+"; stroke-linecap: "+I.wireframe_linecap+"; stroke-linejoin: "+I.wireframe_linejoin):s.setAttribute("style","fill: "+F.__styleString+"; fill-opacity: "+I.opacity);b.appendChild(s)}function d(N,G,W,P,I,L,V){s=e(n++);s.setAttribute("d", +"M "+N.positionScreen.x+" "+N.positionScreen.y+" L "+G.positionScreen.x+" "+G.positionScreen.y+" L "+W.positionScreen.x+","+W.positionScreen.y+" L "+P.positionScreen.x+","+P.positionScreen.y+"z");if(L instanceof THREE.MeshBasicMaterial)F.__styleString=L.color.__styleString;else if(L instanceof THREE.MeshLambertMaterial)if(U){f.r=j.r;f.g=j.g;f.b=j.b;a(V,I,f);F.r=L.color.r*f.r;F.g=L.color.g*f.g;F.b=L.color.b*f.b;F.updateStyleString()}else F.__styleString=L.color.__styleString;else if(L instanceof THREE.MeshDepthMaterial){r= +1-L.__2near/(L.__farPlusNear-I.z*L.__farMinusNear);F.setRGB(r,r,r)}else L instanceof THREE.MeshNormalMaterial&&F.setRGB(g(I.normalWorld.x),g(I.normalWorld.y),g(I.normalWorld.z));L.wireframe?s.setAttribute("style","fill: none; stroke: "+F.__styleString+"; stroke-width: "+L.wireframe_linewidth+"; stroke-opacity: "+L.opacity+"; stroke-linecap: "+L.wireframe_linecap+"; stroke-linejoin: "+L.wireframe_linejoin):s.setAttribute("style","fill: "+F.__styleString+"; fill-opacity: "+L.opacity);b.appendChild(s)} +function e(N){if(m[N]==null){m[N]=document.createElementNS("http://www.w3.org/2000/svg","path");O==0&&m[N].setAttribute("shape-rendering","crispEdges");return m[N]}return m[N]}function g(N){return N<0?Math.min((1+N)*0.5,0.5):0.5+Math.min(N*0.5,0.5)}var h=null,o=new THREE.Projector,b=document.createElementNS("http://www.w3.org/2000/svg","svg"),i,k,y,z,u,x,H,J,K=new THREE.Rectangle,p=new THREE.Rectangle,U=false,F=new THREE.Color(16777215),f=new THREE.Color(16777215),j=new THREE.Color(0),q=new THREE.Color(0), +l=new THREE.Color(0),r,C=new THREE.Vector3,m=[],t=[],v=[],s,n,E,A,O=1;this.domElement=b;this.sortElements=this.sortObjects=this.autoClear=true;this.setQuality=function(N){switch(N){case "high":O=1;break;case "low":O=0}};this.setSize=function(N,G){i=N;k=G;y=i/2;z=k/2;b.setAttribute("viewBox",-y+" "+-z+" "+i+" "+k);b.setAttribute("width",i);b.setAttribute("height",k);K.set(-y,-z,y,z)};this.clear=function(){for(;b.childNodes.length>0;)b.removeChild(b.childNodes[0])};this.render=function(N,G){var W,P, +I,L,V,S,w,M;this.autoClear&&this.clear();h=o.projectScene(N,G,this.sortElements);A=E=n=0;if(U=N.lights.length>0){w=N.lights;j.setRGB(0,0,0);q.setRGB(0,0,0);l.setRGB(0,0,0);W=0;for(P=w.length;W<P;W++){I=w[W];L=I.color;if(I instanceof THREE.AmbientLight){j.r+=L.r;j.g+=L.g;j.b+=L.b}else if(I instanceof THREE.DirectionalLight){q.r+=L.r;q.g+=L.g;q.b+=L.b}else if(I instanceof THREE.PointLight){l.r+=L.r;l.g+=L.g;l.b+=L.b}}}W=0;for(P=h.length;W<P;W++){w=h[W];p.empty();if(w instanceof THREE.RenderableParticle){u= +w;u.x*=y;u.y*=-z;I=0;for(L=w.materials.length;I<L;I++)if(M=w.materials[I]){V=u;S=w;M=M;var Q=E++;if(t[Q]==null){t[Q]=document.createElementNS("http://www.w3.org/2000/svg","circle");O==0&&t[Q].setAttribute("shape-rendering","crispEdges")}s=t[Q];s.setAttribute("cx",V.x);s.setAttribute("cy",V.y);s.setAttribute("r",S.scale.x*y);if(M instanceof THREE.ParticleCircleMaterial){if(U){f.r=j.r+q.r+l.r;f.g=j.g+q.g+l.g;f.b=j.b+q.b+l.b;F.r=M.color.r*f.r;F.g=M.color.g*f.g;F.b=M.color.b*f.b;F.updateStyleString()}else F= +M.color;s.setAttribute("style","fill: "+F.__styleString)}b.appendChild(s)}}else if(w instanceof THREE.RenderableLine){u=w.v1;x=w.v2;u.positionScreen.x*=y;u.positionScreen.y*=-z;x.positionScreen.x*=y;x.positionScreen.y*=-z;p.addPoint(u.positionScreen.x,u.positionScreen.y);p.addPoint(x.positionScreen.x,x.positionScreen.y);if(K.instersects(p)){I=0;for(L=w.materials.length;I<L;)if(M=w.materials[I++]){V=u;S=x;M=M;Q=A++;if(v[Q]==null){v[Q]=document.createElementNS("http://www.w3.org/2000/svg","line");O== +0&&v[Q].setAttribute("shape-rendering","crispEdges")}s=v[Q];s.setAttribute("x1",V.positionScreen.x);s.setAttribute("y1",V.positionScreen.y);s.setAttribute("x2",S.positionScreen.x);s.setAttribute("y2",S.positionScreen.y);if(M instanceof THREE.LineBasicMaterial){F.__styleString=M.color.__styleString;s.setAttribute("style","fill: none; stroke: "+F.__styleString+"; stroke-width: "+M.linewidth+"; stroke-opacity: "+M.opacity+"; stroke-linecap: "+M.linecap+"; stroke-linejoin: "+M.linejoin);b.appendChild(s)}}}}else if(w instanceof +THREE.RenderableFace3){u=w.v1;x=w.v2;H=w.v3;u.positionScreen.x*=y;u.positionScreen.y*=-z;x.positionScreen.x*=y;x.positionScreen.y*=-z;H.positionScreen.x*=y;H.positionScreen.y*=-z;p.addPoint(u.positionScreen.x,u.positionScreen.y);p.addPoint(x.positionScreen.x,x.positionScreen.y);p.addPoint(H.positionScreen.x,H.positionScreen.y);if(K.instersects(p)){I=0;for(L=w.meshMaterials.length;I<L;){M=w.meshMaterials[I++];if(M instanceof THREE.MeshFaceMaterial){V=0;for(S=w.faceMaterials.length;V<S;)(M=w.faceMaterials[V++])&& +c(u,x,H,w,M,N)}else M&&c(u,x,H,w,M,N)}}}else if(w instanceof THREE.RenderableFace4){u=w.v1;x=w.v2;H=w.v3;J=w.v4;u.positionScreen.x*=y;u.positionScreen.y*=-z;x.positionScreen.x*=y;x.positionScreen.y*=-z;H.positionScreen.x*=y;H.positionScreen.y*=-z;J.positionScreen.x*=y;J.positionScreen.y*=-z;p.addPoint(u.positionScreen.x,u.positionScreen.y);p.addPoint(x.positionScreen.x,x.positionScreen.y);p.addPoint(H.positionScreen.x,H.positionScreen.y);p.addPoint(J.positionScreen.x,J.positionScreen.y);if(K.instersects(p)){I= +0;for(L=w.meshMaterials.length;I<L;){M=w.meshMaterials[I++];if(M instanceof THREE.MeshFaceMaterial){V=0;for(S=w.faceMaterials.length;V<S;)(M=w.faceMaterials[V++])&&d(u,x,H,J,w,M,N)}else M&&d(u,x,H,J,w,M,N)}}}}}}; +THREE.WebGLRenderer=function(a){function c(f,j){f.fragment_shader=j.fragment_shader;f.vertex_shader=j.vertex_shader;f.uniforms=Uniforms.clone(j.uniforms)}function d(f,j){f.uniforms.color.value.setRGB(f.color.r*f.opacity,f.color.g*f.opacity,f.color.b*f.opacity);f.uniforms.opacity.value=f.opacity;f.uniforms.map.texture=f.map;f.uniforms.env_map.texture=f.env_map;f.uniforms.reflectivity.value=f.reflectivity;f.uniforms.refraction_ratio.value=f.refraction_ratio;f.uniforms.combine.value=f.combine;f.uniforms.useRefract.value= +f.env_map&&f.env_map.mapping instanceof THREE.CubeRefractionMapping;if(j){f.uniforms.fogColor.value.setHex(j.color.hex);if(j instanceof THREE.Fog){f.uniforms.fogNear.value=j.near;f.uniforms.fogFar.value=j.far}else if(j instanceof THREE.FogExp2)f.uniforms.fogDensity.value=j.density}}function e(f,j){f.uniforms.color.value.setRGB(f.color.r*f.opacity,f.color.g*f.opacity,f.color.b*f.opacity);f.uniforms.opacity.value=f.opacity;if(j){f.uniforms.fogColor.value.setHex(j.color.hex);if(j instanceof THREE.Fog){f.uniforms.fogNear.value= +j.near;f.uniforms.fogFar.value=j.far}else if(j instanceof THREE.FogExp2)f.uniforms.fogDensity.value=j.density}}function g(f,j){var q;if(f=="fragment")q=b.createShader(b.FRAGMENT_SHADER);else if(f=="vertex")q=b.createShader(b.VERTEX_SHADER);b.shaderSource(q,j);b.compileShader(q);if(!b.getShaderParameter(q,b.COMPILE_STATUS)){alert(b.getShaderInfoLog(q));return null}return q}function h(f){switch(f){case THREE.RepeatWrapping:return b.REPEAT;case THREE.ClampToEdgeWrapping:return b.CLAMP_TO_EDGE;case THREE.MirroredRepeatWrapping:return b.MIRRORED_REPEAT; +case THREE.NearestFilter:return b.NEAREST;case THREE.NearestMipMapNearestFilter:return b.NEAREST_MIPMAP_NEAREST;case THREE.NearestMipMapLinearFilter:return b.NEAREST_MIPMAP_LINEAR;case THREE.LinearFilter:return b.LINEAR;case THREE.LinearMipMapNearestFilter:return b.LINEAR_MIPMAP_NEAREST;case THREE.LinearMipMapLinearFilter:return b.LINEAR_MIPMAP_LINEAR;case THREE.ByteType:return b.BYTE;case THREE.UnsignedByteType:return b.UNSIGNED_BYTE;case THREE.ShortType:return b.SHORT;case THREE.UnsignedShortType:return b.UNSIGNED_SHORT; +case THREE.IntType:return b.INT;case THREE.UnsignedShortType:return b.UNSIGNED_INT;case THREE.FloatType:return b.FLOAT;case THREE.AlphaFormat:return b.ALPHA;case THREE.RGBFormat:return b.RGB;case THREE.RGBAFormat:return b.RGBA;case THREE.LuminanceFormat:return b.LUMINANCE;case THREE.LuminanceAlphaFormat:return b.LUMINANCE_ALPHA}return 0}var o=document.createElement("canvas"),b,i=null,k=null,y=new THREE.Matrix4,z,u=new Float32Array(16),x=new Float32Array(16),H=new Float32Array(16),J=new Float32Array(9), +K=new Float32Array(16),p=true,U=new THREE.Color(0),F=0;if(a){if(a.antialias!==undefined)p=a.antialias;a.clearColor!==undefined&&U.setHex(a.clearColor);if(a.clearAlpha!==undefined)F=a.clearAlpha}this.domElement=o;this.autoClear=true;(function(f,j,q){try{b=o.getContext("experimental-webgl",{antialias:f})}catch(l){}if(!b){alert("WebGL not supported");throw"cannot create webgl context";}b.clearColor(0,0,0,1);b.clearDepth(1);b.enable(b.DEPTH_TEST);b.depthFunc(b.LEQUAL);b.frontFace(b.CCW);b.cullFace(b.BACK); +b.enable(b.CULL_FACE);b.enable(b.BLEND);b.blendFunc(b.ONE,b.ONE_MINUS_SRC_ALPHA);b.clearColor(j.r,j.g,j.b,q)})(p,U,F);this.context=b;this.lights={ambient:[0,0,0],directional:{length:0,colors:[],positions:[]},point:{length:0,colors:[],positions:[]}};this.setSize=function(f,j){o.width=f;o.height=j;b.viewport(0,0,o.width,o.height)};this.setClearColor=function(f,j){var q=new THREE.Color(f);b.clearColor(q.r,q.g,q.b,j)};this.clear=function(){b.clear(b.COLOR_BUFFER_BIT|b.DEPTH_BUFFER_BIT)};this.setupLights= +function(f,j){var q,l,r,C=0,m=0,t=0,v,s,n,E=this.lights,A=E.directional.colors,O=E.directional.positions,N=E.point.colors,G=E.point.positions,W=0,P=0;q=0;for(l=j.length;q<l;q++){r=j[q];v=r.color;s=r.position;n=r.intensity;if(r instanceof THREE.AmbientLight){C+=v.r;m+=v.g;t+=v.b}else if(r instanceof THREE.DirectionalLight){A[W*3]=v.r*n;A[W*3+1]=v.g*n;A[W*3+2]=v.b*n;O[W*3]=s.x;O[W*3+1]=s.y;O[W*3+2]=s.z;W+=1}else if(r instanceof THREE.PointLight){N[P*3]=v.r*n;N[P*3+1]=v.g*n;N[P*3+2]=v.b*n;G[P*3]=s.x; +G[P*3+1]=s.y;G[P*3+2]=s.z;P+=1}}E.point.length=P;E.directional.length=W;E.ambient[0]=C;E.ambient[1]=m;E.ambient[2]=t};this.createParticleBuffers=function(f){f.__webGLVertexBuffer=b.createBuffer();f.__webGLFaceBuffer=b.createBuffer()};this.createLineBuffers=function(f){f.__webGLVertexBuffer=b.createBuffer();f.__webGLLineBuffer=b.createBuffer()};this.createMeshBuffers=function(f){f.__webGLVertexBuffer=b.createBuffer();f.__webGLNormalBuffer=b.createBuffer();f.__webGLTangentBuffer=b.createBuffer();f.__webGLUVBuffer= +b.createBuffer();f.__webGLFaceBuffer=b.createBuffer();f.__webGLLineBuffer=b.createBuffer()};this.initLineBuffers=function(f){var j=f.vertices.length;f.__vertexArray=new Float32Array(j*3);f.__lineArray=new Uint16Array(j);f.__webGLLineCount=j};this.initMeshBuffers=function(f,j){var q,l,r=0,C=0,m=0,t=j.geometry.faces,v=f.faces;q=0;for(l=v.length;q<l;q++){fi=v[q];face=t[fi];if(face instanceof THREE.Face3){r+=3;C+=1;m+=3}else if(face instanceof THREE.Face4){r+=4;C+=2;m+=4}}f.__vertexArray=new Float32Array(r* +3);f.__normalArray=new Float32Array(r*3);f.__tangentArray=new Float32Array(r*4);f.__uvArray=new Float32Array(r*2);f.__faceArray=new Uint16Array(C*3);f.__lineArray=new Uint16Array(m*2);r=false;q=0;for(l=j.materials.length;q<l;q++){t=j.materials[q];if(t instanceof THREE.MeshFaceMaterial){t=0;for(v=f.materials.length;t<v;t++)if(f.materials[t]&&f.materials[t].shading!=undefined&&f.materials[t].shading==THREE.SmoothShading){r=true;break}}else if(t&&t.shading!=undefined&&t.shading==THREE.SmoothShading){r= +true;break}if(r)break}f.__needsSmoothNormals=r;f.__webGLFaceCount=C*3;f.__webGLLineCount=m*2};this.setMeshBuffers=function(f,j,q,l,r,C,m,t){var v,s,n,E,A,O,N,G,W,P=0,I=0,L=0,V=0,S=0,w=0,M=0,Q=f.__vertexArray,da=f.__uvArray,ba=f.__normalArray,Z=f.__tangentArray,ja=f.__faceArray,Y=f.__lineArray,qa=f.__needsSmoothNormals,ka=j.geometry,fa=ka.vertices,ha=f.faces,sa=ka.faces,ua=ka.uvs;j=0;for(v=ha.length;j<v;j++){s=ha[j];n=sa[s];s=ua[s];E=n.vertexNormals;A=n.normal;if(n instanceof THREE.Face3){if(l){O= +fa[n.a].position;N=fa[n.b].position;G=fa[n.c].position;Q[I]=O.x;Q[I+1]=O.y;Q[I+2]=O.z;Q[I+3]=N.x;Q[I+4]=N.y;Q[I+5]=N.z;Q[I+6]=G.x;Q[I+7]=G.y;Q[I+8]=G.z;I+=9}if(t&&ka.hasTangents){O=fa[n.a].tangent;N=fa[n.b].tangent;G=fa[n.c].tangent;Z[w]=O.x;Z[w+1]=O.y;Z[w+2]=O.z;Z[w+3]=O.w;Z[w+4]=N.x;Z[w+5]=N.y;Z[w+6]=N.z;Z[w+7]=N.w;Z[w+8]=G.x;Z[w+9]=G.y;Z[w+10]=G.z;Z[w+11]=G.w;w+=12}if(m)if(E.length==3&&qa)for(n=0;n<3;n++){A=E[n];ba[S]=A.x;ba[S+1]=A.y;ba[S+2]=A.z;S+=3}else for(n=0;n<3;n++){ba[S]=A.x;ba[S+1]=A.y; +ba[S+2]=A.z;S+=3}if(C&&s)for(n=0;n<3;n++){E=s[n];da[L]=E.u;da[L+1]=E.v;L+=2}if(r){ja[V]=P;ja[V+1]=P+1;ja[V+2]=P+2;V+=3;Y[M]=P;Y[M+1]=P+1;Y[M+2]=P;Y[M+3]=P+2;Y[M+4]=P+1;Y[M+5]=P+2;M+=6;P+=3}}else if(n instanceof THREE.Face4){if(l){O=fa[n.a].position;N=fa[n.b].position;G=fa[n.c].position;W=fa[n.d].position;Q[I]=O.x;Q[I+1]=O.y;Q[I+2]=O.z;Q[I+3]=N.x;Q[I+4]=N.y;Q[I+5]=N.z;Q[I+6]=G.x;Q[I+7]=G.y;Q[I+8]=G.z;Q[I+9]=W.x;Q[I+10]=W.y;Q[I+11]=W.z;I+=12}if(t&&ka.hasTangents){O=fa[n.a].tangent;N=fa[n.b].tangent; +G=fa[n.c].tangent;n=fa[n.d].tangent;Z[w]=O.x;Z[w+1]=O.y;Z[w+2]=O.z;Z[w+3]=O.w;Z[w+4]=N.x;Z[w+5]=N.y;Z[w+6]=N.z;Z[w+7]=N.w;Z[w+8]=G.x;Z[w+9]=G.y;Z[w+10]=G.z;Z[w+11]=G.w;Z[w+12]=n.x;Z[w+13]=n.y;Z[w+14]=n.z;Z[w+15]=n.w;w+=16}if(m)if(E.length==4&&qa)for(n=0;n<4;n++){A=E[n];ba[S]=A.x;ba[S+1]=A.y;ba[S+2]=A.z;S+=3}else for(n=0;n<4;n++){ba[S]=A.x;ba[S+1]=A.y;ba[S+2]=A.z;S+=3}if(C&&s)for(n=0;n<4;n++){E=s[n];da[L]=E.u;da[L+1]=E.v;L+=2}if(r){ja[V]=P;ja[V+1]=P+1;ja[V+2]=P+2;ja[V+3]=P;ja[V+4]=P+2;ja[V+5]=P+3; +V+=6;Y[M]=P;Y[M+1]=P+1;Y[M+2]=P;Y[M+3]=P+3;Y[M+4]=P+1;Y[M+5]=P+2;Y[M+6]=P+2;Y[M+7]=P+3;M+=8;P+=4}}}if(l){b.bindBuffer(b.ARRAY_BUFFER,f.__webGLVertexBuffer);b.bufferData(b.ARRAY_BUFFER,Q,q)}if(m){b.bindBuffer(b.ARRAY_BUFFER,f.__webGLNormalBuffer);b.bufferData(b.ARRAY_BUFFER,ba,q)}if(t&&ka.hasTangents){b.bindBuffer(b.ARRAY_BUFFER,f.__webGLTangentBuffer);b.bufferData(b.ARRAY_BUFFER,Z,q)}if(C&&L>0){b.bindBuffer(b.ARRAY_BUFFER,f.__webGLUVBuffer);b.bufferData(b.ARRAY_BUFFER,da,q)}if(r){b.bindBuffer(b.ELEMENT_ARRAY_BUFFER, +f.__webGLFaceBuffer);b.bufferData(b.ELEMENT_ARRAY_BUFFER,ja,q);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,f.__webGLLineBuffer);b.bufferData(b.ELEMENT_ARRAY_BUFFER,Y,q)}};this.setLineBuffers=function(f,j,q,l){var r,C,m=f.vertices,t=m.length,v=f.__vertexArray,s=f.__lineArray;if(q)for(q=0;q<t;q++){r=m[q].position;C=q*3;v[C]=r.x;v[C+1]=r.y;v[C+2]=r.z}if(l)for(q=0;q<t;q++)s[q]=q;b.bindBuffer(b.ARRAY_BUFFER,f.__webGLVertexBuffer);b.bufferData(b.ARRAY_BUFFER,v,j);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,f.__webGLLineBuffer); +b.bufferData(b.ELEMENT_ARRAY_BUFFER,s,j)};this.setParticleBuffers=function(){};this.renderBuffer=function(f,j,q,l,r,C){var m,t,v,s;if(!l.program){if(l instanceof THREE.MeshDepthMaterial){c(l,THREE.ShaderLib.depth);l.uniforms.mNear.value=f.near;l.uniforms.mFar.value=f.far}else if(l instanceof THREE.MeshNormalMaterial)c(l,THREE.ShaderLib.normal);else if(l instanceof THREE.MeshBasicMaterial){c(l,THREE.ShaderLib.basic);d(l,q)}else if(l instanceof THREE.MeshLambertMaterial){c(l,THREE.ShaderLib.lambert); +d(l,q)}else if(l instanceof THREE.MeshPhongMaterial){c(l,THREE.ShaderLib.phong);d(l,q)}else if(l instanceof THREE.LineBasicMaterial){c(l,THREE.ShaderLib.basic);e(l,q)}var n,E,A;n=s=t=0;for(E=j.length;n<E;n++){A=j[n];A instanceof THREE.DirectionalLight&&s++;A instanceof THREE.PointLight&&t++}if(t+s<=4){n=s;t=t}else{n=Math.ceil(4*s/(t+s));t=4-n}t={directional:n,point:t};s={fog:q,map:l.map,env_map:l.env_map,maxDirLights:t.directional,maxPointLights:t.point};t=l.fragment_shader;n=l.vertex_shader;E=b.createProgram(); +A=["#ifdef GL_ES\nprecision highp float;\n#endif","#define MAX_DIR_LIGHTS "+s.maxDirLights,"#define MAX_POINT_LIGHTS "+s.maxPointLights,s.fog?"#define USE_FOG":"",s.fog instanceof THREE.FogExp2?"#define FOG_EXP2":"",s.map?"#define USE_MAP":"",s.env_map?"#define USE_ENVMAP":"","uniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"].join("\n");s=[b.getParameter(b.MAX_VERTEX_TEXTURE_IMAGE_UNITS)>0?"#define VERTEX_TEXTURES":"","#define MAX_DIR_LIGHTS "+s.maxDirLights,"#define MAX_POINT_LIGHTS "+s.maxPointLights, +s.map?"#define USE_MAP":"",s.env_map?"#define USE_ENVMAP":"","uniform mat4 objectMatrix;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat4 viewMatrix;\nuniform mat3 normalMatrix;\nuniform vec3 cameraPosition;\nattribute vec3 position;\nattribute vec3 normal;\nattribute vec2 uv;\n"].join("\n");b.attachShader(E,g("fragment",A+t));b.attachShader(E,g("vertex",s+n));b.linkProgram(E);b.getProgramParameter(E,b.LINK_STATUS)||alert("Could not initialise shaders\nVALIDATE_STATUS: "+ +b.getProgramParameter(E,b.VALIDATE_STATUS)+", gl error ["+b.getError()+"]");E.uniforms={};E.attributes={};l.program=E;t=["viewMatrix","modelViewMatrix","projectionMatrix","normalMatrix","objectMatrix","cameraPosition"];for(m in l.uniforms)t.push(m);m=l.program;n=0;for(E=t.length;n<E;n++){A=t[n];m.uniforms[A]=b.getUniformLocation(m,A)}m=l.program;t=["position","normal","uv","tangent"];n=0;for(E=t.length;n<E;n++){A=t[n];m.attributes[A]=b.getAttribLocation(m,A)}}m=l.program;if(m!=i){b.useProgram(m); +i=m}this.loadCamera(m,f);this.loadMatrices(m);if(l instanceof THREE.MeshPhongMaterial||l instanceof THREE.MeshLambertMaterial){this.setupLights(m,j);f=this.lights;l.uniforms.enableLighting.value=f.directional.length+f.point.length;l.uniforms.ambientLightColor.value=f.ambient;l.uniforms.directionalLightColor.value=f.directional.colors;l.uniforms.directionalLightDirection.value=f.directional.positions;l.uniforms.pointLightColor.value=f.point.colors;l.uniforms.pointLightPosition.value=f.point.positions}if(l instanceof +THREE.MeshBasicMaterial||l instanceof THREE.MeshLambertMaterial||l instanceof THREE.MeshPhongMaterial)d(l,q);l instanceof THREE.LineBasicMaterial&&e(l,q);if(l instanceof THREE.MeshPhongMaterial){l.uniforms.ambient.value.setRGB(l.ambient.r,l.ambient.g,l.ambient.b);l.uniforms.specular.value.setRGB(l.specular.r,l.specular.g,l.specular.b);l.uniforms.shininess.value=l.shininess}q=l.uniforms;for(v in q)if(n=m.uniforms[v]){j=q[v];t=j.type;f=j.value;if(t=="i")b.uniform1i(n,f);else if(t=="f")b.uniform1f(n, +f);else if(t=="fv1")b.uniform1fv(n,f);else if(t=="fv")b.uniform3fv(n,f);else if(t=="v2")b.uniform2f(n,f.x,f.y);else if(t=="v3")b.uniform3f(n,f.x,f.y,f.z);else if(t=="c")b.uniform3f(n,f.r,f.g,f.b);else if(t=="t"){b.uniform1i(n,f);if(j=j.texture)if(j.image instanceof Array&&j.image.length==6){j=j;f=f;if(j.image.length==6){if(!j.image.__webGLTextureCube&&!j.image.__cubeMapInitialized&&j.image.loadCount==6){j.image.__webGLTextureCube=b.createTexture();b.bindTexture(b.TEXTURE_CUBE_MAP,j.image.__webGLTextureCube); +b.texParameteri(b.TEXTURE_CUBE_MAP,b.TEXTURE_WRAP_S,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_CUBE_MAP,b.TEXTURE_WRAP_T,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_CUBE_MAP,b.TEXTURE_MAG_FILTER,b.LINEAR);b.texParameteri(b.TEXTURE_CUBE_MAP,b.TEXTURE_MIN_FILTER,b.LINEAR_MIPMAP_LINEAR);for(t=0;t<6;++t)b.texImage2D(b.TEXTURE_CUBE_MAP_POSITIVE_X+t,0,b.RGBA,b.RGBA,b.UNSIGNED_BYTE,j.image[t]);b.generateMipmap(b.TEXTURE_CUBE_MAP);b.bindTexture(b.TEXTURE_CUBE_MAP,null);j.image.__cubeMapInitialized=true}b.activeTexture(b.TEXTURE0+ +f);b.bindTexture(b.TEXTURE_CUBE_MAP,j.image.__webGLTextureCube)}}else{j=j;f=f;if(!j.__webGLTexture&&j.image.loaded){j.__webGLTexture=b.createTexture();b.bindTexture(b.TEXTURE_2D,j.__webGLTexture);b.texImage2D(b.TEXTURE_2D,0,b.RGBA,b.RGBA,b.UNSIGNED_BYTE,j.image);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,h(j.wrap_s));b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,h(j.wrap_t));b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,h(j.mag_filter));b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,h(j.min_filter)); +b.generateMipmap(b.TEXTURE_2D);b.bindTexture(b.TEXTURE_2D,null)}b.activeTexture(b.TEXTURE0+f);b.bindTexture(b.TEXTURE_2D,j.__webGLTexture)}}}v=m.attributes;b.bindBuffer(b.ARRAY_BUFFER,r.__webGLVertexBuffer);b.vertexAttribPointer(v.position,3,b.FLOAT,false,0,0);b.enableVertexAttribArray(v.position);if(v.normal>=0){b.bindBuffer(b.ARRAY_BUFFER,r.__webGLNormalBuffer);b.vertexAttribPointer(v.normal,3,b.FLOAT,false,0,0);b.enableVertexAttribArray(v.normal)}if(v.tangent>=0){b.bindBuffer(b.ARRAY_BUFFER,r.__webGLTangentBuffer); +b.vertexAttribPointer(v.tangent,4,b.FLOAT,false,0,0);b.enableVertexAttribArray(v.tangent)}if(v.uv>=0)if(r.__webGLUVBuffer){b.bindBuffer(b.ARRAY_BUFFER,r.__webGLUVBuffer);b.vertexAttribPointer(v.uv,2,b.FLOAT,false,0,0);b.enableVertexAttribArray(v.uv)}else b.disableVertexAttribArray(v.uv);if(l.wireframe||l instanceof THREE.LineBasicMaterial){v=l.wireframe_linewidth!==undefined?l.wireframe_linewidth:l.linewidth!==undefined?l.linewidth:1;l=l instanceof THREE.LineBasicMaterial&&C.type==THREE.LineStrip? +b.LINE_STRIP:b.LINES;b.lineWidth(v);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,r.__webGLLineBuffer);b.drawElements(l,r.__webGLLineCount,b.UNSIGNED_SHORT,0)}else{b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,r.__webGLFaceBuffer);b.drawElements(b.TRIANGLES,r.__webGLFaceCount,b.UNSIGNED_SHORT,0)}};this.renderPass=function(f,j,q,l,r,C,m){var t,v,s,n,E;s=0;for(n=l.materials.length;s<n;s++){t=l.materials[s];if(t instanceof THREE.MeshFaceMaterial){t=0;for(v=r.materials.length;t<v;t++)if((E=r.materials[t])&&E.blending==C&& +E.opacity<1==m){this.setBlending(E.blending);this.renderBuffer(f,j,q,E,r,l)}}else if((E=t)&&E.blending==C&&E.opacity<1==m){this.setBlending(E.blending);this.renderBuffer(f,j,q,E,r,l)}}};this.render=function(f,j,q,l){var r,C,m,t=f.lights,v=f.fog;this.initWebGLObjects(f);l=l!==undefined?l:true;if(q&&!q.__webGLFramebuffer){q.__webGLFramebuffer=b.createFramebuffer();q.__webGLRenderbuffer=b.createRenderbuffer();q.__webGLTexture=b.createTexture();b.bindRenderbuffer(b.RENDERBUFFER,q.__webGLRenderbuffer); +b.renderbufferStorage(b.RENDERBUFFER,b.DEPTH_COMPONENT16,q.width,q.height);b.bindTexture(b.TEXTURE_2D,q.__webGLTexture);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,h(q.wrap_s));b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,h(q.wrap_t));b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,h(q.mag_filter));b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,h(q.min_filter));b.texImage2D(b.TEXTURE_2D,0,h(q.format),q.width,q.height,0,h(q.format),h(q.type),null);b.bindFramebuffer(b.FRAMEBUFFER,q.__webGLFramebuffer); +b.framebufferTexture2D(b.FRAMEBUFFER,b.COLOR_ATTACHMENT0,b.TEXTURE_2D,q.__webGLTexture,0);b.framebufferRenderbuffer(b.FRAMEBUFFER,b.DEPTH_ATTACHMENT,b.RENDERBUFFER,q.__webGLRenderbuffer);b.bindTexture(b.TEXTURE_2D,null);b.bindRenderbuffer(b.RENDERBUFFER,null);b.bindFramebuffer(b.FRAMEBUFFER,null)}if(q){r=q.__webGLFramebuffer;m=q.width;C=q.height}else{r=null;m=o.width;C=o.height}if(r!=k){b.bindFramebuffer(b.FRAMEBUFFER,r);b.viewport(0,0,m,C);l&&b.clear(b.COLOR_BUFFER_BIT|b.DEPTH_BUFFER_BIT);k=r}this.autoClear&& +this.clear();j.autoUpdateMatrix&&j.updateMatrix();u.set(j.matrix.flatten());H.set(j.projectionMatrix.flatten());l=0;for(r=f.__webGLObjects.length;l<r;l++){C=f.__webGLObjects[l];m=C.object;C=C.buffer;if(m.visible){this.setupMatrices(m,j);this.renderPass(j,t,v,m,C,THREE.NormalBlending,false)}}l=0;for(r=f.__webGLObjects.length;l<r;l++){C=f.__webGLObjects[l];m=C.object;C=C.buffer;if(m.visible){this.setupMatrices(m,j);if(m.doubleSided)b.disable(b.CULL_FACE);else{b.enable(b.CULL_FACE);m.flipSided?b.frontFace(b.CW): +b.frontFace(b.CCW)}this.renderPass(j,t,v,m,C,THREE.AdditiveBlending,false);this.renderPass(j,t,v,m,C,THREE.SubtractiveBlending,false);this.renderPass(j,t,v,m,C,THREE.AdditiveBlending,true);this.renderPass(j,t,v,m,C,THREE.SubtractiveBlending,true);this.renderPass(j,t,v,m,C,THREE.NormalBlending,true)}}if(q&&q.min_filter!==THREE.NearestFilter&&q.min_filter!==THREE.LinearFilter){b.bindTexture(b.TEXTURE_2D,q.__webGLTexture);b.generateMipmap(b.TEXTURE_2D);b.bindTexture(b.TEXTURE_2D,null)}};this.initWebGLObjects= +function(f){function j(s,n,E,A){if(s[n]==undefined){f.__webGLObjects.push({buffer:E,object:A});s[n]=1}}var q,l,r,C,m,t,v;if(!f.__webGLObjects){f.__webGLObjects=[];f.__webGLObjectsMap={}}q=0;for(l=f.objects.length;q<l;q++){r=f.objects[q];m=r.geometry;if(f.__webGLObjectsMap[r.id]==undefined)f.__webGLObjectsMap[r.id]={};v=f.__webGLObjectsMap[r.id];if(r instanceof THREE.Mesh){for(C in m.geometryChunks){t=m.geometryChunks[C];if(!t.__webGLVertexBuffer){this.createMeshBuffers(t);this.initMeshBuffers(t,r); +m.__dirtyVertices=true;m.__dirtyElements=true;m.__dirtyUvs=true;m.__dirtyNormals=true;m.__dirtyTangents=true}if(m.__dirtyVertices||m.__dirtyElements||m.__dirtyUvs)this.setMeshBuffers(t,r,b.DYNAMIC_DRAW,m.__dirtyVertices,m.__dirtyElements,m.__dirtyUvs,m.__dirtyNormals,m.__dirtyTangents);j(v,C,t,r)}m.__dirtyVertices=false;m.__dirtyElements=false;m.__dirtyUvs=false;m.__dirtyNormals=false;m.__dirtyTangents=false}else if(r instanceof THREE.Line){if(!m.__webGLVertexBuffer){this.createLineBuffers(m);this.initLineBuffers(m); +m.__dirtyVertices=true;m.__dirtyElements=true}m.__dirtyVertices&&this.setLineBuffers(m,b.DYNAMIC_DRAW,m.__dirtyVertices,m.__dirtyElements);j(v,0,m,r);m.__dirtyVertices=false;m.__dirtyElements=false}else if(r instanceof THREE.ParticleSystem){m.__webGLVertexBuffer||this.createParticleBuffers(m);j(v,0,m,r)}}};this.removeObject=function(f,j){var q,l;for(q=f.__webGLObjects.length-1;q>=0;q--){l=f.__webGLObjects[q].object;j==l&&f.__webGLObjects.splice(q,1)}};this.setupMatrices=function(f,j){f.autoUpdateMatrix&& +f.updateMatrix();y.multiply(j.matrix,f.matrix);x.set(y.flatten());z=THREE.Matrix4.makeInvert3x3(y).transpose();J.set(z.m);K.set(f.matrix.flatten())};this.loadMatrices=function(f){b.uniformMatrix4fv(f.uniforms.viewMatrix,false,u);b.uniformMatrix4fv(f.uniforms.modelViewMatrix,false,x);b.uniformMatrix4fv(f.uniforms.projectionMatrix,false,H);b.uniformMatrix3fv(f.uniforms.normalMatrix,false,J);b.uniformMatrix4fv(f.uniforms.objectMatrix,false,K)};this.loadCamera=function(f,j){b.uniform3f(f.uniforms.cameraPosition, +j.position.x,j.position.y,j.position.z)};this.setBlending=function(f){switch(f){case THREE.AdditiveBlending:b.blendEquation(b.FUNC_ADD);b.blendFunc(b.ONE,b.ONE);break;case THREE.SubtractiveBlending:b.blendFunc(b.DST_COLOR,b.ZERO);break;default:b.blendEquation(b.FUNC_ADD);b.blendFunc(b.ONE,b.ONE_MINUS_SRC_ALPHA)}};this.setFaceCulling=function(f,j){if(f){!j||j=="ccw"?b.frontFace(b.CCW):b.frontFace(b.CW);if(f=="back")b.cullFace(b.BACK);else f=="front"?b.cullFace(b.FRONT):b.cullFace(b.FRONT_AND_BACK); +b.enable(b.CULL_FACE)}else b.disable(b.CULL_FACE)};this.supportsVertexTextures=function(){return b.getParameter(b.MAX_VERTEX_TEXTURE_IMAGE_UNITS)>0}}; +THREE.Snippets={fog_pars_fragment:"#ifdef USE_FOG\nuniform vec3 fogColor;\n#ifdef FOG_EXP2\nuniform float fogDensity;\n#else\nuniform float fogNear;\nuniform float fogFar;\n#endif\n#endif",fog_fragment:"#ifdef USE_FOG\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\n#ifdef FOG_EXP2\nconst float LOG2 = 1.442695;\nfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\nfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n#else\nfloat fogFactor = smoothstep( fogNear, fogFar, depth );\n#endif\ngl_FragColor = mix( gl_FragColor, vec4( fogColor, 1.0 ), fogFactor );\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\nvarying vec3 vReflect;\nuniform float reflectivity;\nuniform samplerCube env_map;\nuniform int combine;\n#endif", +envmap_fragment:"#ifdef USE_ENVMAP\ncubeColor = textureCube( env_map, vec3( -vReflect.x, vReflect.yz ) );\nif ( combine == 1 ) {\ngl_FragColor = mix( gl_FragColor, cubeColor, reflectivity );\n} else {\ngl_FragColor = gl_FragColor * cubeColor;\n}\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\nvarying vec3 vReflect;\nuniform float refraction_ratio;\nuniform bool useRefract;\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\nvec4 mPosition = objectMatrix * vec4( position, 1.0 );\nvec3 nWorld = mat3( objectMatrix[0].xyz, objectMatrix[1].xyz, objectMatrix[2].xyz ) * normal;\nif ( useRefract ) {\nvReflect = refract( normalize( mPosition.xyz - cameraPosition ), normalize( nWorld.xyz ), refraction_ratio );\n} else {\nvReflect = reflect( normalize( mPosition.xyz - cameraPosition ), normalize( nWorld.xyz ) );\n}\n#endif", +map_pars_fragment:"#ifdef USE_MAP\nvarying vec2 vUv;\nuniform sampler2D map;\n#endif",map_pars_vertex:"#ifdef USE_MAP\nvarying vec2 vUv;\n#endif",map_fragment:"#ifdef USE_MAP\nmapColor = texture2D( map, vUv );\n#endif",map_vertex:"#ifdef USE_MAP\nvUv = uv;\n#endif",lights_pars_vertex:"uniform bool enableLighting;\nuniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n#ifdef PHONG\nvarying vec3 vPointLightVector[ MAX_POINT_LIGHTS ];\n#endif\n#endif", +lights_vertex:"if ( !enableLighting ) {\nvLightWeighting = vec3( 1.0 );\n} else {\nvLightWeighting = ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nfor( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nfloat directionalLightWeighting = max( dot( transformedNormal, normalize( lDirection.xyz ) ), 0.0 );\nvLightWeighting += directionalLightColor[ i ] * directionalLightWeighting;\n}\n#endif\n#if MAX_POINT_LIGHTS > 0\nfor( int i = 0; i < MAX_POINT_LIGHTS; i++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 pointLightVector = normalize( lPosition.xyz - mvPosition.xyz );\nfloat pointLightWeighting = max( dot( transformedNormal, pointLightVector ), 0.0 );\nvLightWeighting += pointLightColor[ i ] * pointLightWeighting;\n#ifdef PHONG\nvPointLightVector[ i ] = pointLightVector;\n#endif\n}\n#endif\n}", +lights_pars_fragment:"#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nvarying vec3 vPointLightVector[ MAX_POINT_LIGHTS ];\n#endif\nvarying vec3 vViewPosition;\nvarying vec3 vNormal;",lights_fragment:"vec3 normal = normalize( vNormal );\nvec3 viewPosition = normalize( vViewPosition );\nvec4 mSpecular = vec4( specular, opacity );\n#if MAX_POINT_LIGHTS > 0\nvec4 pointDiffuse = vec4( 0.0 );\nvec4 pointSpecular = vec4( 0.0 );\nfor( int i = 0; i < MAX_POINT_LIGHTS; i++ ) {\nvec3 pointVector = normalize( vPointLightVector[ i ] );\nvec3 pointHalfVector = normalize( vPointLightVector[ i ] + vViewPosition );\nfloat pointDotNormalHalf = dot( normal, pointHalfVector );\nfloat pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );\nfloat pointSpecularWeight = 0.0;\nif ( pointDotNormalHalf >= 0.0 )\npointSpecularWeight = pow( pointDotNormalHalf, shininess );\npointDiffuse += mColor * pointDiffuseWeight;\npointSpecular += mSpecular * pointSpecularWeight;\n}\n#endif\n#if MAX_DIR_LIGHTS > 0\nvec4 dirDiffuse = vec4( 0.0 );\nvec4 dirSpecular = vec4( 0.0 );\nfor( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\nvec3 dirHalfVector = normalize( lDirection.xyz + vViewPosition );\nfloat dirDotNormalHalf = dot( normal, dirHalfVector );\nfloat dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );\nfloat dirSpecularWeight = 0.0;\nif ( dirDotNormalHalf >= 0.0 )\ndirSpecularWeight = pow( dirDotNormalHalf, shininess );\ndirDiffuse += mColor * dirDiffuseWeight;\ndirSpecular += mSpecular * dirSpecularWeight;\n}\n#endif\nvec4 totalLight = vec4( ambient, opacity );\n#if MAX_DIR_LIGHTS > 0\ntotalLight += dirDiffuse + dirSpecular;\n#endif\n#if MAX_POINT_LIGHTS > 0\ntotalLight += pointDiffuse + pointSpecular;\n#endif"}; +THREE.UniformsLib={common:{color:{type:"c",value:new THREE.Color(15658734)},opacity:{type:"f",value:1},map:{type:"t",value:0,texture:null},env_map:{type:"t",value:1,texture:null},useRefract:{type:"i",value:0},reflectivity:{type:"f",value:1},refraction_ratio:{type:"f",value:0.98},combine:{type:"i",value:0},fogDensity:{type:"f",value:2.5E-4},fogNear:{type:"f",value:1},fogFar:{type:"f",value:2E3},fogColor:{type:"c",value:new THREE.Color(16777215)}},lights:{enableLighting:{type:"i",value:1},ambientLightColor:{type:"fv", +value:[]},directionalLightDirection:{type:"fv",value:[]},directionalLightColor:{type:"fv",value:[]},pointLightPosition:{type:"fv",value:[]},pointLightColor:{type:"fv",value:[]}}}; +THREE.ShaderLib={depth:{uniforms:{mNear:{type:"f",value:1},mFar:{type:"f",value:2E3}},fragment_shader:"uniform float mNear;\nuniform float mFar;\nvoid main() {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat color = 1.0 - smoothstep( mNear, mFar, depth );\ngl_FragColor = vec4( vec3( color ), 1.0 );\n}",vertex_shader:"void main() {\ngl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"},normal:{uniforms:{},fragment_shader:"varying vec3 vNormal;\nvoid main() {\ngl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, 1.0 );\n}", +vertex_shader:"varying vec3 vNormal;\nvoid main() {\nvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\nvNormal = normalize( normalMatrix * normal );\ngl_Position = projectionMatrix * mvPosition;\n}"},basic:{uniforms:THREE.UniformsLib.common,fragment_shader:["uniform vec3 color;\nuniform float opacity;",THREE.Snippets.map_pars_fragment,THREE.Snippets.envmap_pars_fragment,THREE.Snippets.fog_pars_fragment,"void main() {\nvec4 mColor = vec4( color, opacity );\nvec4 mapColor = vec4( 1.0 );\nvec4 cubeColor = vec4( 1.0 );", +THREE.Snippets.map_fragment,"gl_FragColor = mColor * mapColor;",THREE.Snippets.envmap_fragment,THREE.Snippets.fog_fragment,"}"].join("\n"),vertex_shader:[THREE.Snippets.map_pars_vertex,THREE.Snippets.envmap_pars_vertex,"void main() {\nvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",THREE.Snippets.map_vertex,THREE.Snippets.envmap_vertex,"gl_Position = projectionMatrix * mvPosition;\n}"].join("\n")},lambert:{uniforms:Uniforms.merge([THREE.UniformsLib.common,THREE.UniformsLib.lights]),fragment_shader:["uniform vec3 color;\nuniform float opacity;\nvarying vec3 vLightWeighting;", +THREE.Snippets.map_pars_fragment,THREE.Snippets.envmap_pars_fragment,THREE.Snippets.fog_pars_fragment,"void main() {\nvec4 mColor = vec4( color, opacity );\nvec4 mapColor = vec4( 1.0 );\nvec4 cubeColor = vec4( 1.0 );",THREE.Snippets.map_fragment,"gl_FragColor = mColor * mapColor * vec4( vLightWeighting, 1.0 );",THREE.Snippets.envmap_fragment,THREE.Snippets.fog_fragment,"}"].join("\n"),vertex_shader:["varying vec3 vLightWeighting;",THREE.Snippets.map_pars_vertex,THREE.Snippets.envmap_pars_vertex, +THREE.Snippets.lights_pars_vertex,"void main() {\nvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",THREE.Snippets.map_vertex,THREE.Snippets.envmap_vertex,"vec3 transformedNormal = normalize( normalMatrix * normal );",THREE.Snippets.lights_vertex,"gl_Position = projectionMatrix * mvPosition;\n}"].join("\n")},phong:{uniforms:Uniforms.merge([THREE.UniformsLib.common,THREE.UniformsLib.lights,{ambient:{type:"c",value:new THREE.Color(328965)},specular:{type:"c",value:new THREE.Color(1118481)}, +shininess:{type:"f",value:30}}]),fragment_shader:["uniform vec3 color;\nuniform float opacity;\nuniform vec3 ambient;\nuniform vec3 specular;\nuniform float shininess;\nvarying vec3 vLightWeighting;",THREE.Snippets.map_pars_fragment,THREE.Snippets.envmap_pars_fragment,THREE.Snippets.fog_pars_fragment,THREE.Snippets.lights_pars_fragment,"void main() {\nvec4 mColor = vec4( color, opacity );\nvec4 mapColor = vec4( 1.0 );\nvec4 cubeColor = vec4( 1.0 );",THREE.Snippets.map_fragment,THREE.Snippets.lights_fragment, +"gl_FragColor = mapColor * totalLight * vec4( vLightWeighting, 1.0 );",THREE.Snippets.envmap_fragment,THREE.Snippets.fog_fragment,"}"].join("\n"),vertex_shader:["#define PHONG\nvarying vec3 vLightWeighting;\nvarying vec3 vViewPosition;\nvarying vec3 vNormal;",THREE.Snippets.map_pars_vertex,THREE.Snippets.envmap_pars_vertex,THREE.Snippets.lights_pars_vertex,"void main() {\nvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",THREE.Snippets.map_vertex,THREE.Snippets.envmap_vertex,"#ifndef USE_ENVMAP\nvec4 mPosition = objectMatrix * vec4( position, 1.0 );\n#endif\nvViewPosition = cameraPosition - mPosition.xyz;\nvec3 transformedNormal = normalize( normalMatrix * normal );\nvNormal = transformedNormal;", +THREE.Snippets.lights_vertex,"gl_Position = projectionMatrix * mvPosition;\n}"].join("\n")}};THREE.RenderableObject=function(){this.z=this.object=null};THREE.RenderableFace3=function(){this.z=null;this.v1=new THREE.Vertex;this.v2=new THREE.Vertex;this.v3=new THREE.Vertex;this.centroidWorld=new THREE.Vector3;this.centroidScreen=new THREE.Vector3;this.normalWorld=new THREE.Vector3;this.vertexNormalsWorld=[];this.faceMaterials=this.meshMaterials=null;this.overdraw=false;this.uvs=[null,null,null]}; +THREE.RenderableParticle=function(){this.rotation=this.z=this.y=this.x=null;this.scale=new THREE.Vector2;this.materials=null};THREE.RenderableLine=function(){this.z=null;this.v1=new THREE.Vertex;this.v2=new THREE.Vertex;this.materials=null}; diff --git a/extlib/thingiview.js/binaryReader.js b/extlib/thingiview.js/binaryReader.js new file mode 100644 index 00000000..f99a2379 --- /dev/null +++ b/extlib/thingiview.js/binaryReader.js @@ -0,0 +1,126 @@ +// BinaryReader +// Refactored by Vjeux <vjeuxx@gmail.com> +// http://blog.vjeux.com/2010/javascript/javascript-binary-reader.html + +// Original +//+ Jonas Raoni Soares Silva +//@ http://jsfromhell.com/classes/binary-parser [rev. #1] + +BinaryReader = function (data) { + this._buffer = data; + this._pos = 0; +}; + +BinaryReader.prototype = { + + /* Public */ + + readInt8: function (){ return this._decodeInt(8, true); }, + readUInt8: function (){ return this._decodeInt(8, false); }, + readInt16: function (){ return this._decodeInt(16, true); }, + readUInt16: function (){ return this._decodeInt(16, false); }, + readInt32: function (){ return this._decodeInt(32, true); }, + readUInt32: function (){ return this._decodeInt(32, false); }, + + readFloat: function (){ return this._decodeFloat(23, 8); }, + readDouble: function (){ return this._decodeFloat(52, 11); }, + + readChar: function () { return this.readString(1); }, + readString: function (length) { + this._checkSize(length * 8); + var result = this._buffer.substr(this._pos, length); + this._pos += length; + return result; + }, + + seek: function (pos) { + this._pos = pos; + this._checkSize(0); + }, + + getPosition: function () { + return this._pos; + }, + + getSize: function () { + return this._buffer.length; + }, + + + /* Private */ + + _decodeFloat: function(precisionBits, exponentBits){ + var length = precisionBits + exponentBits + 1; + var size = length >> 3; + this._checkSize(length); + + var bias = Math.pow(2, exponentBits - 1) - 1; + var signal = this._readBits(precisionBits + exponentBits, 1, size); + var exponent = this._readBits(precisionBits, exponentBits, size); + var significand = 0; + var divisor = 2; + // var curByte = length + (-precisionBits >> 3) - 1; + var curByte = 0; + do { + var byteValue = this._readByte(++curByte, size); + var startBit = precisionBits % 8 || 8; + var mask = 1 << startBit; + while (mask >>= 1) { + if (byteValue & mask) { + significand += 1 / divisor; + } + divisor *= 2; + } + } while (precisionBits -= startBit); + + this._pos += size; + + return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity + : (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand + : Math.pow(2, exponent - bias) * (1 + significand) : 0); + }, + + _decodeInt: function(bits, signed){ + var x = this._readBits(0, bits, bits / 8), max = Math.pow(2, bits); + var result = signed && x >= max / 2 ? x - max : x; + + this._pos += bits / 8; + return result; + }, + + //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) + _shl: function (a, b){ + for (++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1); + return a; + }, + + _readByte: function (i, size) { + return this._buffer.charCodeAt(this._pos + size - i - 1) & 0xff; + }, + + _readBits: function (start, length, size) { + var offsetLeft = (start + length) % 8; + var offsetRight = start % 8; + var curByte = size - (start >> 3) - 1; + var lastByte = size + (-(start + length) >> 3); + var diff = curByte - lastByte; + + var sum = (this._readByte(curByte, size) >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1); + + if (diff && offsetLeft) { + sum += (this._readByte(lastByte++, size) & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight; + } + + while (diff) { + sum += this._shl(this._readByte(lastByte++, size), (diff-- << 3) - offsetRight); + } + + return sum; + }, + + _checkSize: function (neededBits) { + if (!(this._pos + Math.ceil(neededBits / 8) < this._buffer.length)) { + throw new Error("Index out of bound"); + } + } +};
\ No newline at end of file diff --git a/extlib/thingiview.js/plane.js b/extlib/thingiview.js/plane.js new file mode 100644 index 00000000..9f970be0 --- /dev/null +++ b/extlib/thingiview.js/plane.js @@ -0,0 +1,62 @@ +/** + * @author mr.doob / http://mrdoob.com/ + * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as + */ + +var Plane = function ( width, height, segments_width, segments_height ) { + + THREE.Geometry.call( this ); + + var ix, iy, + width_half = width / 2, + height_half = height / 2, + gridX = segments_width || 1, + gridY = segments_height || 1, + gridX1 = gridX + 1, + gridY1 = gridY + 1, + segment_width = width / gridX, + segment_height = height / gridY; + + + for( iy = 0; iy < gridY1; iy++ ) { + + for( ix = 0; ix < gridX1; ix++ ) { + + var x = ix * segment_width - width_half; + var y = iy * segment_height - height_half; + + this.vertices.push( new THREE.Vertex( new THREE.Vector3( x, - y, 0 ) ) ); + + } + + } + + for( iy = 0; iy < gridY; iy++ ) { + + for( ix = 0; ix < gridX; ix++ ) { + + var a = ix + gridX1 * iy; + var b = ix + gridX1 * ( iy + 1 ); + var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + var d = ( ix + 1 ) + gridX1 * iy; + + this.faces.push( new THREE.Face4( a, b, c, d ) ); + this.uvs.push( [ + new THREE.UV( ix / gridX, iy / gridY ), + new THREE.UV( ix / gridX, ( iy + 1 ) / gridY ), + new THREE.UV( ( ix + 1 ) / gridX, ( iy + 1 ) / gridY ), + new THREE.UV( ( ix + 1 ) / gridX, iy / gridY ) + ] ); + + } + + } + + this.computeCentroids(); + this.computeFaceNormals(); + this.sortFacesByMaterial(); + +}; + +Plane.prototype = new THREE.Geometry(); +Plane.prototype.constructor = Plane; diff --git a/extlib/thingiview.js/stats.js b/extlib/thingiview.js/stats.js new file mode 100644 index 00000000..270d1ce3 --- /dev/null +++ b/extlib/thingiview.js/stats.js @@ -0,0 +1,2 @@ +// stats.js r5 - http://github.com/mrdoob/stats.js +var Stats=function(){var j=0,u=2,r,C=0,E=new Date().getTime(),w=E,f=E,m=0,e=1000,i=0,F,q,c,d,B,k=0,G=1000,a=0,A,t,p,D,l,v=0,o=1000,s=0,h,n,z,g,b,y={fps:{bg:{r:16,g:16,b:48},fg:{r:0,g:255,b:255}},ms:{bg:{r:16,g:48,b:16},fg:{r:0,g:255,b:0}},mem:{bg:{r:48,g:16,b:26},fg:{r:255,g:0,b:128}}};r=document.createElement("div");r.style.fontFamily="Helvetica, Arial, sans-serif";r.style.textAlign="left";r.style.fontSize="9px";r.style.opacity="0.9";r.style.width="80px";r.style.cursor="pointer";r.addEventListener("click",H,false);F=document.createElement("div");F.style.backgroundColor="rgb("+Math.floor(y.fps.bg.r/2)+","+Math.floor(y.fps.bg.g/2)+","+Math.floor(y.fps.bg.b/2)+")";F.style.padding="2px 0px 3px 0px";r.appendChild(F);q=document.createElement("div");q.innerHTML="<strong>FPS</strong>";q.style.color="rgb("+y.fps.fg.r+","+y.fps.fg.g+","+y.fps.fg.b+")";q.style.margin="0px 0px 1px 3px";F.appendChild(q);c=document.createElement("canvas");c.width=74;c.height=30;c.style.display="block";c.style.marginLeft="3px";F.appendChild(c);d=c.getContext("2d");d.fillStyle="rgb("+y.fps.bg.r+","+y.fps.bg.g+","+y.fps.bg.b+")";d.fillRect(0,0,c.width,c.height);B=d.getImageData(0,0,c.width,c.height);A=document.createElement("div");A.style.backgroundColor="rgb("+Math.floor(y.ms.bg.r/2)+","+Math.floor(y.ms.bg.g/2)+","+Math.floor(y.ms.bg.b/2)+")";A.style.padding="2px 0px 3px 0px";A.style.display="none";r.appendChild(A);t=document.createElement("div");t.innerHTML="<strong>MS</strong>";t.style.color="rgb("+y.ms.fg.r+","+y.ms.fg.g+","+y.ms.fg.b+")";t.style.margin="0px 0px 1px 3px";A.appendChild(t);p=document.createElement("canvas");p.width=74;p.height=30;p.style.display="block";p.style.marginLeft="3px";A.appendChild(p);D=p.getContext("2d");D.fillStyle="rgb("+y.ms.bg.r+","+y.ms.bg.g+","+y.ms.bg.b+")";D.fillRect(0,0,p.width,p.height);l=D.getImageData(0,0,p.width,p.height);try{if(webkitPerformance&&webkitPerformance.memory.totalJSHeapSize){u=3}}catch(x){}h=document.createElement("div");h.style.backgroundColor="rgb("+Math.floor(y.mem.bg.r/2)+","+Math.floor(y.mem.bg.g/2)+","+Math.floor(y.mem.bg.b/2)+")";h.style.padding="2px 0px 3px 0px";h.style.display="none";r.appendChild(h);n=document.createElement("div");n.innerHTML="<strong>MEM</strong>";n.style.color="rgb("+y.mem.fg.r+","+y.mem.fg.g+","+y.mem.fg.b+")";n.style.margin="0px 0px 1px 3px";h.appendChild(n);z=document.createElement("canvas");z.width=74;z.height=30;z.style.display="block";z.style.marginLeft="3px";h.appendChild(z);g=z.getContext("2d");g.fillStyle="#301010";g.fillRect(0,0,z.width,z.height);b=g.getImageData(0,0,z.width,z.height);function I(N,M,K){var J,O,L;for(O=0;O<30;O++){for(J=0;J<73;J++){L=(J+O*74)*4;N[L]=N[L+4];N[L+1]=N[L+5];N[L+2]=N[L+6]}}for(O=0;O<30;O++){L=(73+O*74)*4;if(O<M){N[L]=y[K].bg.r;N[L+1]=y[K].bg.g;N[L+2]=y[K].bg.b}else{N[L]=y[K].fg.r;N[L+1]=y[K].fg.g;N[L+2]=y[K].fg.b}}}function H(){j++;j==u?j=0:j;F.style.display="none";A.style.display="none";h.style.display="none";switch(j){case 0:F.style.display="block";break;case 1:A.style.display="block";break;case 2:h.style.display="block";break}}return{domElement:r,update:function(){C++;E=new Date().getTime();k=E-w;G=Math.min(G,k);a=Math.max(a,k);I(l.data,Math.min(30,30-(k/200)*30),"ms");t.innerHTML="<strong>"+k+" MS</strong> ("+G+"-"+a+")";D.putImageData(l,0,0);w=E;if(E>f+1000){m=Math.round((C*1000)/(E-f));e=Math.min(e,m);i=Math.max(i,m);I(B.data,Math.min(30,30-(m/100)*30),"fps");q.innerHTML="<strong>"+m+" FPS</strong> ("+e+"-"+i+")";d.putImageData(B,0,0);if(u==3){v=webkitPerformance.memory.usedJSHeapSize*9.54e-7;o=Math.min(o,v);s=Math.max(s,v);I(b.data,Math.min(30,30-(v/2)),"mem");n.innerHTML="<strong>"+Math.round(v)+" MEM</strong> ("+Math.round(o)+"-"+Math.round(s)+")";g.putImageData(b,0,0)}f=E;C=0}}}};
\ No newline at end of file diff --git a/extlib/thingiview.js/thingiloader.js b/extlib/thingiview.js/thingiloader.js new file mode 100644 index 00000000..3791a49c --- /dev/null +++ b/extlib/thingiview.js/thingiloader.js @@ -0,0 +1,318 @@ +Thingiloader = function(event) { + // Code from https://developer.mozilla.org/En/Using_XMLHttpRequest#Receiving_binary_data + this.load_binary_resource = function(url) { + var req = new XMLHttpRequest(); + req.open('GET', url, false); + // The following line says we want to receive data as Binary and not as Unicode + req.overrideMimeType('text/plain; charset=x-user-defined'); + req.send(null); + if (req.status != 200) return ''; + + return req.responseText; + }; + + this.loadSTL = function(url) { + var looksLikeBinary = function(reader) { + // STL files don't specify a way to distinguish ASCII from binary. + // The usual way is checking for "solid" at the start of the file -- + // but Thingiverse has seen at least one binary STL file in the wild + // that breaks this. + + // The approach here is different: binary STL files contain a triangle + // count early in the file. If this correctly predicts the file's length, + // it is most probably a binary STL file. + + reader.seek(80); // skip the header + var count = reader.readUInt32(); + + var predictedSize = 80 /* header */ + 4 /* count */ + 50 * count; + return reader.getSize() == predictedSize; + }; + + workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); + var file = this.load_binary_resource(url); + var reader = new BinaryReader(file); + + if (looksLikeBinary(reader)) { + this.loadSTLBinary(reader); + } else { + this.loadSTLString(file); + } + }; + + this.loadOBJ = function(url) { + workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); + var file = this.load_binary_resource(url); + this.loadOBJString(file); + }; + + this.loadJSON = function(url) { + workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); + var file = this.load_binary_resource(url); + this.loadJSONString(file); + }; + + this.loadPLY = function(url) { + workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); + + var file = this.load_binary_resource(url); + + if (file.match(/format ascii/i)) { + this.loadPLYString(file); + } else { + this.loadPLYBinary(file); + } + }; + + this.loadSTLString = function(STLString) { + workerFacadeMessage({'status':'message', 'content':'Parsing STL String...'}); + workerFacadeMessage({'status':'complete', 'content':this.ParseSTLString(STLString)}); + }; + + this.loadSTLBinary = function(STLBinary) { + workerFacadeMessage({'status':'message', 'content':'Parsing STL Binary...'}); + workerFacadeMessage({'status':'complete', 'content':this.ParseSTLBinary(STLBinary)}); + }; + + this.loadOBJString = function(OBJString) { + workerFacadeMessage({'status':'message', 'content':'Parsing OBJ String...'}); + workerFacadeMessage({'status':'complete', 'content':this.ParseOBJString(OBJString)}); + }; + + this.loadJSONString = function(JSONString) { + workerFacadeMessage({'status':'message', 'content':'Parsing JSON String...'}); + workerFacadeMessage({'status':'complete', 'content':eval(JSONString)}); + }; + + this.loadPLYString = function(PLYString) { + workerFacadeMessage({'status':'message', 'content':'Parsing PLY String...'}); + workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYString(PLYString)}); + }; + + this.loadPLYBinary = function(PLYBinary) { + workerFacadeMessage({'status':'message', 'content':'Parsing PLY Binary...'}); + workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYBinary(PLYBinary)}); + }; + + this.ParsePLYString = function(input) { + var properties = []; + var vertices = []; + var colors = []; + + var vertex_count = 0; + + var header = /ply\n([\s\S]+)\nend_header/ig.exec(input)[1]; + var data = /end_header\n([\s\S]+)$/ig.exec(input)[1]; + + // workerFacadeMessage({'status':'message', 'content':'header:\n' + header}); + // workerFacadeMessage({'status':'message', 'content':'data:\n' + data}); + + header_parts = header.split("\n"); + + for (i in header_parts) { + if (/element vertex/i.test(header_parts[i])) { + vertex_count = /element vertex (\d+)/i.exec(header_parts[i])[1]; + } else if (/property/i.test(header_parts[i])) { + properties.push(/property (.*) (.*)/i.exec(header_parts[i])[2]); + } + } + + // workerFacadeMessage({'status':'message', 'content':'properties: ' + properties}); + + data_parts = data.split("\n"); + + for (i in data_parts) { + data_line = data_parts[i]; + data_line_parts = data_line.split(" "); + + vertices.push([ + parseFloat(data_line_parts[properties.indexOf("x")]), + parseFloat(data_line_parts[properties.indexOf("y")]), + parseFloat(data_line_parts[properties.indexOf("z")]) + ]); + + colors.push([ + parseInt(data_line_parts[properties.indexOf("red")]), + parseInt(data_line_parts[properties.indexOf("green")]), + parseInt(data_line_parts[properties.indexOf("blue")]) + ]); + } + + // workerFacadeMessage({'status':'message', 'content':'vertices: ' + vertices}); + + return [vertices, colors]; + }; + + this.ParsePLYBinary = function(input) { + return false; + }; + + this.ParseSTLBinary = function(input) { + // Skip the header. + input.seek(80); + + // Load the number of vertices. + var count = input.readUInt32(); + + // During the parse loop we maintain the following data structures: + var vertices = []; // Append-only list of all unique vertices. + var vert_hash = {}; // Mapping from vertex to index in 'vertices', above. + var faces = []; // List of triangle descriptions, each a three-element + // list of indices in 'vertices', above. + + for (var i = 0; i < count; i++) { + if (i % 100 == 0) { + workerFacadeMessage({ + 'status':'message', + 'content':'Parsing ' + (i+1) + ' of ' + count + ' polygons...' + }); + workerFacadeMessage({ + 'status':'progress', + 'content':parseInt(i / count * 100) + '%' + }); + } + + // Skip the normal (3 single-precision floats) + input.seek(input.getPosition() + 12); + + var face_indices = []; + for (var x = 0; x < 3; x++) { + var vertex = [input.readFloat(), input.readFloat(), input.readFloat()]; + + var vertexIndex = vert_hash[vertex]; + if (vertexIndex == null) { + vertexIndex = vertices.length; + vertices.push(vertex); + vert_hash[vertex] = vertexIndex; + } + + face_indices.push(vertexIndex); + } + faces.push(face_indices); + + // Skip the "attribute" field (unused in common models) + input.readUInt16(); + } + + return [vertices, faces]; + }; + + // build stl's vertex and face arrays + this.ParseSTLString = function(STLString) { + var vertexes = []; + var faces = []; + + var face_vertexes = []; + var vert_hash = {} + + // console.log(STLString); + + // strip out extraneous stuff + STLString = STLString.replace(/\r/, "\n"); + STLString = STLString.replace(/^solid[^\n]*/, ""); + STLString = STLString.replace(/\n/g, " "); + STLString = STLString.replace(/facet normal /g,""); + STLString = STLString.replace(/outer loop/g,""); + STLString = STLString.replace(/vertex /g,""); + STLString = STLString.replace(/endloop/g,""); + STLString = STLString.replace(/endfacet/g,""); + STLString = STLString.replace(/endsolid[^\n]*/, ""); + STLString = STLString.replace(/\s+/g, " "); + STLString = STLString.replace(/^\s+/, ""); + + // console.log(STLString); + + var facet_count = 0; + var block_start = 0; + + var points = STLString.split(" "); + + workerFacadeMessage({'status':'message', 'content':'Parsing vertices...'}); + for (var i=0; i<points.length/12-1; i++) { + if ((i % 100) == 0) { + workerFacadeMessage({'status':'progress', 'content':parseInt(i / (points.length/12-1) * 100) + '%'}); + } + + var face_indices = []; + for (var x=0; x<3; x++) { + var vertex = [parseFloat(points[block_start+x*3+3]), parseFloat(points[block_start+x*3+4]), parseFloat(points[block_start+x*3+5])]; + + var vertexIndex = vert_hash[vertex]; + if (vertexIndex == null) { + vertexIndex = vertexes.length; + vertexes.push(vertex); + vert_hash[vertex] = vertexIndex; + } + + face_indices.push(vertexIndex); + } + faces.push(face_indices); + + block_start = block_start + 12; + } + + return [vertexes, faces]; + }; + + this.ParseOBJString = function(OBJString) { + var vertexes = []; + var faces = []; + + var lines = OBJString.split("\n"); + + // var normal_position = 0; + + for (var i=0; i<lines.length; i++) { + workerFacadeMessage({'status':'progress', 'content':parseInt(i / lines.length * 100) + '%'}); + + line_parts = lines[i].replace(/\s+/g, " ").split(" "); + + if (line_parts[0] == "v") { + vertexes.push([parseFloat(line_parts[1]), parseFloat(line_parts[2]), parseFloat(line_parts[3])]); + } else if (line_parts[0] == "f") { + faces.push([parseFloat(line_parts[1].split("/")[0])-1, parseFloat(line_parts[2].split("/")[0])-1, parseFloat(line_parts[3].split("/")[0]-1), 0]) + } + } + + return [vertexes, faces]; + }; + + switch(event.data.cmd) { + case "loadSTL": + this.loadSTL(event.data.param); + break; + case "loadSTLString": + this.loadSTLString(event.data.param); + break; + case "loadSTLBinary": + this.loadSTLBinary(event.data.param); + break; + case "loadOBJ": + this.loadOBJ(event.data.param); + break; + case "loadOBJString": + this.loadOBJString(event.data.param); + break; + case "loadJSON": + this.loadJSON(event.data.param); + break; + case "loadPLY": + this.loadPLY(event.data.param); + break; + case "loadPLYString": + this.loadPLYString(event.data.param); + break; + case "loadPLYBinary": + this.loadPLYBinary(event.data.param); + break; + } + +}; + +if (typeof(window) === "undefined") { + onmessage = Thingiloader; + workerFacadeMessage = postMessage; + importScripts('binaryReader.js'); +} else { + workerFacadeMessage = WorkerFacade.add(thingiurlbase + "/thingiloader.js", Thingiloader); +} diff --git a/extlib/thingiview.js/thingiview.js b/extlib/thingiview.js/thingiview.js new file mode 100644 index 00000000..5eb30335 --- /dev/null +++ b/extlib/thingiview.js/thingiview.js @@ -0,0 +1,898 @@ +Thingiview = function(containerId) { + scope = this; + + this.containerId = containerId; + var container = document.getElementById(containerId); + + // var stats = null; + var camera = null; + var scene = null; + var renderer = null; + var object = null; + var plane = null; + + var ambientLight = null; + var directionalLight = null; + var pointLight = null; + + var targetXRotation = 0; + var targetXRotationOnMouseDown = 0; + var mouseX = 0; + var mouseXOnMouseDown = 0; + + var targetYRotation = 0; + var targetYRotationOnMouseDown = 0; + var mouseY = 0; + var mouseYOnMouseDown = 0; + + var mouseDown = false; + var mouseOver = false; + + var windowHalfX = window.innerWidth / 2; + var windowHalfY = window.innerHeight / 2 + + var view = null; + var infoMessage = null; + var progressBar = null; + var alertBox = null; + + var timer = null; + + var rotateTimer = null; + var rotateListener = null; + var wasRotating = null; + + var cameraView = 'diagonal'; + var cameraZoom = 0; + var rotate = false; + var backgroundColor = '#606060'; + var objectMaterial = 'solid'; + var objectColor = 0xffffff; + var showPlane = true; + var isWebGl = false; + + if (document.defaultView && document.defaultView.getComputedStyle) { + var width = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('width')); + var height = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('height')); + } else { + var width = parseFloat(container.currentStyle.width); + var height = parseFloat(container.currentStyle.height); + } + + var geometry; + + this.initScene = function() { + container.style.position = 'relative'; + container.innerHTML = ''; + + camera = new THREE.Camera(45, width/ height, 1, 100000); + camera.updateMatrix(); + + scene = new THREE.Scene(); + + ambientLight = new THREE.AmbientLight(0x202020); + scene.addLight(ambientLight); + + directionalLight = new THREE.DirectionalLight(0xffffff, 0.75); + directionalLight.position.x = 1; + directionalLight.position.y = 1; + directionalLight.position.z = 2; + directionalLight.position.normalize(); + scene.addLight(directionalLight); + + pointLight = new THREE.PointLight(0xffffff, 0.3); + pointLight.position.x = 0; + pointLight.position.y = -25; + pointLight.position.z = 10; + scene.addLight(pointLight); + + progressBar = document.createElement('div'); + progressBar.style.position = 'absolute'; + progressBar.style.top = '0px'; + progressBar.style.left = '0px'; + progressBar.style.backgroundColor = 'red'; + progressBar.style.padding = '5px'; + progressBar.style.display = 'none'; + progressBar.style.overflow = 'visible'; + progressBar.style.whiteSpace = 'nowrap'; + progressBar.style.zIndex = 100; + container.appendChild(progressBar); + + alertBox = document.createElement('div'); + alertBox.id = 'alertBox'; + alertBox.style.position = 'absolute'; + alertBox.style.top = '25%'; + alertBox.style.left = '25%'; + alertBox.style.width = '50%'; + alertBox.style.height = '50%'; + alertBox.style.backgroundColor = '#dddddd'; + alertBox.style.padding = '10px'; + // alertBox.style.overflowY = 'scroll'; + alertBox.style.display = 'none'; + alertBox.style.zIndex = 100; + container.appendChild(alertBox); + + // load a blank object + // this.loadSTLString(''); + + if (showPlane) { + loadPlaneGeometry(); + } + + this.setCameraView(cameraView); + this.setObjectMaterial(objectMaterial); + + testCanvas = document.createElement('canvas'); + try { + if (testCanvas.getContext('experimental-webgl')) { + // showPlane = false; + isWebGl = true; + renderer = new THREE.WebGLRenderer(); + // renderer = new THREE.CanvasRenderer(); + } else { + renderer = new THREE.CanvasRenderer(); + } + } catch(e) { + renderer = new THREE.CanvasRenderer(); + // log("failed webgl detection"); + } + + // renderer.setSize(container.innerWidth, container.innerHeight); + + renderer.setSize(width, height); + renderer.domElement.style.backgroundColor = backgroundColor; + container.appendChild(renderer.domElement); + + // stats = new Stats(); + // stats.domElement.style.position = 'absolute'; + // stats.domElement.style.top = '0px'; + // container.appendChild(stats.domElement); + + // TODO: figure out how to get the render window to resize when window resizes + // window.addEventListener('resize', onContainerResize(), false); + // container.addEventListener('resize', onContainerResize(), false); + + // renderer.domElement.addEventListener('mousemove', onRendererMouseMove, false); + window.addEventListener('mousemove', onRendererMouseMove, false); + renderer.domElement.addEventListener('mouseover', onRendererMouseOver, false); + renderer.domElement.addEventListener('mouseout', onRendererMouseOut, false); + renderer.domElement.addEventListener('mousedown', onRendererMouseDown, false); + // renderer.domElement.addEventListener('mouseup', onRendererMouseUp, false); + window.addEventListener('mouseup', onRendererMouseUp, false); + + renderer.domElement.addEventListener('touchstart', onRendererTouchStart, false); + renderer.domElement.addEventListener('touchend', onRendererTouchEnd, false); + renderer.domElement.addEventListener('touchmove', onRendererTouchMove, false); + + renderer.domElement.addEventListener('DOMMouseScroll', onRendererScroll, false); + renderer.domElement.addEventListener('mousewheel', onRendererScroll, false); + renderer.domElement.addEventListener('gesturechange', onRendererGestureChange, false); + } + + // FIXME + // onContainerResize = function(event) { + // width = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('width')); + // height = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('height')); + // + // // log("resized width: " + width + ", height: " + height); + // + // if (renderer) { + // renderer.setSize(width, height); + // camera.projectionMatrix = THREE.Matrix4.makePerspective(70, width / height, 1, 10000); + // sceneLoop(); + // } + // }; + + onRendererScroll = function(event) { + event.preventDefault(); + + var rolled = 0; + + if (event.wheelDelta === undefined) { + // Firefox + // The measurement units of the detail and wheelDelta properties are different. + rolled = -40 * event.detail; + } else { + rolled = event.wheelDelta; + } + + if (rolled > 0) { + // up + scope.setCameraZoom(+10); + } else { + // down + scope.setCameraZoom(-10); + } + } + + onRendererGestureChange = function(event) { + event.preventDefault(); + + if (event.scale > 1) { + scope.setCameraZoom(+5); + } else { + scope.setCameraZoom(-5); + } + } + + onRendererMouseOver = function(event) { + mouseOver = true; + // targetRotation = object.rotation.z; + if (timer == null) { + // log('starting loop'); + timer = setInterval(sceneLoop, 1000/60); + } + } + + onRendererMouseDown = function(event) { + // log("down"); + + event.preventDefault(); + mouseDown = true; + + if(scope.getRotation()){ + wasRotating = true; + scope.setRotation(false); + } else { + wasRotating = false; + } + + mouseXOnMouseDown = event.clientX - windowHalfX; + mouseYOnMouseDown = event.clientY - windowHalfY; + + targetXRotationOnMouseDown = targetXRotation; + targetYRotationOnMouseDown = targetYRotation; + } + + onRendererMouseMove = function(event) { + // log("move"); + + if (mouseDown) { + mouseX = event.clientX - windowHalfX; + // targetXRotation = targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02; + xrot = targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02; + + mouseY = event.clientY - windowHalfY; + // targetYRotation = targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.02; + yrot = targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.02; + + targetXRotation = xrot; + targetYRotation = yrot; + } + } + + onRendererMouseUp = function(event) { + // log("up"); + if (mouseDown) { + mouseDown = false; + if (!mouseOver) { + clearInterval(timer); + timer = null; + } + if (wasRotating) { + scope.setRotation(true); + } + } + } + + onRendererMouseOut = function(event) { + if (!mouseDown) { + clearInterval(timer); + timer = null; + } + mouseOver = false; + } + + onRendererTouchStart = function(event) { + targetXRotation = object.rotation.z; + targetYRotation = object.rotation.x; + + timer = setInterval(sceneLoop, 1000/60); + + if (event.touches.length == 1) { + event.preventDefault(); + + mouseXOnMouseDown = event.touches[0].pageX - windowHalfX; + targetXRotationOnMouseDown = targetXRotation; + + mouseYOnMouseDown = event.touches[0].pageY - windowHalfY; + targetYRotationOnMouseDown = targetYRotation; + } + } + + onRendererTouchEnd = function(event) { + clearInterval(timer); + timer = null; + // targetXRotation = object.rotation.z; + // targetYRotation = object.rotation.x; + } + + onRendererTouchMove = function(event) { + if (event.touches.length == 1) { + event.preventDefault(); + + mouseX = event.touches[0].pageX - windowHalfX; + targetXRotation = targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05; + + mouseY = event.touches[0].pageY - windowHalfY; + targetYRotation = targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.05; + } + } + + sceneLoop = function() { + if (object) { + // if (view == 'bottom') { + // if (showPlane) { + // plane.rotation.z = object.rotation.z -= (targetRotation + object.rotation.z) * 0.05; + // } else { + // object.rotation.z -= (targetRotation + object.rotation.z) * 0.05; + // } + // } else { + // if (showPlane) { + // plane.rotation.z = object.rotation.z += (targetRotation - object.rotation.z) * 0.05; + // } else { + // object.rotation.z += (targetRotation - object.rotation.z) * 0.05; + // } + // } + + if (showPlane) { + plane.rotation.z = object.rotation.z = (targetXRotation - object.rotation.z) * 0.2; + plane.rotation.x = object.rotation.x = (targetYRotation - object.rotation.x) * 0.2; + } else { + object.rotation.z = (targetXRotation - object.rotation.z) * 0.2; + object.rotation.x = (targetYRotation - object.rotation.x) * 0.2; + } + + // log(object.rotation.x); + + camera.updateMatrix(); + object.updateMatrix(); + + if (showPlane) { + plane.updateMatrix(); + } + + renderer.render(scene, camera); + // stats.update(); + } + } + + rotateLoop = function() { + // targetRotation += 0.01; + targetXRotation += 0.05; + sceneLoop(); + } + + this.getShowPlane = function(){ + return showPlane; + } + + this.setShowPlane = function(show) { + showPlane = show; + + if (show) { + if (scene && !plane) { + loadPlaneGeometry(); + } + plane.material[0].opacity = 1; + // plane.updateMatrix(); + } else { + if (scene && plane) { + // alert(plane.material[0].opacity); + plane.material[0].opacity = 0; + // plane.updateMatrix(); + } + } + + sceneLoop(); + } + + this.getRotation = function() { + return rotateTimer !== null; + } + + this.resetRotation = function () { + if (rotate) { + this.setRotation(false); + this.setRotation(true); + } + } + + this.setRotation = function(rotate) { + rotation = rotate; + + if (rotate) { + rotateTimer = setInterval(rotateLoop, 1000/60); + } else { + clearInterval(rotateTimer); + rotateTimer = null; + } + + scope.onSetRotation(); + } + + this.onSetRotation = function(callback) { + if(callback === undefined){ + if(rotateListener !== null){ + try{ + rotateListener(scope.getRotation()); + } catch(ignored) {} + } + } else { + rotateListener = callback; + } + } + + this.setCameraView = function(dir) { + cameraView = dir; + + targetXRotation = 0; + targetYRotation = 0; + + if (object) { + object.rotation.x = 0; + object.rotation.y = 0; + object.rotation.z = 0; + } + + if (showPlane && object) { + plane.rotation.x = object.rotation.x; + plane.rotation.y = object.rotation.y; + plane.rotation.z = object.rotation.z; + } + + if (dir == 'top') { + // camera.position.y = 0; + // camera.position.z = 100; + // camera.target.position.z = 0; + if (showPlane) { + plane.flipSided = false; + } + } else if (dir == 'side') { + // camera.position.y = -70; + // camera.position.z = 70; + // camera.target.position.z = 0; + targetYRotation = -4.5; + if (showPlane) { + plane.flipSided = false; + } + } else if (dir == 'bottom') { + // camera.position.y = 0; + // camera.position.z = -100; + // camera.target.position.z = 0; + if (showPlane) { + plane.flipSided = true; + } + } else { + // camera.position.y = -70; + // camera.position.z = 70; + // camera.target.position.z = 0; + if (showPlane) { + plane.flipSided = false; + } + } + + mouseX = targetXRotation; + mouseXOnMouseDown = targetXRotation; + + mouseY = targetYRotation; + mouseYOnMouseDown = targetYRotation; + + scope.centerCamera(); + + sceneLoop(); + } + + this.setCameraZoom = function(factor) { + cameraZoom = factor; + + if (cameraView == 'bottom') { + if (camera.position.z + factor > 0) { + factor = 0; + } + } else { + if (camera.position.z - factor < 0) { + factor = 0; + } + } + + if (cameraView == 'top') { + camera.position.z -= factor; + } else if (cameraView == 'bottom') { + camera.position.z += factor; + } else if (cameraView == 'side') { + camera.position.y += factor; + camera.position.z -= factor; + } else { + camera.position.y += factor; + camera.position.z -= factor; + } + + sceneLoop(); + } + + this.getObjectMaterial = function() { + return objectMaterial; + } + + this.setObjectMaterial = function(type) { + objectMaterial = type; + + loadObjectGeometry(); + } + + this.setBackgroundColor = function(color) { + backgroundColor = color + + if (renderer) { + renderer.domElement.style.backgroundColor = color; + } + } + + this.setObjectColor = function(color) { + objectColor = parseInt(color.replace(/\#/g, ''), 16); + + loadObjectGeometry(); + } + + this.loadSTL = function(url) { + scope.newWorker('loadSTL', url); + } + + this.loadOBJ = function(url) { + scope.newWorker('loadOBJ', url); + } + + this.loadSTLString = function(STLString) { + scope.newWorker('loadSTLString', STLString); + } + + this.loadSTLBinary = function(STLBinary) { + scope.newWorker('loadSTLBinary', STLBinary); + } + + this.loadOBJString = function(OBJString) { + scope.newWorker('loadOBJString', OBJString); + } + + this.loadJSON = function(url) { + scope.newWorker('loadJSON', url); + } + + this.loadPLY = function(url) { + scope.newWorker('loadPLY', url); + } + + this.loadPLYString = function(PLYString) { + scope.newWorker('loadPLYString', PLYString); + } + + this.loadPLYBinary = function(PLYBinary) { + scope.newWorker('loadPLYBinary', PLYBinary); + } + + this.centerCamera = function() { + if (geometry) { + // Using method from http://msdn.microsoft.com/en-us/library/bb197900(v=xnagamestudio.10).aspx + // log("bounding sphere radius = " + geometry.boundingSphere.radius); + + // look at the center of the object + camera.target.position.x = geometry.center_x; + camera.target.position.y = geometry.center_y; + camera.target.position.z = geometry.center_z; + + // set camera position to center of sphere + camera.position.x = geometry.center_x; + camera.position.y = geometry.center_y; + camera.position.z = geometry.center_z; + + // find distance to center + distance = geometry.boundingSphere.radius / Math.sin((camera.fov/2) * (Math.PI / 180)); + + // zoom backwards about half that distance, I don't think I'm doing the math or backwards vector calculation correctly? + // scope.setCameraZoom(-distance/1.8); + // scope.setCameraZoom(-distance/1.5); + scope.setCameraZoom(-distance/1.9); + + directionalLight.position.x = geometry.min_y * 2; + directionalLight.position.y = geometry.min_y * 2; + directionalLight.position.z = geometry.max_z * 2; + + pointLight.position.x = geometry.center_y; + pointLight.position.y = geometry.center_y; + pointLight.position.z = geometry.max_z * 2; + } else { + // set to any valid position so it doesn't fail before geometry is available + camera.position.y = -70; + camera.position.z = 70; + camera.target.position.z = 0; + } + } + + this.loadArray = function(array) { + log("loading array..."); + geometry = new STLGeometry(array); + loadObjectGeometry(); + scope.resetRotation(); + scope.centerCamera(); + log("finished loading " + geometry.faces.length + " faces."); + } + + this.newWorker = function(cmd, param) { + scope.setRotation(false); + + var worker = new WorkerFacade(thingiurlbase + '/thingiloader.js'); + + worker.onmessage = function(event) { + if (event.data.status == "complete") { + progressBar.innerHTML = 'Initializing geometry...'; + // scene.removeObject(object); + geometry = new STLGeometry(event.data.content); + loadObjectGeometry(); + progressBar.innerHTML = ''; + progressBar.style.display = 'none'; + + scope.resetRotation(); + log("finished loading " + geometry.faces.length + " faces."); + scope.centerCamera(); + } else if (event.data.status == "complete_points") { + progressBar.innerHTML = 'Initializing points...'; + + geometry = new THREE.Geometry(); + + var material = new THREE.ParticleBasicMaterial( { color: 0xff0000, opacity: 1 } ); + + // material = new THREE.ParticleBasicMaterial( { size: 35, sizeAttenuation: false} ); + // material.color.setHSV( 1.0, 0.2, 0.8 ); + + for (i in event.data.content[0]) { + // for (var i=0; i<10; i++) { + vector = new THREE.Vector3( event.data.content[0][i][0], event.data.content[0][i][1], event.data.content[0][i][2] ); + geometry.vertices.push( new THREE.Vertex( vector ) ); + } + + particles = new THREE.ParticleSystem( geometry, material ); + particles.sortParticles = true; + particles.updateMatrix(); + scene.addObject( particles ); + + camera.updateMatrix(); + renderer.render(scene, camera); + + progressBar.innerHTML = ''; + progressBar.style.display = 'none'; + + scope.resetRotation(); + log("finished loading " + event.data.content[0].length + " points."); + // scope.centerCamera(); + } else if (event.data.status == "progress") { + progressBar.style.display = 'block'; + progressBar.style.width = event.data.content; + // log(event.data.content); + } else if (event.data.status == "message") { + progressBar.style.display = 'block'; + progressBar.innerHTML = event.data.content; + log(event.data.content); + } else if (event.data.status == "alert") { + scope.displayAlert(event.data.content); + } else { + alert('Error: ' + event.data); + log('Unknown Worker Message: ' + event.data); + } + } + + worker.onerror = function(error) { + log(error); + error.preventDefault(); + } + + worker.postMessage({'cmd':cmd, 'param':param}); + } + + this.displayAlert = function(msg) { + msg = msg + "<br/><br/><center><input type=\"button\" value=\"Ok\" onclick=\"document.getElementById('alertBox').style.display='none'\"></center>" + + alertBox.innerHTML = msg; + alertBox.style.display = 'block'; + + // log(msg); + } + + function loadPlaneGeometry() { + // TODO: switch to lines instead of the Plane object so we can get rid of the horizontal lines in canvas renderer... + plane = new THREE.Mesh(new Plane(100, 100, 10, 10), new THREE.MeshBasicMaterial({color:0xafafaf,wireframe:true})); + scene.addObject(plane); + } + + function loadObjectGeometry() { + if (scene && geometry) { + if (objectMaterial == 'wireframe') { + // material = new THREE.MeshColorStrokeMaterial(objectColor, 1, 1); + material = new THREE.MeshBasicMaterial({color:objectColor,wireframe:true}); + } else { + if (isWebGl) { + // material = new THREE.MeshPhongMaterial(objectColor, objectColor, 0xffffff, 50, 1.0); + // material = new THREE.MeshColorFillMaterial(objectColor); + // material = new THREE.MeshLambertMaterial({color:objectColor}); + material = new THREE.MeshLambertMaterial({color:objectColor, shading: THREE.FlatShading}); + } else { + // material = new THREE.MeshColorFillMaterial(objectColor); + material = new THREE.MeshLambertMaterial({color:objectColor, shading: THREE.FlatShading}); + } + } + + // scene.removeObject(object); + + if (object) { + // shouldn't be needed, but this fixes a bug with webgl not removing previous object when loading a new one dynamically + object.materials = [new THREE.MeshBasicMaterial({color:0xffffff, opacity:0})]; + scene.removeObject(object); + // object.geometry = geometry; + // object.materials = [material]; + } + + object = new THREE.Mesh(geometry, material); + scene.addObject(object); + + if (objectMaterial != 'wireframe') { + object.overdraw = true; + object.doubleSided = true; + } + + object.updateMatrix(); + + targetXRotation = 0; + targetYRotation = 0; + + sceneLoop(); + } + } + +}; + +var STLGeometry = function(stlArray) { + // log("building geometry..."); + THREE.Geometry.call(this); + + var scope = this; + + // var vertexes = stlArray[0]; + // var normals = stlArray[1]; + // var faces = stlArray[2]; + + for (var i=0; i<stlArray[0].length; i++) { + v(stlArray[0][i][0], stlArray[0][i][1], stlArray[0][i][2]); + } + + for (var i=0; i<stlArray[1].length; i++) { + f3(stlArray[1][i][0], stlArray[1][i][1], stlArray[1][i][2]); + } + + function v(x, y, z) { + // log("adding vertex: " + x + "," + y + "," + z); + scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) ); + } + + function f3(a, b, c) { + // log("adding face: " + a + "," + b + "," + c) + scope.faces.push( new THREE.Face3( a, b, c ) ); + } + + // log("computing centroids..."); + this.computeCentroids(); + // log("computing normals..."); + // this.computeNormals(); + this.computeFaceNormals(); + this.sortFacesByMaterial(); + // log("finished building geometry"); + + scope.min_x = 0; + scope.min_y = 0; + scope.min_z = 0; + + scope.max_x = 0; + scope.max_y = 0; + scope.max_z = 0; + + for (var v = 0, vl = scope.vertices.length; v < vl; v ++) { + scope.max_x = Math.max(scope.max_x, scope.vertices[v].position.x); + scope.max_y = Math.max(scope.max_y, scope.vertices[v].position.y); + scope.max_z = Math.max(scope.max_z, scope.vertices[v].position.z); + + scope.min_x = Math.min(scope.min_x, scope.vertices[v].position.x); + scope.min_y = Math.min(scope.min_y, scope.vertices[v].position.y); + scope.min_z = Math.min(scope.min_z, scope.vertices[v].position.z); +} + + scope.center_x = (scope.max_x + scope.min_x)/2; + scope.center_y = (scope.max_y + scope.min_y)/2; + scope.center_z = (scope.max_z + scope.min_z)/2; +} + +STLGeometry.prototype = new THREE.Geometry(); +STLGeometry.prototype.constructor = STLGeometry; + +function log(msg) { + if (this.console) { + console.log(msg); + } +} + +/* A facade for the Web Worker API that fakes it in case it's missing. +Good when web workers aren't supported in the browser, but it's still fast enough, so execution doesn't hang too badly (e.g. Opera 10.5). +By Stefan Wehrmeyer, licensed under MIT +*/ + +var WorkerFacade; +if(!!window.Worker){ + WorkerFacade = (function(){ + return function(path){ + return new window.Worker(path); + }; + }()); +} else { + WorkerFacade = (function(){ + var workers = {}, masters = {}, loaded = false; + var that = function(path){ + var theworker = {}, loaded = false, callings = []; + theworker.postToWorkerFunction = function(args){ + try{ + workers[path]({"data":args}); + }catch(err){ + theworker.onerror(err); + } + }; + theworker.postMessage = function(params){ + if(!loaded){ + callings.push(params); + return; + } + theworker.postToWorkerFunction(params); + }; + masters[path] = theworker; + var scr = document.createElement("SCRIPT"); + scr.src = path; + scr.type = "text/javascript"; + scr.onload = function(){ + loaded = true; + while(callings.length > 0){ + theworker.postToWorkerFunction(callings[0]); + callings.shift(); + } + }; + document.body.appendChild(scr); + + var binaryscr = document.createElement("SCRIPT"); + binaryscr.src = thingiurlbase + '/binaryReader.js'; + binaryscr.type = "text/javascript"; + document.body.appendChild(binaryscr); + + return theworker; + }; + that.fake = true; + that.add = function(pth, worker){ + workers[pth] = worker; + return function(param){ + masters[pth].onmessage({"data": param}); + }; + }; + that.toString = function(){ + return "FakeWorker('"+path+"')"; + }; + return that; + }()); +} + +/* Then just use WorkerFacade instead of Worker (or alias it) + +The Worker code must should use a custom function (name it how you want) instead of postMessage. +Put this at the end of the Worker: + +if(typeof(window) === "undefined"){ + onmessage = nameOfWorkerFunction; + customPostMessage = postMessage; +} else { + customPostMessage = WorkerFacade.add("path/to/thisworker.js", nameOfWorkerFunction); +} + +*/ diff --git a/extlib/video-js/LGPLv3-LICENSE.txt b/extlib/video-js/LGPLv3-LICENSE.txt new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/extlib/video-js/LGPLv3-LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 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. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/extlib/video-js/demo.html b/extlib/video-js/demo.html deleted file mode 100644 index a8393af0..00000000 --- a/extlib/video-js/demo.html +++ /dev/null @@ -1,23 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>Video.js | HTML5 Video Player</title> - - <link href="http://vjs.zencdn.net/c/video-js.css" rel="stylesheet" type="text/css"> - - <!-- video.js must be in the <head> for older IEs to work. --> - <script src="http://vjs.zencdn.net/c/video.js"></script> - -</head> -<body> - - <video id="example_video_1" class="video-js vjs-default-skin" controls preload="none" width="640" height="264" - poster="http://video-js.zencoder.com/oceans-clip.png" - data-setup="{}"> - <source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4' /> - <source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm' /> - <source src="http://video-js.zencoder.com/oceans-clip.ogv" type='video/ogg' /> - </video> - -</body> -</html> diff --git a/extlib/video-js/video-js.css b/extlib/video-js/video-js.css deleted file mode 100644 index a1a18a00..00000000 --- a/extlib/video-js/video-js.css +++ /dev/null @@ -1,427 +0,0 @@ -/* -VideoJS Default Styles (http://videojs.com) -Version 3.1.0 -*/ - -/* -REQUIRED STYLES (be careful overriding) -================================================================================ */ -/* When loading the player, the video tag is replaced with a DIV, - that will hold the video tag or object tag for other playback methods. - The div contains the video playback element (Flash or HTML5) and controls, and sets the width and height of the video. - - ** If you want to add some kind of border/padding (e.g. a frame), or special positioning, use another containing element. - Otherwise you risk messing up control positioning and full window mode. ** -*/ -.video-js { - background-color: #000; position: relative; padding: 0; - - /* Start with 10px for base font size so other dimensions can be em based and easily calculable. */ - font-size: 10px; - - /* 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: 1 !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; -} - -/* DEFAULT SKIN (override in another file to create new skins) -================================================================================ -Instead of editing this file, I recommend creating your own skin CSS file to be included after this file, -so you can upgrade to newer versions easier. You can remove all these styles by removing the 'vjs-default-skin' class from the tag. */ - -/* The default control bar. Created by bar.js */ -.vjs-default-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: 2.6em; /* Including any margin you want above or below control items */ - color: #fff; border-top: 1px solid #404040; - - /* 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-default-skin .vjs-control { - position: relative; float: left; - text-align: center; margin: 0; padding: 0; - height: 2.6em; width: 2.6em; -} - -.vjs-default-skin .vjs-control:focus { - outline: 0; -} - -/* Hide control text visually, but have it available for screenreaders: h5bp.com/v */ -.vjs-default-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-default-skin .vjs-play-control { width: 5em; cursor: pointer !important; } -/* Play Icon */ -.vjs-default-skin.vjs-paused .vjs-play-control div { width: 15px; height: 17px; background: url('video-js.png'); margin: 0.5em auto 0; } -.vjs-default-skin.vjs-playing .vjs-play-control div { width: 15px; height: 17px; background: url('video-js.png') -25px 0; margin: 0.5em auto 0; } - -/* Rewind --------------------------------------------------------------------------------- */ -.vjs-default-skin .vjs-rewind-control { width: 5em; cursor: pointer !important; } -.vjs-default-skin .vjs-rewind-control div { width: 19px; height: 16px; background: url('video-js.png'); margin: 0.5em auto 0; } - -/* Volume/Mute --------------------------------------------------------------------------------- */ -.vjs-default-skin .vjs-mute-control { width: 3.8em; cursor: pointer !important; float: right; } -.vjs-default-skin .vjs-mute-control div { width: 22px; height: 16px; background: url('video-js.png') -75px -25px; margin: 0.5em auto 0; } -.vjs-default-skin .vjs-mute-control.vjs-vol-0 div { background: url('video-js.png') 0 -25px; } -.vjs-default-skin .vjs-mute-control.vjs-vol-1 div { background: url('video-js.png') -25px -25px; } -.vjs-default-skin .vjs-mute-control.vjs-vol-2 div { background: url('video-js.png') -50px -25px; } - - -.vjs-default-skin .vjs-volume-control { width: 5em; float: right; } -.vjs-default-skin .vjs-volume-bar { - position: relative; width: 5em; height: 0.6em; margin: 1em auto 0; 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); -} -.vjs-default-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; - - background: #fff; - background: -moz-linear-gradient(top, #fff, #ccc); - background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ccc)); - background: -webkit-linear-gradient(top, #fff, #ccc); - background: -o-linear-gradient(top, #fff, #ccc); - background: -ms-linear-gradient(top, #fff, #ccc); - background: linear-gradient(top, #fff, #ccc); -} -.vjs-default-skin .vjs-volume-handle { - position: absolute; top: -0.2em; width: 0.8em; height: 0.8em; background: #ccc; left: 0; - border: 1px solid #fff; - -moz-border-radius: 0.6em; -webkit-border-radius: 0.6em; border-radius: 0.6em; -} - -/* Progress --------------------------------------------------------------------------------- */ -.vjs-default-skin div.vjs-progress-control { - position: absolute; - left: 4.8em; right: 4.8em; /* Leave room for time displays. */ - height: 1.0em; width: auto; - top: -1.3em; /* Set above the rest of the controls. And leave room for 2px of borders (progress bottom and controls top). */ - border-bottom: 1px solid #1F1F1F; - border-top: 1px solid #222; - - /* CSS Gradient */ - background: #333; - background: -moz-linear-gradient(top, #222, #333); - background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#222), to(#333)); - background: -webkit-linear-gradient(top, #222, #333); - background: -o-linear-gradient(top, #333, #222); - background: -ms-linear-gradient(top, #333, #222); - background: linear-gradient(top, #333, #222); - - - /* 1px top shadow */ -/* -webkit-box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.15); -moz-box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.15); box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.15);*/ -} - -/* Box containing play and load progresses. Also acts as seek scrubber. */ -.vjs-default-skin .vjs-progress-holder { - position: relative; cursor: pointer !important; /*overflow: hidden;*/ - padding: 0; margin: 0; /* Placement within the progress control item */ - height: 1.0em; - -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-default-skin .vjs-progress-holder .vjs-play-progress, -.vjs-default-skin .vjs-progress-holder .vjs-load-progress { /* Progress Bars */ - position: absolute; display: block; height: 1.0em; 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-default-skin .vjs-play-progress { - /* CSS Gradient. */ - background: #fff; /* Old browsers */ - background: -moz-linear-gradient(top, #fff 0%, #d6d6d6 50%, #fff 100%); - background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#fff), color-stop(50%,#d6d6d6), color-stop(100%,#fff)); - background: -webkit-linear-gradient(top, #fff 0%,#d6d6d6 50%,#fff 100%); - background: -o-linear-gradient(top, #fff 0%,#d6d6d6 50%,#fff 100%); - background: -ms-linear-gradient(top, #fff 0%,#d6d6d6 50%,#fff 100%); - background: linear-gradient(top, #fff 0%,#d6d6d6 50%,#fff 100%); - - background: #efefef; - background: -moz-linear-gradient(top, #efefef 0%, #f5f5f5 50%, #dbdbdb 50%, #f1f1f1 100%); - background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#efefef), color-stop(50%,#f5f5f5), color-stop(50%,#dbdbdb), color-stop(100%,#f1f1f1)); - background: -webkit-linear-gradient(top, #efefef 0%,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%); - background: -o-linear-gradient(top, #efefef 0%,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%); - background: -ms-linear-gradient(top, #efefef 0%,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#efefef', endColorstr='#f1f1f1',GradientType=0 ); - background: linear-gradient(top, #efefef 0%,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%); -} -.vjs-default-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-default-skin div.vjs-seek-handle { - position: absolute; - width: 16px; height: 16px; /* Match img pixles */ - margin-top: -0.3em; - left: 0; top: 0; /*Needed for IE6*/ - - background: url('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-default-skin .vjs-time-controls { - position: absolute; - right: 0; - height: 1.0em; width: 4.8em; - top: -1.3em; - border-bottom: 1px solid #1F1F1F; - border-top: 1px solid #222; - background-color: #333; - - font-size: 1em; line-height: 1.0em; font-weight: normal; font-family: Helvetica, Arial, sans-serif; - - background: #333; - background: -moz-linear-gradient(top, #222, #333); - background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#222), to(#333)); - background: -webkit-linear-gradient(top, #222, #333); - background: -o-linear-gradient(top, #333, #222); - background: -ms-linear-gradient(top, #333, #222); - background: linear-gradient(top, #333, #222); - - /* 1px top shadow */ -/* -webkit-box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.15); -moz-box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.15); box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, 0.15);*/ -} - -.vjs-default-skin .vjs-current-time { left: 0; } - -.vjs-default-skin .vjs-duration { right: 0; display: none; } -.vjs-default-skin .vjs-remaining-time { right: 0; } - -.vjs-time-divider { display:none; } - -.vjs-default-skin .vjs-time-control { font-size: 1em; line-height: 1; font-weight: normal; font-family: Helvetica, Arial, sans-serif; } -.vjs-default-skin .vjs-time-control span { line-height: 25px; /* Centering vertically */ } - -/* Fullscreen --------------------------------------------------------------------------------- */ -.vjs-secondary-controls { float: right; } - -.vjs-default-skin .vjs-fullscreen-control { width: 3.8em; cursor: pointer !important; float: right; } -.vjs-default-skin .vjs-fullscreen-control div { width: 16px; height: 16px; background: url('video-js.png') -50px 0; margin: 0.5em auto 0; } - -.vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control div { background: url('video-js.png') -75px 0; } - - -/* Big Play Button (at start) ----------------------------------------------------------*/ -.vjs-default-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 #fff; 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-default-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-default-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('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/extlib/video-js/video-js.min.css b/extlib/video-js/video-js.min.css index 4394cdd7..06c0e6b4 100644 --- a/extlib/video-js/video-js.min.css +++ b/extlib/video-js/video-js.min.css @@ -1 +1 @@ -.video-js{background-color:#000;position:relative;padding:0;font-size:10px;vertical-align:middle}.video-js .vjs-tech{position:absolute;top:0;left:0;width:100%;height:100%}.video-js:-moz-full-screen{position:absolute}body.vjs-full-window{padding:0;margin:0;height:100%;overflow-y:auto}.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}.video-js:-webkit-full-screen{width:100%!important;height:100%!important}.vjs-poster{margin:0 auto;padding:0;cursor:pointer;position:relative;width:100%;max-height:100%}.video-js .vjs-subtitles{color:#fff;font-size:20px;text-align:center;position:absolute;bottom:40px;left:0;right:0}.vjs-fade-in{visibility:visible!important;opacity:1!important;-webkit-transition:visibility 0s linear 0s,opacity .3s linear;-moz-transition:visibility 0s linear 0s,opacity .3s linear;-ms-transition:visibility 0s linear 0s,opacity .3s linear;-o-transition:visibility 0s linear 0s,opacity .3s linear;transition:visibility 0s linear 0s,opacity .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}.vjs-default-skin .vjs-controls{position:absolute;bottom:0;left:0;right:0;margin:0;padding:0;height:2.6em;color:#fff;border-top:1px solid #404040;background:#242424;background:-moz-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:-webkit-gradient(linear,0% 0,0% 100%,color-stop(50%,#242424),color-stop(50%,#1f1f1f),color-stop(100%,#171717));background:-webkit-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:-o-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:-ms-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);visibility:hidden;opacity:0}.vjs-default-skin .vjs-control{position:relative;float:left;text-align:center;margin:0;padding:0;height:2.6em;width:2.6em}.vjs-default-skin .vjs-control:focus{outline:0}.vjs-default-skin .vjs-control-text{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.vjs-default-skin .vjs-play-control{width:5em;cursor:pointer!important}.vjs-default-skin.vjs-paused .vjs-play-control div{width:15px;height:17px;background:url('video-js.png');margin:.5em auto 0}.vjs-default-skin.vjs-playing .vjs-play-control div{width:15px;height:17px;background:url('video-js.png') -25px 0;margin:.5em auto 0}.vjs-default-skin .vjs-rewind-control{width:5em;cursor:pointer!important}.vjs-default-skin .vjs-rewind-control div{width:19px;height:16px;background:url('video-js.png');margin:.5em auto 0}.vjs-default-skin .vjs-mute-control{width:3.8em;cursor:pointer!important;float:right}.vjs-default-skin .vjs-mute-control div{width:22px;height:16px;background:url('video-js.png') -75px -25px;margin:.5em auto 0}.vjs-default-skin .vjs-mute-control.vjs-vol-0 div{background:url('video-js.png') 0 -25px}.vjs-default-skin .vjs-mute-control.vjs-vol-1 div{background:url('video-js.png') -25px -25px}.vjs-default-skin .vjs-mute-control.vjs-vol-2 div{background:url('video-js.png') -50px -25px}.vjs-default-skin .vjs-volume-control{width:5em;float:right}.vjs-default-skin .vjs-volume-bar{position:relative;width:5em;height:.6em;margin:1em auto 0;cursor:pointer!important;-moz-border-radius:.3em;-webkit-border-radius:.3em;border-radius:.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)}.vjs-default-skin .vjs-volume-level{position:absolute;top:0;left:0;height:.6em;-moz-border-radius:.3em;-webkit-border-radius:.3em;border-radius:.3em;background:#fff;background:-moz-linear-gradient(top,#fff,#ccc);background:-webkit-gradient(linear,0% 0,0% 100%,from(#fff),to(#ccc));background:-webkit-linear-gradient(top,#fff,#ccc);background:-o-linear-gradient(top,#fff,#ccc);background:-ms-linear-gradient(top,#fff,#ccc);background:linear-gradient(top,#fff,#ccc)}.vjs-default-skin .vjs-volume-handle{position:absolute;top:-0.2em;width:.8em;height:.8em;background:#ccc;left:0;border:1px solid #fff;-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.vjs-default-skin div.vjs-progress-control{position:absolute;left:4.8em;right:4.8em;height:1.0em;width:auto;top:-1.3em;border-bottom:1px solid #1f1f1f;border-top:1px solid #222;background:#333;background:-moz-linear-gradient(top,#222,#333);background:-webkit-gradient(linear,0% 0,0% 100%,from(#222),to(#333));background:-webkit-linear-gradient(top,#222,#333);background:-o-linear-gradient(top,#333,#222);background:-ms-linear-gradient(top,#333,#222);background:linear-gradient(top,#333,#222)}.vjs-default-skin .vjs-progress-holder{position:relative;cursor:pointer!important;padding:0;margin:0;height:1.0em;-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em;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-default-skin .vjs-progress-holder .vjs-play-progress,.vjs-default-skin .vjs-progress-holder .vjs-load-progress{position:absolute;display:block;height:1.0em;margin:0;padding:0;left:0;top:0;-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.vjs-default-skin .vjs-play-progress{background:#fff;background:-moz-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:-webkit-gradient(linear,0% 0,0% 100%,color-stop(0%,#fff),color-stop(50%,#d6d6d6),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:-o-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:-ms-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:#efefef;background:-moz-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);background:-webkit-gradient(linear,0% 0,0% 100%,color-stop(0%,#efefef),color-stop(50%,#f5f5f5),color-stop(50%,#dbdbdb),color-stop(100%,#f1f1f1));background:-webkit-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);background:-o-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);background:-ms-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#efefef',endColorstr='#f1f1f1',GradientType=0);background:linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%)}.vjs-default-skin .vjs-load-progress{opacity:.8;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-default-skin div.vjs-seek-handle{position:absolute;width:16px;height:16px;margin-top:-0.3em;left:0;top:0;background:url('video-js.png') 0 -50px;-moz-border-radius:.8em;-webkit-border-radius:.8em;border-radius:.8em;-webkit-box-shadow:0 2px 4px 0 #000;-moz-box-shadow:0 2px 4px 0 #000;box-shadow:0 2px 4px 0 #000}.vjs-default-skin .vjs-time-controls{position:absolute;right:0;height:1.0em;width:4.8em;top:-1.3em;border-bottom:1px solid #1f1f1f;border-top:1px solid #222;background-color:#333;font-size:1em;line-height:1.0em;font-weight:normal;font-family:Helvetica,Arial,sans-serif;background:#333;background:-moz-linear-gradient(top,#222,#333);background:-webkit-gradient(linear,0% 0,0% 100%,from(#222),to(#333));background:-webkit-linear-gradient(top,#222,#333);background:-o-linear-gradient(top,#333,#222);background:-ms-linear-gradient(top,#333,#222);background:linear-gradient(top,#333,#222)}.vjs-default-skin .vjs-current-time{left:0}.vjs-default-skin .vjs-duration{right:0;display:none}.vjs-default-skin .vjs-remaining-time{right:0}.vjs-time-divider{display:none}.vjs-default-skin .vjs-time-control{font-size:1em;line-height:1;font-weight:normal;font-family:Helvetica,Arial,sans-serif}.vjs-default-skin .vjs-time-control span{line-height:25px}.vjs-secondary-controls{float:right}.vjs-default-skin .vjs-fullscreen-control{width:3.8em;cursor:pointer!important;float:right}.vjs-default-skin .vjs-fullscreen-control div{width:16px;height:16px;background:url('video-js.png') -50px 0;margin:.5em auto 0}.vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control div{background:url('video-js.png') -75px 0}.vjs-default-skin .vjs-big-play-button{display:block;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:.3em solid #fff;opacity:.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%);-webkit-box-shadow:4px 4px 8px #000;-moz-box-shadow:4px 4px 8px #000;box-shadow:4px 4px 8px #000}.vjs-default-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-default-skin div.vjs-big-play-button span{position:absolute;top:50%;left:50%;display:block;width:35px;height:42px;margin:-20px 0 0 -15px;background:url('video-js.png') -100px 0}.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)}}div.vjs-loading-spinner .ball1{opacity:.12;position:absolute;left:20px;top:0;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:.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:.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:.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:.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:.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:.87;position:absolute;left:0;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}
\ No newline at end of file +.video-js{background-color:#000;position:relative;padding:0;font-size:10px;vertical-align:middle}.video-js .vjs-tech{position:absolute;top:0;left:0;width:100%;height:100%}.video-js:-moz-full-screen{position:absolute}body.vjs-full-window{padding:0;margin:0;height:100%;overflow-y:auto}.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}.video-js:-webkit-full-screen{width:100%!important;height:100%!important}.vjs-poster{margin:0 auto;padding:0;cursor:pointer;position:relative;width:100%;max-height:100%}.video-js .vjs-text-track-display{text-align:center;position:absolute;bottom:4em;left:1em;right:1em;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.video-js .vjs-text-track{display:none;color:#fff;font-size:1.4em;text-align:center;margin-bottom:.1em;background:#000;background:rgba(0,0,0,0.50)}.video-js .vjs-subtitles{color:#fff}.video-js .vjs-captions{color:#fc6}.vjs-tt-cue{display:block}.vjs-fade-in{visibility:visible!important;opacity:1!important;-webkit-transition:visibility 0s linear 0s,opacity .3s linear;-moz-transition:visibility 0s linear 0s,opacity .3s linear;-ms-transition:visibility 0s linear 0s,opacity .3s linear;-o-transition:visibility 0s linear 0s,opacity .3s linear;transition:visibility 0s linear 0s,opacity .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}.vjs-default-skin .vjs-controls{position:absolute;bottom:0;left:0;right:0;margin:0;padding:0;height:2.6em;color:#fff;border-top:1px solid #404040;background:#242424;background:-moz-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:-webkit-gradient(linear,0% 0,0% 100%,color-stop(50%,#242424),color-stop(50%,#1f1f1f),color-stop(100%,#171717));background:-webkit-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:-o-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:-ms-linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);background:linear-gradient(top,#242424 50%,#1f1f1f 50%,#171717 100%);visibility:hidden;opacity:0}.vjs-default-skin .vjs-control{position:relative;float:left;text-align:center;margin:0;padding:0;height:2.6em;width:2.6em}.vjs-default-skin .vjs-control:focus{outline:0}.vjs-default-skin .vjs-control-text{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.vjs-default-skin .vjs-play-control{width:5em;cursor:pointer!important}.vjs-default-skin.vjs-paused .vjs-play-control div{width:15px;height:17px;background:url('video-js.png');margin:.5em auto 0}.vjs-default-skin.vjs-playing .vjs-play-control div{width:15px;height:17px;background:url('video-js.png') -25px 0;margin:.5em auto 0}.vjs-default-skin .vjs-rewind-control{width:5em;cursor:pointer!important}.vjs-default-skin .vjs-rewind-control div{width:19px;height:16px;background:url('video-js.png');margin:.5em auto 0}.vjs-default-skin .vjs-mute-control{width:3.8em;cursor:pointer!important;float:right}.vjs-default-skin .vjs-mute-control div{width:22px;height:16px;background:url('video-js.png') -75px -25px;margin:.5em auto 0}.vjs-default-skin .vjs-mute-control.vjs-vol-0 div{background:url('video-js.png') 0 -25px}.vjs-default-skin .vjs-mute-control.vjs-vol-1 div{background:url('video-js.png') -25px -25px}.vjs-default-skin .vjs-mute-control.vjs-vol-2 div{background:url('video-js.png') -50px -25px}.vjs-default-skin .vjs-volume-control{width:5em;float:right}.vjs-default-skin .vjs-volume-bar{position:relative;width:5em;height:.6em;margin:1em auto 0;cursor:pointer!important;-moz-border-radius:.3em;-webkit-border-radius:.3em;border-radius:.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)}.vjs-default-skin .vjs-volume-level{position:absolute;top:0;left:0;height:.6em;-moz-border-radius:.3em;-webkit-border-radius:.3em;border-radius:.3em;background:#fff;background:-moz-linear-gradient(top,#fff,#ccc);background:-webkit-gradient(linear,0% 0,0% 100%,from(#fff),to(#ccc));background:-webkit-linear-gradient(top,#fff,#ccc);background:-o-linear-gradient(top,#fff,#ccc);background:-ms-linear-gradient(top,#fff,#ccc);background:linear-gradient(top,#fff,#ccc)}.vjs-default-skin .vjs-volume-handle{position:absolute;top:-0.2em;width:.8em;height:.8em;background:#ccc;left:0;border:1px solid #fff;-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.vjs-default-skin div.vjs-progress-control{position:absolute;left:4.8em;right:4.8em;height:1.0em;width:auto;top:-1.3em;border-bottom:1px solid #1f1f1f;border-top:1px solid #222;background:#333;background:-moz-linear-gradient(top,#222,#333);background:-webkit-gradient(linear,0% 0,0% 100%,from(#222),to(#333));background:-webkit-linear-gradient(top,#222,#333);background:-o-linear-gradient(top,#333,#222);background:-ms-linear-gradient(top,#333,#222);background:linear-gradient(top,#333,#222)}.vjs-default-skin .vjs-progress-holder{position:relative;cursor:pointer!important;padding:0;margin:0;height:1.0em;-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em;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-default-skin .vjs-progress-holder .vjs-play-progress,.vjs-default-skin .vjs-progress-holder .vjs-load-progress{position:absolute;display:block;height:1.0em;margin:0;padding:0;left:0;top:0;-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.vjs-default-skin .vjs-play-progress{background:#fff;background:-moz-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:-webkit-gradient(linear,0% 0,0% 100%,color-stop(0%,#fff),color-stop(50%,#d6d6d6),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:-o-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:-ms-linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:linear-gradient(top,#fff 0,#d6d6d6 50%,#fff 100%);background:#efefef;background:-moz-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);background:-webkit-gradient(linear,0% 0,0% 100%,color-stop(0%,#efefef),color-stop(50%,#f5f5f5),color-stop(50%,#dbdbdb),color-stop(100%,#f1f1f1));background:-webkit-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);background:-o-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);background:-ms-linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#efefef',endColorstr='#f1f1f1',GradientType=0);background:linear-gradient(top,#efefef 0,#f5f5f5 50%,#dbdbdb 50%,#f1f1f1 100%)}.vjs-default-skin .vjs-load-progress{opacity:.8;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-default-skin div.vjs-seek-handle{position:absolute;width:16px;height:16px;margin-top:-0.3em;left:0;top:0;background:url('video-js.png') 0 -50px;-moz-border-radius:.8em;-webkit-border-radius:.8em;border-radius:.8em;-webkit-box-shadow:0 2px 4px 0 #000;-moz-box-shadow:0 2px 4px 0 #000;box-shadow:0 2px 4px 0 #000}.vjs-default-skin .vjs-time-controls{position:absolute;right:0;height:1.0em;width:4.8em;top:-1.3em;border-bottom:1px solid #1f1f1f;border-top:1px solid #222;background-color:#333;font-size:1em;line-height:1.0em;font-weight:normal;font-family:Helvetica,Arial,sans-serif;background:#333;background:-moz-linear-gradient(top,#222,#333);background:-webkit-gradient(linear,0% 0,0% 100%,from(#222),to(#333));background:-webkit-linear-gradient(top,#222,#333);background:-o-linear-gradient(top,#333,#222);background:-ms-linear-gradient(top,#333,#222);background:linear-gradient(top,#333,#222)}.vjs-default-skin .vjs-current-time{left:0}.vjs-default-skin .vjs-duration{right:0;display:none}.vjs-default-skin .vjs-remaining-time{right:0}.vjs-time-divider{display:none}.vjs-default-skin .vjs-time-control{font-size:1em;line-height:1;font-weight:normal;font-family:Helvetica,Arial,sans-serif}.vjs-default-skin .vjs-time-control span{line-height:25px}.vjs-secondary-controls{float:right}.vjs-default-skin .vjs-fullscreen-control{width:3.8em;cursor:pointer!important;float:right}.vjs-default-skin .vjs-fullscreen-control div{width:16px;height:16px;background:url('video-js.png') -50px 0;margin:.5em auto 0}.vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control div{background:url('video-js.png') -75px 0}.vjs-default-skin .vjs-big-play-button{display:block;z-index:2;position:absolute;top:50%;left:50%;width:8.0em;height:8.0em;margin:-42px 0 0 -42px;text-align:center;vertical-align:center;cursor:pointer!important;border:.2em solid #fff;opacity:.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%);-webkit-box-shadow:4px 4px 8px #000;-moz-box-shadow:4px 4px 8px #000;box-shadow:4px 4px 8px #000}.vjs-default-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-default-skin div.vjs-big-play-button span{position:absolute;top:50%;left:50%;display:block;width:35px;height:42px;margin:-20px 0 0 -15px;background:url('video-js.png') -100px 0}.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)}}div.vjs-loading-spinner .ball1{opacity:.12;position:absolute;left:20px;top:0;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:.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:.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:.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:.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:.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:.87;position:absolute;left:0;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}.vjs-default-skin .vjs-menu-button{float:right;margin:.2em .5em 0 0;padding:0;width:3em;height:2em;cursor:pointer!important;border:1px solid #111;-moz-border-radius:.3em;-webkit-border-radius:.3em;border-radius:.3em;background:#4d4d4d;background:-moz-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#4d4d4d),color-stop(50%,#3f3f3f),color-stop(50%,#333),color-stop(100%,#252525));background:-webkit-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:-o-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:-ms-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%)}.vjs-default-skin .vjs-menu-button div{background:url('video-js.png') 0 -75px no-repeat;width:16px;height:16px;margin:.2em auto 0;padding:0}.vjs-default-skin .vjs-menu-button ul{display:none;opacity:.8;padding:0;margin:0;position:absolute;width:10em;bottom:2em;max-height:15em;left:-3.5em;background-color:#111;border:2px solid #333;-moz-border-radius:.7em;-webkit-border-radius:1em;border-radius:.5em;-webkit-box-shadow:0 2px 4px 0 #000;-moz-box-shadow:0 2px 4px 0 #000;box-shadow:0 2px 4px 0 #000;overflow:auto}.vjs-default-skin .vjs-menu-button:focus ul,.vjs-default-skin .vjs-menu-button:hover ul{display:block;list-style:none}.vjs-default-skin .vjs-menu-button ul li{list-style:none;margin:0;padding:.3em 0 .3em 20px;line-height:1.4em;font-size:1.2em;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;text-align:left}.vjs-default-skin .vjs-menu-button ul li.vjs-selected{text-decoration:underline;background:url('video-js.png') -125px -50px no-repeat}.vjs-default-skin .vjs-menu-button ul li:focus,.vjs-default-skin .vjs-menu-button ul li:hover,.vjs-default-skin .vjs-menu-button ul li.vjs-selected:focus,.vjs-default-skin .vjs-menu-button ul li.vjs-selected:hover{background-color:#ccc;color:#111;outline:0}.vjs-default-skin .vjs-menu-button ul li.vjs-menu-title{text-align:center;text-transform:uppercase;font-size:1em;line-height:2em;padding:0;margin:0 0 .3em 0;color:#fff;font-weight:bold;cursor:default;background:#4d4d4d;background:-moz-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#4d4d4d),color-stop(50%,#3f3f3f),color-stop(50%,#333),color-stop(100%,#252525));background:-webkit-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:-o-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:-ms-linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%);background:linear-gradient(top,#4d4d4d 0,#3f3f3f 50%,#333 50%,#252525 100%)}.vjs-default-skin .vjs-captions-button div{background-position:-25px -75px}.vjs-default-skin .vjs-chapters-button div{background-position:-100px -75px}.vjs-default-skin .vjs-chapters-button ul{width:20em;left:-8.5em}
\ No newline at end of file diff --git a/extlib/video-js/video-js.png b/extlib/video-js/video-js.png Binary files differindex c692d123..100bc7f8 100644 --- a/extlib/video-js/video-js.png +++ b/extlib/video-js/video-js.png diff --git a/extlib/video-js/video-js.swf b/extlib/video-js/video-js.swf Binary files differdeleted file mode 100644 index 3273af5a..00000000 --- a/extlib/video-js/video-js.swf +++ /dev/null diff --git a/extlib/video-js/video.js b/extlib/video-js/video.js deleted file mode 100644 index c41cceec..00000000 --- a/extlib/video-js/video.js +++ /dev/null @@ -1,3744 +0,0 @@ -/*! -Video.js - HTML5 Video Player -Version 3.1.0 - -LGPL v3 LICENSE INFO -This file is part of Video.js. Copyright 2011 Zencoder, Inc. - -Video.js is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Video.js is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with Video.js. If not, see <http://www.gnu.org/licenses/>. -*/ - -// Self-executing function to prevent global vars and help with minification -;(function(window, undefined){ - var document = window.document;// HTML5 Shiv. Must be in <head> to support older browsers. -document.createElement("video");document.createElement("audio"); - -var VideoJS = function(id, addOptions, ready){ - var tag; // Element of ID - - // Allow for element or ID to be passed in - // String ID - if (typeof id == "string") { - - // Adjust for jQuery ID syntax - if (id.indexOf("#") === 0) { - id = id.slice(1); - } - - // If a player instance has already been created for this ID return it. - if (_V_.players[id]) { - return _V_.players[id]; - - // Otherwise get element for ID - } else { - tag = _V_.el(id) - } - - // ID is a media element - } else { - tag = id; - } - - // Check for a useable element - if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also - throw new TypeError("The element or ID supplied is not valid. (VideoJS)"); // Returns - } - - // Element may have a player attr referring to an already created player instance. - // If not, set up a new player and return the instance. - return tag.player || new _V_.Player(tag, addOptions, ready); -}, - -// Shortcut -_V_ = VideoJS, - -// CDN Version. Used to target right flash swf. -CDN_VERSION = "3.1"; - -VideoJS.players = {}; - -VideoJS.options = { - - // Default order of fallback technology - techOrder: ["html5","flash"], - // techOrder: ["flash","html5"], - - html5: {}, - flash: { swf: "http://vjs.zencdn.net/c/video-js.swf" }, - - // Default of web browser is 300x150. Should rely on source width/height. - width: "auto", - height: "auto", - - // defaultVolume: 0.85, - defaultVolume: 0.00, // The freakin seaguls are driving me crazy! - - // Included control sets - components: { - "poster": {}, - "loadingSpinner": {}, - "bigPlayButton": {}, - "controlBar": {}, - "subtitlesDisplay": {} - } - - // components: [ - // "poster", - // "loadingSpinner", - // "bigPlayButton", - // { name: "controlBar", options: { - // components: [ - // "playToggle", - // "fullscreenToggle", - // "currentTimeDisplay", - // "timeDivider", - // "durationDisplay", - // "remainingTimeDisplay", - // { name: "progressControl", options: { - // components: [ - // { name: "seekBar", options: { - // components: [ - // "loadProgressBar", - // "playProgressBar", - // "seekHandle" - // ]} - // } - // ]} - // }, - // { name: "volumeControl", options: { - // components: [ - // { name: "volumeBar", options: { - // components: [ - // "volumeLevel", - // "volumeHandle" - // ]} - // } - // ]} - // }, - // "muteToggle" - // ] - // }}, - // "subtitlesDisplay"/*, "replay"*/ - // ] -}; - -// Set CDN Version of swf -if (CDN_VERSION != "GENERATED_CDN_VSN") { - _V_.options.flash.swf = "http://vjs.zencdn.net/"+CDN_VERSION+"/video-js.swf" -} - -// Automatically set up any tags that have a data-setup attribute -_V_.autoSetup = function(){ - var options, vid, player, - vids = document.getElementsByTagName("video"); - - // Check if any media elements exist - if (vids && vids.length > 0) { - - for (var i=0,j=vids.length; i<j; i++) { - vid = vids[i]; - - // Check if element exists, has getAttribute func. - // IE seems to consider typeof el.getAttribute == "object" instead of "function" like expected, at least when loading the player immediately. - if (vid && vid.getAttribute) { - - // Make sure this player hasn't already been set up. - if (vid.player === undefined) { - options = vid.getAttribute("data-setup"); - - // Check if data-setup attr exists. - // We only auto-setup if they've added the data-setup attr. - if (options !== null) { - - // Parse options JSON - // If empty string, make it a parsable json object. - options = JSON.parse(options || "{}"); - - // Create new video.js instance. - player = _V_(vid, options); - } - } - - // If getAttribute isn't defined, we need to wait for the DOM. - } else { - _V_.autoSetupTimeout(1); - break; - } - } - - // No videos were found, so keep looping unless page is finisehd loading. - } else if (!_V_.windowLoaded) { - _V_.autoSetupTimeout(1); - } -}; - -// Pause to let the DOM keep processing -_V_.autoSetupTimeout = function(wait){ - setTimeout(_V_.autoSetup, wait); -}; -_V_.merge = function(obj1, obj2, safe){ - // Make sure second object exists - if (!obj2) { obj2 = {}; }; - - for (var attrname in obj2){ - if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; } - } - return obj1; -}; -_V_.extend = function(obj){ this.merge(this, obj, true); }; - -_V_.extend({ - tech: {}, // Holder for playback technology settings - controlSets: {}, // Holder for control set definitions - - // Device Checks - isIE: function(){ return !+"\v1"; }, - isFF: function(){ return !!_V_.ua.match("Firefox") }, - isIPad: function(){ return navigator.userAgent.match(/iPad/i) !== null; }, - isIPhone: function(){ return navigator.userAgent.match(/iPhone/i) !== null; }, - isIOS: function(){ return VideoJS.isIPhone() || VideoJS.isIPad(); }, - iOSVersion: function() { - var match = navigator.userAgent.match(/OS (\d+)_/i); - if (match && match[1]) { return match[1]; } - }, - isAndroid: function(){ return navigator.userAgent.match(/Android.*AppleWebKit/i) !== null; }, - androidVersion: function() { - var match = navigator.userAgent.match(/Android (\d+)\./i); - if (match && match[1]) { return match[1]; } - }, - - testVid: document.createElement("video"), - ua: navigator.userAgent, - support: {}, - - each: function(arr, fn){ - if (!arr || arr.length === 0) { return; } - for (var i=0,j=arr.length; i<j; i++) { - fn.call(this, arr[i], i); - } - }, - - eachProp: function(obj, fn){ - if (!obj) { return; } - for (var name in obj) { - if (obj.hasOwnProperty(name)) { - fn.call(this, name, obj[name]); - } - } - }, - - el: function(id){ return document.getElementById(id); }, - createElement: function(tagName, attributes){ - var el = document.createElement(tagName), - attrname; - for (attrname in attributes){ - if (attributes.hasOwnProperty(attrname)) { - if (attrname.indexOf("-") !== -1) { - el.setAttribute(attrname, attributes[attrname]); - } else { - el[attrname] = attributes[attrname]; - } - } - } - return el; - }, - - insertFirst: function(node, parent){ - if (parent.firstChild) { - parent.insertBefore(node, parent.firstChild); - } else { - parent.appendChild(node); - } - }, - - addClass: function(element, classToAdd){ - if ((" "+element.className+" ").indexOf(" "+classToAdd+" ") == -1) { - element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd; - } - }, - - removeClass: function(element, classToRemove){ - if (element.className.indexOf(classToRemove) == -1) { return; } - var classNames = element.className.split(" "); - classNames.splice(classNames.indexOf(classToRemove),1); - element.className = classNames.join(" "); - }, - - remove: function(item, array){ - if (!array) return; - var i = array.indexOf(item); - if (i != -1) { - return array.splice(i, 1) - }; - }, - - // Attempt to block the ability to select text while dragging controls - blockTextSelection: function(){ - document.body.focus(); - document.onselectstart = function () { return false; }; - }, - // Turn off text selection blocking - unblockTextSelection: function(){ document.onselectstart = function () { return true; }; }, - - // Return seconds as H:MM:SS or M:SS - // Supplying a guide (in seconds) will include enough leading zeros to cover the length of the guide - formatTime: function(seconds, guide) { - guide = guide || seconds; // Default to using seconds as guide - var s = Math.floor(seconds % 60), - m = Math.floor(seconds / 60 % 60), - h = Math.floor(seconds / 3600), - gm = Math.floor(guide / 60 % 60), - gh = Math.floor(guide / 3600); - - // Check if we need to show hours - h = (h > 0 || gh > 0) ? h + ":" : ""; - - // If hours are showing, we may need to add a leading zero. - // Always show at least one digit of minutes. - m = (((h || gm >= 10) && m < 10) ? "0" + m : m) + ":"; - - // Check if leading zero is need for seconds - s = (s < 10) ? "0" + s : s; - - return h + m + s; - }, - - capitalize: function(string){ - return string.charAt(0).toUpperCase() + string.slice(1); - }, - - // Return the relative horizonal position of an event as a value from 0-1 - getRelativePosition: function(x, relativeElement){ - return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth)); - }, - - getComputedStyleValue: function(element, style){ - return window.getComputedStyle(element, null).getPropertyValue(style); - }, - - trim: function(string){ return string.toString().replace(/^\s+/, "").replace(/\s+$/, ""); }, - round: function(num, dec) { - if (!dec) { dec = 0; } - return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec); - }, - - isEmpty: function(object) { - for (var prop in object) { - return false; - } - return true; - }, - - // Mimic HTML5 TimeRange Spec. - createTimeRange: function(start, end){ - return { - length: 1, - start: function() { return start; }, - end: function() { return end; } - }; - }, - - /* Element Data Store. Allows for binding data to an element without putting it directly on the element. - Ex. Event listneres are stored here. - (also from jsninja.com) - ================================================================================ */ - cache: {}, // Where the data is stored - guid: 1, // Unique ID for the element - expando: "vdata" + (new Date).getTime(), // Unique attribute to store element's guid in - - // Returns the cache object where data for the element is stored - getData: function(elem){ - var id = elem[_V_.expando]; - if (!id) { - id = elem[_V_.expando] = _V_.guid++; - _V_.cache[id] = {}; - } - return _V_.cache[id]; - }, - // Delete data for the element from the cache and the guid attr from element - removeData: function(elem){ - var id = elem[_V_.expando]; - if (!id) { return; } - // Remove all stored data - delete _V_.cache[id]; - // Remove the expando property from the DOM node - try { - delete elem[_V_.expando]; - } catch(e) { - if (elem.removeAttribute) { - elem.removeAttribute(_V_.expando); - } else { - // IE doesn't appear to support removeAttribute on the document element - elem[_V_.expando] = null; - } - } - }, - - /* Proxy (a.k.a Bind or Context). A simple method for changing the context of a function - It also stores a unique id on the function so it can be easily removed from events - ================================================================================ */ - proxy: function(context, fn) { - // Make sure the function has a unique ID - if (!fn.guid) { fn.guid = _V_.guid++; } - // Create the new function that changes the context - var ret = function() { - return fn.apply(context, arguments); - }; - - // Give the new function the same ID - // (so that they are equivalent and can be easily removed) - ret.guid = fn.guid; - - return ret; - }, - - get: function(url, onSuccess, onError){ - // if (netscape.security.PrivilegeManager.enablePrivilege) { - // netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); - // } - - var local = (url.indexOf("file:") == 0 || (window.location.href.indexOf("file:") == 0 && url.indexOf("http:") == -1)); - - if (typeof XMLHttpRequest == "undefined") { - XMLHttpRequest = function () { - try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {} - try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {} - try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {} - throw new Error("This browser does not support XMLHttpRequest."); - }; - } - - var request = new XMLHttpRequest(); - - try { - request.open("GET", url); - } catch(e) { - _V_.log("VideoJS XMLHttpRequest (open)", e); - // onError(e); - return false; - } - - request.onreadystatechange = _V_.proxy(this, function() { - if (request.readyState == 4) { - if (request.status == 200 || local && request.status == 0) { - onSuccess(request.responseText); - } else { - if (onError) { - onError(); - } - } - } - }); - - try { - request.send(); - } catch(e) { - _V_.log("VideoJS XMLHttpRequest (send)", e); - if (onError) { - onError(e); - } - } - }, - - /* Local Storage - ================================================================================ */ - setLocalStorage: function(key, value){ - // IE was throwing errors referencing the var anywhere without this - var localStorage = localStorage || false; - if (!localStorage) { return; } - try { - localStorage[key] = value; - } catch(e) { - if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014 - _V_.log("LocalStorage Full (VideoJS)", e); - } else { - _V_.log("LocalStorage Error (VideoJS)", e); - } - } - } - -}); - -// usage: log('inside coolFunc', this, arguments); -// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ -_V_.log = function(){ - _V_.log.history = _V_.log.history || [];// store logs to an array for reference - _V_.log.history.push(arguments); - if(window.console) { - arguments.callee = arguments.callee.caller; - var newarr = [].slice.call(arguments); - (typeof console.log === 'object' ? _V_.log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr)); - } -}; - -// make it safe to use console.log always -(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try -{console.log();return window.console;}catch(err){return window.console={};}})()); - -// Offset Left -// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/ -if ("getBoundingClientRect" in document.documentElement) { - _V_.findPosX = function(el) { - var box; - - try { - box = el.getBoundingClientRect(); - } catch(e) {} - - if (!box) { return 0; } - - var docEl = document.documentElement, - body = document.body, - clientLeft = docEl.clientLeft || body.clientLeft || 0, - scrollLeft = window.pageXOffset || body.scrollLeft, - left = box.left + scrollLeft - clientLeft; - - return left; - }; -} else { - _V_.findPosX = function(el) { - var curleft = el.offsetLeft; - // _V_.log(obj.className, obj.offsetLeft) - while(el = obj.offsetParent) { - if (el.className.indexOf("video-js") == -1) { - // _V_.log(el.offsetParent, "OFFSETLEFT", el.offsetLeft) - // _V_.log("-webkit-full-screen", el.webkitMatchesSelector("-webkit-full-screen")); - // _V_.log("-webkit-full-screen", el.querySelectorAll(".video-js:-webkit-full-screen")); - } else { - } - curleft += el.offsetLeft; - } - return curleft; - }; -}// Using John Resig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/ -// (function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; _V_.Class = function(){}; _V_.Class.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments); } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class;};})(); -(function(){ - var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; - _V_.Class = function(){}; - _V_.Class.extend = function(prop) { - var _super = this.prototype; - initializing = true; - var prototype = new this(); - initializing = false; - for (var name in prop) { - prototype[name] = typeof prop[name] == "function" && - typeof _super[name] == "function" && fnTest.test(prop[name]) ? - (function(name, fn){ - return function() { - var tmp = this._super; - this._super = _super[name]; - var ret = fn.apply(this, arguments); - this._super = tmp; - return ret; - }; - })(name, prop[name]) : - prop[name]; - } - function Class() { - if ( !initializing && this.init ) { - return this.init.apply(this, arguments); - - // Attempting to recreate accessing function form of class. - } else if (!initializing) { - return arguments.callee.prototype.init() - } - } - Class.prototype = prototype; - Class.constructor = Class; - Class.extend = arguments.callee; - return Class; - }; -})(); - -/* Player Component- Base class for all UI objects -================================================================================ */ -_V_.Component = _V_.Class.extend({ - - init: function(player, options){ - this.player = player; - - // Allow for overridding default component options - options = this.options = _V_.merge(this.options || {}, options); - - // Create element if one wasn't provided in options - if (options.el) { - this.el = options.el; - } else { - this.el = this.createElement(); - } - - // Add any components in options - this.initComponents(); - }, - - destroy: function(){}, - - createElement: function(type, attrs){ - return _V_.createElement(type || "div", attrs); - }, - - buildCSSClass: function(){ - // Child classes can include a function that does: - // return "CLASS NAME" + this._super(); - return ""; - }, - - initComponents: function(){ - var options = this.options; - if (options && options.components) { - // Loop through components and add them to the player - this.eachProp(options.components, function(name, opts){ - - // Allow waiting to add components until a specific event is called - var tempAdd = this.proxy(function(){ - this.addComponent(name, opts); - }); - - if (opts.loadEvent) { - this.one(opts.loadEvent, tempAdd) - } else { - tempAdd(); - } - }); - } - }, - - // Add child components to this component. - // Will generate a new child component and then append child component's element to this component's element. - // Takes either the name of the UI component class, or an object that contains a name, UI Class, and options. - addComponent: function(name, options){ - var componentClass, component; - - // Make sure options is at least an empty object to protect against errors - options = options || {}; - - // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.) - componentClass = options.componentClass || _V_.capitalize(name); - - // Create a new object & element for this controls set - // If there's no .player, this is a player - component = new _V_[componentClass](this.player || this, options); - - // Add the UI object's element to the container div (box) - this.el.appendChild(component.el); - - // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy. - this[name] = component; - }, - - /* Display - ================================================================================ */ - show: function(){ - this.el.style.display = "block"; - }, - - hide: function(){ - this.el.style.display = "none"; - }, - - fadeIn: function(){ - this.removeClass("vjs-fade-out"); - this.addClass("vjs-fade-in"); - }, - - fadeOut: function(){ - this.removeClass("vjs-fade-in"); - this.addClass("vjs-fade-out"); - }, - - addClass: function(classToAdd){ - _V_.addClass(this.el, classToAdd); - }, - - removeClass: function(classToRemove){ - _V_.removeClass(this.el, classToRemove); - }, - - /* Events - ================================================================================ */ - addEvent: function(type, fn){ - return _V_.addEvent(this.el, type, _V_.proxy(this, fn)); - }, - removeEvent: function(type, fn){ - return _V_.removeEvent(this.el, type, fn); - }, - triggerEvent: function(type, e){ - return _V_.triggerEvent(this.el, type, e); - }, - one: function(type, fn) { - _V_.one.call(this, this.el, type, fn); - }, - - /* Ready - Trigger functions when component is ready - ================================================================================ */ - ready: function(fn){ - if (!fn) return this; - - if (this.isReady) { - fn.call(this); - } else { - if (this.readyQueue === undefined) { - this.readyQueue = []; - } - this.readyQueue.push(fn); - } - - return this; - }, - - triggerReady: function(){ - this.isReady = true; - if (this.readyQueue && this.readyQueue.length > 0) { - // Call all functions in ready queue - this.each(this.readyQueue, function(fn){ - fn.call(this); - }); - - // Reset Ready Queue - this.readyQueue = []; - } - }, - - /* Utility - ================================================================================ */ - each: function(arr, fn){ _V_.each.call(this, arr, fn); }, - - eachProp: function(obj, fn){ _V_.eachProp.call(this, obj, fn); }, - - extend: function(obj){ _V_.merge(this, obj) }, - - // More easily attach 'this' to functions - proxy: function(fn){ return _V_.proxy(this, fn); } - -});/* Control - Base class for all control elements -================================================================================ */ -_V_.Control = _V_.Component.extend({ - - buildCSSClass: function(){ - return "vjs-control " + this._super(); - } - -}); - -/* Button - Base class for all buttons -================================================================================ */ -_V_.Button = _V_.Control.extend({ - - init: function(player, options){ - this._super(player, options); - - this.addEvent("click", this.onClick); - this.addEvent("focus", this.onFocus); - this.addEvent("blur", this.onBlur); - }, - - createElement: function(type, attrs){ - // Add standard Aria and Tabindex info - attrs = _V_.merge({ - className: this.buildCSSClass(), - innerHTML: '<div><span class="vjs-control-text">' + (this.buttonText || "Need Text") + '</span></div>', - role: "button", - tabIndex: 0 - }, attrs); - - return this._super(type, attrs); - }, - - // Click - Override with specific functionality for button - onClick: function(){}, - - // Focus - Add keyboard functionality to element - onFocus: function(){ - _V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); - }, - - // KeyPress (document level) - Trigger click when keys are pressed - onKeyPress: function(event){ - // Check for space bar (32) or enter (13) keys - if (event.which == 32 || event.which == 13) { - event.preventDefault(); - this.onClick(); - } - }, - - // Blur - Remove keyboard triggers - onBlur: function(){ - _V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); - } - -}); - -/* Play Button -================================================================================ */ -_V_.PlayButton = _V_.Button.extend({ - - buttonText: "Play", - - buildCSSClass: function(){ - return "vjs-play-button " + this._super(); - }, - - onClick: function(){ - this.player.play(); - } - -}); - -/* Pause Button -================================================================================ */ -_V_.PauseButton = _V_.Button.extend({ - - buttonText: "Pause", - - buildCSSClass: function(){ - return "vjs-pause-button " + this._super(); - }, - - onClick: function(){ - this.player.pause(); - } - -}); - -/* Play Toggle - Play or Pause Media -================================================================================ */ -_V_.PlayToggle = _V_.Button.extend({ - - buttonText: "Play", - - init: function(player, options){ - this._super(player, options); - - player.addEvent("play", _V_.proxy(this, this.onPlay)); - player.addEvent("pause", _V_.proxy(this, this.onPause)); - }, - - buildCSSClass: function(){ - return "vjs-play-control " + this._super(); - }, - - // OnClick - Toggle between play and pause - onClick: function(){ - if (this.player.paused()) { - this.player.play(); - } else { - this.player.pause(); - } - }, - - // OnPlay - Add the vjs-playing class to the element so it can change appearance - onPlay: function(){ - _V_.removeClass(this.el, "vjs-paused"); - _V_.addClass(this.el, "vjs-playing"); - }, - - // OnPause - Add the vjs-paused class to the element so it can change appearance - onPause: function(){ - _V_.removeClass(this.el, "vjs-playing"); - _V_.addClass(this.el, "vjs-paused"); - } - -}); - - -/* Fullscreen Toggle Behaviors -================================================================================ */ -_V_.FullscreenToggle = _V_.Button.extend({ - - buttonText: "Fullscreen", - - buildCSSClass: function(){ - return "vjs-fullscreen-control " + this._super(); - }, - - onClick: function(){ - if (!this.player.isFullScreen) { - this.player.requestFullScreen(); - } else { - this.player.cancelFullScreen(); - } - } - -}); - -/* Big Play Button -================================================================================ */ -_V_.BigPlayButton = _V_.Button.extend({ - init: function(player, options){ - this._super(player, options); - - player.addEvent("play", _V_.proxy(this, this.hide)); - player.addEvent("ended", _V_.proxy(this, this.show)); - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-big-play-button", - innerHTML: "<span></span>" - }); - }, - - onClick: function(){ - // Go back to the beginning if big play button is showing at the end. - // Have to check for current time otherwise it might throw a 'not ready' error. - if(this.player.currentTime()) { - this.player.currentTime(0); - } - this.player.play(); - } -}); - -/* Loading Spinner -================================================================================ */ -_V_.LoadingSpinner = _V_.Component.extend({ - init: function(player, options){ - this._super(player, options); - - player.addEvent("canplay", _V_.proxy(this, this.hide)); - player.addEvent("canplaythrough", _V_.proxy(this, this.hide)); - player.addEvent("playing", _V_.proxy(this, this.hide)); - - player.addEvent("seeking", _V_.proxy(this, this.show)); - player.addEvent("error", _V_.proxy(this, this.show)); - - // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner. - // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing - // player.addEvent("stalled", _V_.proxy(this, this.show)); - - player.addEvent("waiting", _V_.proxy(this, this.show)); - }, - - createElement: function(){ - - var classNameSpinner, innerHtmlSpinner; - - if ( typeof this.player.el.style.WebkitBorderRadius == "string" - || typeof this.player.el.style.MozBorderRadius == "string" - || typeof this.player.el.style.KhtmlBorderRadius == "string" - || typeof this.player.el.style.borderRadius == "string") - { - classNameSpinner = "vjs-loading-spinner"; - innerHtmlSpinner = "<div class='ball1'></div><div class='ball2'></div><div class='ball3'></div><div class='ball4'></div><div class='ball5'></div><div class='ball6'></div><div class='ball7'></div><div class='ball8'></div>"; - } else { - classNameSpinner = "vjs-loading-spinner-fallback"; - innerHtmlSpinner = ""; - } - - return this._super("div", { - className: classNameSpinner, - innerHTML: innerHtmlSpinner - }); - } -}); - -/* Control Bar -================================================================================ */ -_V_.ControlBar = _V_.Component.extend({ - - options: { - loadEvent: "play", - components: { - "playToggle": {}, - "fullscreenToggle": {}, - "currentTimeDisplay": {}, - "timeDivider": {}, - "durationDisplay": {}, - "remainingTimeDisplay": {}, - "progressControl": {}, - "volumeControl": {}, - "muteToggle": {} - } - }, - - init: function(player, options){ - this._super(player, options); - - player.addEvent("play", this.proxy(function(){ - this.fadeIn(); - this.player.addEvent("mouseover", this.proxy(this.fadeIn)); - this.player.addEvent("mouseout", this.proxy(this.fadeOut)); - })); - }, - - createElement: function(){ - return _V_.createElement("div", { - className: "vjs-controls" - }); - }, - - fadeIn: function(){ - this._super(); - this.player.triggerEvent("controlsvisible"); - }, - - fadeOut: function(){ - this._super(); - this.player.triggerEvent("controlshidden"); - } -}); - -/* Time -================================================================================ */ -_V_.CurrentTimeDisplay = _V_.Component.extend({ - - init: function(player, options){ - this._super(player, options); - - player.addEvent("timeupdate", _V_.proxy(this, this.updateContent)); - }, - - createElement: function(){ - var el = this._super("div", { - className: "vjs-current-time vjs-time-controls vjs-control" - }); - - this.content = _V_.createElement("div", { - className: "vjs-current-time-display", - innerHTML: '0:00' - }); - - el.appendChild(_V_.createElement("div").appendChild(this.content)); - return el; - }, - - updateContent: function(){ - // Allows for smooth scrubbing, when player can't keep up. - var time = (this.player.scrubbing) ? this.player.values.currentTime : this.player.currentTime(); - this.content.innerHTML = _V_.formatTime(time, this.player.duration()); - } - -}); - -_V_.DurationDisplay = _V_.Component.extend({ - - init: function(player, options){ - this._super(player, options); - - player.addEvent("timeupdate", _V_.proxy(this, this.updateContent)); - }, - - createElement: function(){ - var el = this._super("div", { - className: "vjs-duration vjs-time-controls vjs-control" - }); - - this.content = _V_.createElement("div", { - className: "vjs-duration-display", - innerHTML: '0:00' - }); - - el.appendChild(_V_.createElement("div").appendChild(this.content)); - return el; - }, - - updateContent: function(){ - if (this.player.duration()) { this.content.innerHTML = _V_.formatTime(this.player.duration()); } - } - -}); - -// Time Separator (Not used in main skin, but still available, and could be used as a 'spare element') -_V_.TimeDivider = _V_.Component.extend({ - - createElement: function(){ - return this._super("div", { - className: "vjs-time-divider", - innerHTML: '<div><span>/</span></div>' - }); - } - -}); - -_V_.RemainingTimeDisplay = _V_.Component.extend({ - - init: function(player, options){ - this._super(player, options); - - player.addEvent("timeupdate", _V_.proxy(this, this.updateContent)); - }, - - createElement: function(){ - var el = this._super("div", { - className: "vjs-remaining-time vjs-time-controls vjs-control" - }); - - this.content = _V_.createElement("div", { - className: "vjs-remaining-time-display", - innerHTML: '-0:00' - }); - - el.appendChild(_V_.createElement("div").appendChild(this.content)); - return el; - }, - - updateContent: function(){ - if (this.player.duration()) { this.content.innerHTML = "-"+_V_.formatTime(this.player.remainingTime()); } - - // Allows for smooth scrubbing, when player can't keep up. - // var time = (this.player.scrubbing) ? this.player.values.currentTime : this.player.currentTime(); - // this.content.innerHTML = _V_.formatTime(time, this.player.duration()); - } - -}); - -/* Slider - Parent for seek bar and volume slider -================================================================================ */ -_V_.Slider = _V_.Component.extend({ - - init: function(player, options){ - this._super(player, options); - - player.addEvent(this.playerEvent, _V_.proxy(this, this.update)); - - this.addEvent("mousedown", this.onMouseDown); - this.addEvent("focus", this.onFocus); - this.addEvent("blur", this.onBlur); - - this.player.addEvent("controlsvisible", this.proxy(this.update)); - - // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520 - // this.player.one("timeupdate", this.proxy(this.update)); - - this.update(); - }, - - createElement: function(type, attrs) { - attrs = _V_.merge({ - role: "slider", - "aria-valuenow": 0, - "aria-valuemin": 0, - "aria-valuemax": 100, - tabIndex: 0 - }, attrs); - - return this._super(type, attrs); - }, - - onMouseDown: function(event){ - event.preventDefault(); - _V_.blockTextSelection(); - - _V_.addEvent(document, "mousemove", _V_.proxy(this, this.onMouseMove)); - _V_.addEvent(document, "mouseup", _V_.proxy(this, this.onMouseUp)); - - this.onMouseMove(event); - }, - - onMouseUp: function(event) { - _V_.unblockTextSelection(); - _V_.removeEvent(document, "mousemove", this.onMouseMove, false); - _V_.removeEvent(document, "mouseup", this.onMouseUp, false); - - this.update(); - }, - - update: function(){ - // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse. - // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later. - // var progress = (this.player.scrubbing) ? this.player.values.currentTime / this.player.duration() : this.player.currentTime() / this.player.duration(); - - var barProgress, - progress = this.getPercent(); - handle = this.handle, - bar = this.bar; - - // Protect against no duration and other division issues - if (isNaN(progress)) { progress = 0; } - - barProgress = progress; - - // If there is a handle, we need to account for the handle in our calculation for progress bar - // so that it doesn't fall short of or extend past the handle. - if (handle) { - - var box = this.el, - boxWidth = box.offsetWidth, - - handleWidth = handle.el.offsetWidth, - - // The width of the handle in percent of the containing box - // In IE, widths may not be ready yet causing NaN - handlePercent = (handleWidth) ? handleWidth / boxWidth : 0, - - // Get the adjusted size of the box, considering that the handle's center never touches the left or right side. - // There is a margin of half the handle's width on both sides. - boxAdjustedPercent = 1 - handlePercent; - - // Adjust the progress that we'll use to set widths to the new adjusted box width - adjustedProgress = progress * boxAdjustedPercent, - - // The bar does reach the left side, so we need to account for this in the bar's width - barProgress = adjustedProgress + (handlePercent / 2); - - // Move the handle from the left based on the adjected progress - handle.el.style.left = _V_.round(adjustedProgress * 100, 2) + "%"; - } - - // Set the new bar width - bar.el.style.width = _V_.round(barProgress * 100, 2) + "%"; - }, - - calculateDistance: function(event){ - var box = this.el, - boxX = _V_.findPosX(box), - boxW = box.offsetWidth, - handle = this.handle; - - if (handle) { - var handleW = handle.el.offsetWidth; - - // Adjusted X and Width, so handle doesn't go outside the bar - boxX = boxX + (handleW / 2); - boxW = boxW - handleW; - } - - // Percent that the click is through the adjusted area - return Math.max(0, Math.min(1, (event.pageX - boxX) / boxW)); - }, - - onFocus: function(event){ - _V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); - }, - - onKeyPress: function(event){ - if (event.which == 37) { // Left Arrow - event.preventDefault(); - this.stepBack(); - } else if (event.which == 39) { // Right Arrow - event.preventDefault(); - this.stepForward(); - } - }, - - onBlur: function(event){ - _V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress)); - } -}); - - -/* Progress -================================================================================ */ - -// Progress Control: Seek, Load Progress, and Play Progress -_V_.ProgressControl = _V_.Component.extend({ - - options: { - components: { - "seekBar": {} - } - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-progress-control vjs-control" - }); - } - -}); - -// Seek Bar and holder for the progress bars -_V_.SeekBar = _V_.Slider.extend({ - - options: { - components: { - "loadProgressBar": {}, - - // Set property names to bar and handle to match with the parent Slider class is looking for - "bar": { componentClass: "PlayProgressBar" }, - "handle": { componentClass: "SeekHandle" } - } - }, - - playerEvent: "timeupdate", - - init: function(player, options){ - this._super(player, options); - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-progress-holder" - }); - }, - - getPercent: function(){ - return this.player.currentTime() / this.player.duration(); - }, - - onMouseDown: function(event){ - this._super(event); - - this.player.scrubbing = true; - - this.videoWasPlaying = !this.player.paused(); - this.player.pause(); - }, - - onMouseMove: function(event){ - var newTime = this.calculateDistance(event) * this.player.duration(); - - // Don't let video end while scrubbing. - if (newTime == this.player.duration()) { newTime = newTime - 0.1; } - - // Set new time (tell player to seek to new time) - this.player.currentTime(newTime); - }, - - onMouseUp: function(event){ - this._super(event); - - this.player.scrubbing = false; - if (this.videoWasPlaying) { - this.player.play(); - } - }, - - stepForward: function(){ - this.player.currentTime(this.player.currentTime() + 1); - }, - - stepBack: function(){ - this.player.currentTime(this.player.currentTime() - 1); - } - -}); - -// Load Progress Bar -_V_.LoadProgressBar = _V_.Component.extend({ - - init: function(player, options){ - this._super(player, options); - player.addEvent("progress", _V_.proxy(this, this.update)); - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-load-progress", - innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>' - }); - }, - - update: function(){ - if (this.el.style) { this.el.style.width = _V_.round(this.player.bufferedPercent() * 100, 2) + "%"; } - } - -}); - -// Play Progress Bar -_V_.PlayProgressBar = _V_.Component.extend({ - - createElement: function(){ - return this._super("div", { - className: "vjs-play-progress", - innerHTML: '<span class="vjs-control-text">Progress: 0%</span>' - }); - } - -}); - -// Seek Handle -// SeekBar Behavior includes play progress bar, and seek handle -// Needed so it can determine seek position based on handle position/size -_V_.SeekHandle = _V_.Component.extend({ - - createElement: function(){ - return this._super("div", { - className: "vjs-seek-handle", - innerHTML: '<span class="vjs-control-text">00:00</span>' - }); - } - -}); - -/* Volume Scrubber -================================================================================ */ -// Progress Control: Seek, Load Progress, and Play Progress -_V_.VolumeControl = _V_.Component.extend({ - - options: { - components: { - "volumeBar": {} - } - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-volume-control vjs-control" - }); - } - -}); - -_V_.VolumeBar = _V_.Slider.extend({ - - options: { - components: { - "bar": { componentClass: "VolumeLevel" }, - "handle": { componentClass: "VolumeHandle" } - } - }, - - playerEvent: "volumechange", - - createElement: function(){ - return this._super("div", { - className: "vjs-volume-bar" - }); - }, - - onMouseMove: function(event) { - this.player.volume(this.calculateDistance(event)); - }, - - getPercent: function(){ - return this.player.volume(); - }, - - stepForward: function(){ - this.player.volume(this.player.volume() + 0.1); - }, - - stepBack: function(){ - this.player.volume(this.player.volume() - 0.1); - } -}); - -_V_.VolumeLevel = _V_.Component.extend({ - - createElement: function(){ - return this._super("div", { - className: "vjs-volume-level", - innerHTML: '<span class="vjs-control-text"></span>' - }); - } - -}); - -_V_.VolumeHandle = _V_.Component.extend({ - - createElement: function(){ - return this._super("div", { - className: "vjs-volume-handle", - innerHTML: '<span class="vjs-control-text"></span>' - // tabindex: 0, - // role: "slider", "aria-valuenow": 0, "aria-valuemin": 0, "aria-valuemax": 100 - }); - } - -}); - -_V_.MuteToggle = _V_.Button.extend({ - - init: function(player, options){ - this._super(player, options); - - player.addEvent("volumechange", _V_.proxy(this, this.update)); - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-mute-control vjs-control", - innerHTML: '<div><span class="vjs-control-text">Mute</span></div>' - }); - }, - - onClick: function(event){ - this.player.muted( this.player.muted() ? false : true ); - }, - - update: function(event){ - var vol = this.player.volume(), - level = 3; - - if (vol == 0 || this.player.muted()) { - level = 0; - } else if (vol < 0.33) { - level = 1; - } else if (vol < 0.67) { - level = 2; - } - - /* TODO improve muted icon classes */ - _V_.each.call(this, [0,1,2,3], function(i){ - _V_.removeClass(this.el, "vjs-vol-"+i); - }); - _V_.addClass(this.el, "vjs-vol-"+level); - } - -}); - - -/* Poster Image -================================================================================ */ -_V_.Poster = _V_.Button.extend({ - init: function(player, options){ - this._super(player, options); - - if (!this.player.options.poster) { - this.hide(); - } - - player.addEvent("play", _V_.proxy(this, this.hide)); - }, - - createElement: function(){ - return _V_.createElement("img", { - className: "vjs-poster", - src: this.player.options.poster, - - // Don't want poster to be tabbable. - tabIndex: -1 - }); - }, - - onClick: function(){ - this.player.play(); - } -}); - - -/* Text Track Displays -================================================================================ */ -// Create a behavior type for each text track type (subtitlesDisplay, captionsDisplay, etc.). -// Then you can easily do something like. -// player.addBehavior(myDiv, "subtitlesDisplay"); -// And the myDiv's content will be updated with the text change. - -// Base class for all track displays. Should not be instantiated on its own. -_V_.TextTrackDisplay = _V_.Component.extend({ - - init: function(player, options){ - this._super(player, options); - - player.addEvent(this.trackType + "update", _V_.proxy(this, this.update)); - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-" + this.trackType - }); - }, - - update: function(){ - this.el.innerHTML = this.player.textTrackValue(this.trackType); - } - -}); - -_V_.SubtitlesDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "subtitles" - -}); - -_V_.CaptionsDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "captions" - -}); - -_V_.ChaptersDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "chapters" - -}); - -_V_.DescriptionsDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "descriptions" - -});// ECMA-262 is the standard for javascript. -// The following methods are impelemented EXACTLY as described in the standard (according to Mozilla Docs), and do not override the default method if one exists. -// This may conflict with other libraries that modify the array prototype, but those libs should update to use the standard. - -// [].indexOf -// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf -if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { - "use strict"; - if (this === void 0 || this === null) { - throw new TypeError(); - } - var t = Object(this); - var len = t.length >>> 0; - if (len === 0) { - return -1; - } - var n = 0; - if (arguments.length > 0) { - n = Number(arguments[1]); - if (n !== n) { // shortcut for verifying if it's NaN - n = 0; - } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - if (n >= len) { - return -1; - } - var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); - for (; k < len; k++) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - } -} - -// NOT NEEDED YET -// [].lastIndexOf -// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf -// if (!Array.prototype.lastIndexOf) -// { -// Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) -// { -// "use strict"; -// -// if (this === void 0 || this === null) -// throw new TypeError(); -// -// var t = Object(this); -// var len = t.length >>> 0; -// if (len === 0) -// return -1; -// -// var n = len; -// if (arguments.length > 1) -// { -// n = Number(arguments[1]); -// if (n !== n) -// n = 0; -// else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) -// n = (n > 0 || -1) * Math.floor(Math.abs(n)); -// } -// -// var k = n >= 0 -// ? Math.min(n, len - 1) -// : len - Math.abs(n); -// -// for (; k >= 0; k--) -// { -// if (k in t && t[k] === searchElement) -// return k; -// } -// return -1; -// }; -// } - - -// NOT NEEDED YET -// Array forEach per ECMA standard https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach -// Production steps of ECMA-262, Edition 5, 15.4.4.18 -// Reference: http://es5.github.com/#x15.4.4.18 -// if ( !Array.prototype.forEach ) { -// -// Array.prototype.forEach = function( callback, thisArg ) { -// -// var T, k; -// -// if ( this == null ) { -// throw new TypeError( " this is null or not defined" ); -// } -// -// // 1. Let O be the result of calling ToObject passing the |this| value as the argument. -// var O = Object(this); -// -// // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". -// // 3. Let len be ToUint32(lenValue). -// var len = O.length >>> 0; -// -// // 4. If IsCallable(callback) is false, throw a TypeError exception. -// // See: http://es5.github.com/#x9.11 -// if ( {}.toString.call(callback) != "[object Function]" ) { -// throw new TypeError( callback + " is not a function" ); -// } -// -// // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. -// if ( thisArg ) { -// T = thisArg; -// } -// -// // 6. Let k be 0 -// k = 0; -// -// // 7. Repeat, while k < len -// while( k < len ) { -// -// var kValue; -// -// // a. Let Pk be ToString(k). -// // This is implicit for LHS operands of the in operator -// // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. -// // This step can be combined with c -// // c. If kPresent is true, then -// if ( k in O ) { -// -// // i. Let kValue be the result of calling the Get internal method of O with argument Pk. -// kValue = O[ Pk ]; -// -// // ii. Call the Call internal method of callback with T as the this value and -// // argument list containing kValue, k, and O. -// callback.call( T, kValue, k, O ); -// } -// // d. Increase k by 1. -// k++; -// } -// // 8. return undefined -// }; -// } - - -// NOT NEEDED YET -// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map -// Production steps of ECMA-262, Edition 5, 15.4.4.19 -// Reference: http://es5.github.com/#x15.4.4.19 -// if (!Array.prototype.map) { -// Array.prototype.map = function(callback, thisArg) { -// -// var T, A, k; -// -// if (this == null) { -// throw new TypeError(" this is null or not defined"); -// } -// -// // 1. Let O be the result of calling ToObject passing the |this| value as the argument. -// var O = Object(this); -// -// // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length". -// // 3. Let len be ToUint32(lenValue). -// var len = O.length >>> 0; -// -// // 4. If IsCallable(callback) is false, throw a TypeError exception. -// // See: http://es5.github.com/#x9.11 -// if ({}.toString.call(callback) != "[object Function]") { -// throw new TypeError(callback + " is not a function"); -// } -// -// // 5. If thisArg was supplied, let T be thisArg; else let T be undefined. -// if (thisArg) { -// T = thisArg; -// } -// -// // 6. Let A be a new array created as if by the expression new Array(len) where Array is -// // the standard built-in constructor with that name and len is the value of len. -// A = new Array(len); -// -// // 7. Let k be 0 -// k = 0; -// -// // 8. Repeat, while k < len -// while(k < len) { -// -// var kValue, mappedValue; -// -// // a. Let Pk be ToString(k). -// // This is implicit for LHS operands of the in operator -// // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk. -// // This step can be combined with c -// // c. If kPresent is true, then -// if (k in O) { -// -// // i. Let kValue be the result of calling the Get internal method of O with argument Pk. -// kValue = O[ k ]; -// -// // ii. Let mappedValue be the result of calling the Call internal method of callback -// // with T as the this value and argument list containing kValue, k, and O. -// mappedValue = callback.call(T, kValue, k, O); -// -// // iii. Call the DefineOwnProperty internal method of A with arguments -// // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true}, -// // and false. -// -// // In browsers that support Object.defineProperty, use the following: -// // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true }); -// -// // For best browser support, use the following: -// A[ k ] = mappedValue; -// } -// // d. Increase k by 1. -// k++; -// } -// -// // 9. return A -// return A; -// }; -// } -// Event System (J.Resig - Secrets of a JS Ninja http://jsninja.com/ [Go read it, really]) -// (Book version isn't completely usable, so fixed some things and borrowed from jQuery where it's working) -// -// This should work very similarly to jQuery's events, however it's based off the book version which isn't as -// robust as jquery's, so there's probably some differences. -// -// When you add an event listener using _V_.addEvent, -// it stores the handler function in seperate cache object, -// and adds a generic handler to the element's event, -// along with a unique id (guid) to the element. - -_V_.extend({ - - // Add an event listener to element - // It stores the handler function in a separate cache object - // and adds a generic handler to the element's event, - // along with a unique id (guid) to the element. - addEvent: function(elem, type, fn){ - var data = _V_.getData(elem), handlers; - - // We only need to generate one handler per element - if (data && !data.handler) { - // Our new meta-handler that fixes the event object and the context - data.handler = function(event){ - event = _V_.fixEvent(event); - var handlers = _V_.getData(elem).events[event.type]; - // Go through and call all the real bound handlers - if (handlers) { - - // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off. - var handlersCopy = []; - _V_.each(handlers, function(handler, i){ - handlersCopy[i] = handler; - }) - - for (var i = 0, l = handlersCopy.length; i < l; i++) { - handlersCopy[i].call(elem, event); - } - } - }; - } - - // We need a place to store all our event data - if (!data.events) { data.events = {}; } - - // And a place to store the handlers for this event type - handlers = data.events[type]; - - if (!handlers) { - handlers = data.events[ type ] = []; - - // Attach our meta-handler to the element, since one doesn't exist - if (document.addEventListener) { - elem.addEventListener(type, data.handler, false); - } else if (document.attachEvent) { - elem.attachEvent("on" + type, data.handler); - } - } - - if (!fn.guid) { fn.guid = _V_.guid++; } - - handlers.push(fn); - }, - - removeEvent: function(elem, type, fn) { - var data = _V_.getData(elem), handlers; - // If no events exist, nothing to unbind - if (!data.events) { return; } - - // Are we removing all bound events? - if (!type) { - for (type in data.events) { - _V_.cleanUpEvents(elem, type); - } - return; - } - - // And a place to store the handlers for this event type - handlers = data.events[type]; - - // If no handlers exist, nothing to unbind - if (!handlers) { return; } - - // See if we're only removing a single handler - if (fn && fn.guid) { - for (var i = 0; i < handlers.length; i++) { - // We found a match (don't stop here, there could be a couple bound) - if (handlers[i].guid === fn.guid) { - // Remove the handler from the array of handlers - handlers.splice(i--, 1); - } - } - } - - _V_.cleanUpEvents(elem, type); - }, - - cleanUpEvents: function(elem, type) { - var data = _V_.getData(elem); - // Remove the events of a particular type if there are none left - - if (data.events[type].length === 0) { - delete data.events[type]; - - // Remove the meta-handler from the element - if (document.removeEventListener) { - elem.removeEventListener(type, data.handler, false); - } else if (document.detachEvent) { - elem.detachEvent("on" + type, data.handler); - } - } - - // Remove the events object if there are no types left - if (_V_.isEmpty(data.events)) { - delete data.events; - delete data.handler; - } - - // Finally remove the expando if there is no data left - if (_V_.isEmpty(data)) { - _V_.removeData(elem); - } - }, - - fixEvent: function(event) { - if (event[_V_.expando]) { return event; } - // store a copy of the original event object - // and "clone" to set read-only properties - var originalEvent = event; - event = new _V_.Event(originalEvent); - - for ( var i = _V_.Event.props.length, prop; i; ) { - prop = _V_.Event.props[ --i ]; - event[prop] = originalEvent[prop]; - } - - // Fix target property, if necessary - if (!event.target) { event.target = event.srcElement || document; } - - // check if target is a textnode (safari) - if (event.target.nodeType === 3) { event.target = event.target.parentNode; } - - // Add relatedTarget, if necessary - if (!event.relatedTarget && event.fromElement) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; - } - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var eventDocument = event.target.ownerDocument || document, - doc = eventDocument.documentElement, - body = eventDocument.body; - - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add which for key events - if (event.which == null && (event.charCode != null || event.keyCode != null)) { - event.which = event.charCode != null ? event.charCode : event.keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - } - - return event; - }, - - triggerEvent: function(elem, event) { - var data = _V_.getData(elem), - parent = elem.parentNode || elem.ownerDocument, - type = event.type || event, - handler; - - if (data) { handler = data.handler } - - // Added in attion to book. Book code was broke. - event = typeof event === "object" ? - event[_V_.expando] ? - event : - new _V_.Event(type, event) : - new _V_.Event(type); - - event.type = type; - if (handler) { - handler.call(elem, event); - } - - // Clean up the event in case it is being reused - event.result = undefined; - event.target = elem; - - // Bubble the event up the tree to the document, - // Unless it's been explicitly stopped - // if (parent && !event.isPropagationStopped()) { - // _V_.triggerEvent(parent, event); - // - // // We're at the top document so trigger the default action - // } else if (!parent && !event.isDefaultPrevented()) { - // // log(type); - // var targetData = _V_.getData(event.target); - // // log(targetData); - // var targetHandler = targetData.handler; - // // log("2"); - // if (event.target[event.type]) { - // // Temporarily disable the bound handler, - // // don't want to execute it twice - // if (targetHandler) { - // targetData.handler = function(){}; - // } - // - // // Trigger the native event (click, focus, blur) - // event.target[event.type](); - // - // // Restore the handler - // if (targetHandler) { - // targetData.handler = targetHandler; - // } - // } - // } - }, - - one: function(elem, type, fn) { - _V_.addEvent(elem, type, function(){ - _V_.removeEvent(elem, type, arguments.callee) - fn.apply(this, arguments); - }); - } -}); - -// Custom Event object for standardizing event objects between browsers. -_V_.Event = function(src, props){ - // Event object - if (src && src.type) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if (props) { _V_.merge(this, props); } - - this.timeStamp = (new Date).getTime(); - - // Mark it as fixed - this[_V_.expando] = true; -}; - -_V_.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if (!e) { return; } - - // if preventDefault exists run it on the original event - if (e.preventDefault) { - e.preventDefault(); - // otherwise set the returnValue property of the original event to false (IE) - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if (!e) { return; } - // if stopPropagation exists run it on the original event - if (e.stopPropagation) { e.stopPropagation(); } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse -}; -_V_.Event.props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "); - -function returnTrue(){ return true; } -function returnFalse(){ return false; } - -// Javascript JSON implementation -// (Parse Method Only) -// https://github.com/douglascrockford/JSON-js/blob/master/json2.js - -var JSON; -if (!JSON) { JSON = {}; } - -(function(){ - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { - var j; - - function walk(holder, key) { - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - - j = eval('(' + text + ')'); - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - - throw new SyntaxError('JSON.parse'); - }; - } -}()); -/* UI Component- Base class for all UI objects -================================================================================ */ -_V_.Player = _V_.Component.extend({ - - init: function(tag, addOptions, ready){ - - this.tag = tag; // Store the original tag used to set options - - var el = this.el = _V_.createElement("div"), // Div to contain video and controls - options = this.options = {}, - width = options.width = tag.getAttribute('width'), - height = options.height = tag.getAttribute('height'), - - // Browsers default to 300x150 if there's no width/height or video size data. - initWidth = width || 300, - initHeight = height || 150; - - // Make player findable on elements - tag.player = el.player = this; - - // Add callback to ready queue - this.ready(ready); - - // Wrap video tag in div (el/box) container - tag.parentNode.insertBefore(el, tag); - el.appendChild(tag); // Breaks iPhone, fixed in HTML5 setup. - - // Give video tag properties to box - el.id = this.id = tag.id; // ID will now reference box, not the video tag - el.className = tag.className; - // Update tag id/class for use as HTML5 playback tech - tag.id += "_html5_api"; - tag.className = "vjs-tech"; - - // Make player easily findable by ID - _V_.players[el.id] = this; - - // Make box use width/height of tag, or default 300x150 - el.setAttribute("width", initWidth); - el.setAttribute("height", initHeight); - // Enforce with CSS since width/height attrs don't work on divs - el.style.width = initWidth+"px"; - el.style.height = initHeight+"px"; - // Remove width/height attrs from tag so CSS can make it 100% width/height - tag.removeAttribute("width"); - tag.removeAttribute("height"); - - // Set Options - _V_.merge(options, _V_.options); // Copy Global Defaults - _V_.merge(options, this.getVideoTagSettings()); // Override with Video Tag Options - _V_.merge(options, addOptions); // Override/extend with options from setup call - - // Store controls setting, and then remove immediately so native controls don't flash. - tag.removeAttribute("controls"); - - // Poster will be handled by a manual <img> - tag.removeAttribute("poster"); - - // Empty video tag sources and tracks so the built in player doesn't use them also. - if (tag.hasChildNodes()) { - for (var i=0,j=tag.childNodes;i<j.length;i++) { - if (j[i].nodeName == "SOURCE" || j[i].nodeName == "TRACK") { - tag.removeChild(j[i]); - } - } - } - - // Holder for playback tech components - this.techs = {}; - - // Cache for video property values. - this.values = {}; - - this.addClass("vjs-paused"); - - this.addEvent("ended", this.onEnded); - this.addEvent("play", this.onPlay); - this.addEvent("pause", this.onPause); - this.addEvent("error", this.onError); - - // When the API is ready, loop through the components and add to the player. - if (options.controls) { - this.ready(function(){ - this.initComponents(); - }); - } - - // If there are no sources when the player is initialized, - // load the first supported playback technology. - if (!options.sources || options.sources.length == 0) { - for (var i=0,j=options.techOrder; i<j.length; i++) { - var techName = j[i], - tech = _V_[techName]; - - // Check if the browser supports this technology - if (tech.isSupported()) { - this.loadTech(techName); - break; - } - } - } else { - // Loop through playback technologies (HTML5, Flash) and check for support - // Then load the best source. - this.src(options.sources); - } - }, - - // Cache for video property values. - values: {}, - - destroy: function(){ - // Ensure that tracking progress and time progress will stop and plater deleted - this.stopTrackingProgress(); - this.stopTrackingCurrentTime(); - delete _V_.players[this.id] - }, - - createElement: function(type, options){ - - }, - - getVideoTagSettings: function(){ - var options = { - sources: [], - tracks: [] - }; - - options.src = this.tag.getAttribute("src"); - options.controls = this.tag.getAttribute("controls") !== null; - options.poster = this.tag.getAttribute("poster"); - options.preload = this.tag.getAttribute("preload"); - options.autoplay = this.tag.getAttribute("autoplay") !== null; // hasAttribute not IE <8 compatible - options.loop = this.tag.getAttribute("loop") !== null; - options.muted = this.tag.getAttribute("muted") !== null; - - if (this.tag.hasChildNodes()) { - for (var c,i=0,j=this.tag.childNodes;i<j.length;i++) { - c = j[i]; - if (c.nodeName == "SOURCE") { - options.sources.push({ - src: c.getAttribute('src'), - type: c.getAttribute('type'), - media: c.getAttribute('media'), - title: c.getAttribute('title') - }); - } - if (c.nodeName == "TRACK") { - options.tracks.push(new _V_.Track({ - src: c.getAttribute("src"), - kind: c.getAttribute("kind"), - srclang: c.getAttribute("srclang"), - label: c.getAttribute("label"), - 'default': c.getAttribute("default") !== null, - title: c.getAttribute("title") - }, this)); - - } - } - } - return options; - }, - - /* PLayback Technology (tech) - ================================================================================ */ - // Load/Create an instance of playback technlogy including element and API methods - // And append playback element in player div. - loadTech: function(techName, source){ - - // Pause and remove current playback technology - if (this.tech) { - this.unloadTech(); - - // If the first time loading, HTML5 tag will exist but won't be initialized - // So we need to remove it if we're not loading HTML5 - } else if (techName != "html5" && this.tag) { - this.el.removeChild(this.tag); - this.tag = false; - } - - this.techName = techName; - - // Turn off API access because we're loading a new tech that might load asynchronously - this.isReady = false; - - var techReady = function(){ - this.player.triggerReady(); - - // Manually track progress in cases where the browser/flash player doesn't report it. - if (!this.support.progressEvent) { - this.player.manualProgressOn(); - } - - // Manually track timeudpates in cases where the browser/flash player doesn't report it. - if (!this.support.timeupdateEvent) { - this.player.manualTimeUpdatesOn(); - } - } - - // Grab tech-specific options from player options and add source and parent element to use. - var techOptions = _V_.merge({ source: source, parentEl: this.el }, this.options[techName]) - - if (source) { - if (source.src == this.values.src && this.values.currentTime > 0) { - techOptions.startTime = this.values.currentTime; - } - - this.values.src = source.src; - } - - // Initialize tech instance - this.tech = new _V_[techName](this, techOptions); - this.tech.ready(techReady); - }, - - unloadTech: function(){ - this.tech.destroy(); - - // Turn off any manual progress or timeupdate tracking - if (this.manualProgress) { this.manualProgressOff(); } - - if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); } - - this.tech = false; - }, - - // There's many issues around changing the size of a Flash (or other plugin) object. - // First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268 - // Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen. - // To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized. - // reloadTech: function(betweenFn){ - // _V_.log("unloadingTech") - // this.unloadTech(); - // _V_.log("unloadedTech") - // if (betweenFn) { betweenFn.call(); } - // _V_.log("LoadingTech") - // this.loadTech(this.techName, { src: this.values.src }) - // _V_.log("loadedTech") - // }, - - /* Fallbacks for unsupported event types - ================================================================================ */ - // Manually trigger progress events based on changes to the buffered amount - // Many flash players and older HTML5 browsers don't send progress or progress-like events - manualProgressOn: function(){ - this.manualProgress = true; - - // Trigger progress watching when a source begins loading - this.trackProgress(); - - // Watch for a native progress event call on the tech element - // In HTML5, some older versions don't support the progress event - // So we're assuming they don't, and turning off manual progress if they do. - this.tech.addEvent("progress", function(){ - - // Remove this listener from the element - this.removeEvent("progress", arguments.callee); - - // Update known progress support for this playback technology - this.support.progressEvent = true; - - // Turn off manual progress tracking - this.player.manualProgressOff(); - }); - }, - - manualProgressOff: function(){ - this.manualProgress = false; - this.stopTrackingProgress(); - }, - - trackProgress: function(){ - this.progressInterval = setInterval(_V_.proxy(this, function(){ - // Don't trigger unless buffered amount is greater than last time - // log(this.values.bufferEnd, this.buffered().end(0), this.duration()) - /* TODO: update for multiple buffered regions */ - if (this.values.bufferEnd < this.buffered().end(0)) { - this.triggerEvent("progress"); - } else if (this.bufferedPercent() == 1) { - this.stopTrackingProgress(); - this.triggerEvent("progress"); // Last update - } - }), 500); - }, - stopTrackingProgress: function(){ clearInterval(this.progressInterval); }, - - /* Time Tracking -------------------------------------------------------------- */ - manualTimeUpdatesOn: function(){ - this.manualTimeUpdates = true; - - this.addEvent("play", this.trackCurrentTime); - this.addEvent("pause", this.stopTrackingCurrentTime); - // timeupdate is also called by .currentTime whenever current time is set - - // Watch for native timeupdate event - this.tech.addEvent("timeupdate", function(){ - - // Remove this listener from the element - this.removeEvent("timeupdate", arguments.callee); - - // Update known progress support for this playback technology - this.support.timeupdateEvent = true; - - // Turn off manual progress tracking - this.player.manualTimeUpdatesOff(); - }); - }, - - manualTimeUpdatesOff: function(){ - this.manualTimeUpdates = false; - this.stopTrackingCurrentTime(); - this.removeEvent("play", this.trackCurrentTime); - this.removeEvent("pause", this.stopTrackingCurrentTime); - }, - - trackCurrentTime: function(){ - if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); } - this.currentTimeInterval = setInterval(_V_.proxy(this, function(){ - this.triggerEvent("timeupdate"); - }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15 - }, - - // Turn off play progress tracking (when paused or dragging) - stopTrackingCurrentTime: function(){ clearInterval(this.currentTimeInterval); }, - - /* Player event handlers (how the player reacts to certain events) - ================================================================================ */ - onEnded: function(){ - if (this.options.loop) { - this.currentTime(0); - this.play(); - } else { - this.pause(); - this.currentTime(0); - this.pause(); - } - }, - - onPlay: function(){ - _V_.removeClass(this.el, "vjs-paused"); - _V_.addClass(this.el, "vjs-playing"); - }, - - onPause: function(){ - _V_.removeClass(this.el, "vjs-playing"); - _V_.addClass(this.el, "vjs-paused"); - }, - - onError: function(e) { - _V_.log("Video Error", e); - }, - -/* Player API -================================================================================ */ - - apiCall: function(method, arg){ - if (this.isReady) { - return this.tech[method](arg); - } else { - _V_.log("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]", arguments.callee.caller.arguments.callee.caller.arguments.callee.caller) - return false; - // throw new Error("The playback technology API is not ready yet. Use player.ready(myFunction)."+" ["+method+"]"); - } - }, - - play: function(){ - this.apiCall("play"); return this; - }, - pause: function(){ - this.apiCall("pause"); return this; - }, - paused: function(){ - return this.apiCall("paused"); - }, - - currentTime: function(seconds){ - if (seconds !== undefined) { - - // Cache the last set value for smoother scrubbing. - this.values.lastSetCurrentTime = seconds; - - this.apiCall("setCurrentTime", seconds); - - if (this.manualTimeUpdates) { - this.triggerEvent("timeupdate"); - } - return this; - } - - // Cache last currentTime and return - return this.values.currentTime = this.apiCall("currentTime"); - }, - duration: function(){ - return this.apiCall("duration"); - }, - remainingTime: function(){ - return this.duration() - this.currentTime(); - }, - - buffered: function(){ - var buffered = this.apiCall("buffered"), - start = 0, end = this.values.bufferEnd = this.values.bufferEnd || 0, - timeRange; - - if (buffered && buffered.length > 0 && buffered.end(0) !== end) { - end = buffered.end(0); - // Storing values allows them be overridden by setBufferedFromProgress - this.values.bufferEnd = end; - } - - return _V_.createTimeRange(start, end); - }, - - // Calculates amount of buffer is full - bufferedPercent: function(){ - return (this.duration()) ? this.buffered().end(0) / this.duration() : 0; - }, - - volume: function(percentAsDecimal){ - if (percentAsDecimal !== undefined) { - var vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1 - this.values.volume = vol; - this.apiCall("setVolume", vol); - _V_.setLocalStorage("volume", vol); - return this; - } - // if (this.values.volume) { return this.values.volume; } - return this.apiCall("volume"); - }, - muted: function(muted){ - if (muted !== undefined) { - this.apiCall("setMuted", muted); - return this; - } - return this.apiCall("muted"); - }, - - width: function(width, skipListeners){ - if (width !== undefined) { - this.el.width = width; - this.el.style.width = width+"px"; - if (!skipListeners) { this.triggerEvent("resize"); } - return this; - } - return parseInt(this.el.getAttribute("width")); - }, - height: function(height){ - if (height !== undefined) { - this.el.height = height; - this.el.style.height = height+"px"; - this.triggerEvent("resize"); - return this; - } - return parseInt(this.el.getAttribute("height")); - }, - size: function(width, height){ - // Skip resize listeners on width for optimization - return this.width(width, true).height(height); - }, - - supportsFullScreen: function(){ return this.apiCall("supportsFullScreen"); }, - - // Turn on fullscreen (or window) mode - requestFullScreen: function(){ - var requestFullScreen = _V_.support.requestFullScreen; - - this.isFullScreen = true; - - // Check for browser element fullscreen support - if (requestFullScreen) { - - // Flash and other plugins get reloaded when you take their parent to fullscreen. - // To fix that we'll remove the tech, and reload it after the resize has finished. - if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) { - - this.pause(); - this.unloadTech(); - - _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){ - _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee); - this.loadTech(this.techName, { src: this.values.src }); - })); - - this.el[requestFullScreen.requestFn](); - - } else { - this.el[requestFullScreen.requestFn](); - } - - // In case the user presses escape to exit fullscreen, we need to update fullscreen status - _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){ - this.isFullScreen = document[requestFullScreen.isFullScreen]; - })); - - } else if (this.tech.supportsFullScreen()) { - this.apiCall("enterFullScreen"); - - } else { - this.enterFullWindow(); - } - - this.triggerEvent("fullscreenchange"); - - return this; - }, - - cancelFullScreen: function(){ - var requestFullScreen = _V_.support.requestFullScreen; - - // Check for browser element fullscreen support - if (requestFullScreen) { - - // Flash and other plugins get reloaded when you take their parent to fullscreen. - // To fix that we'll remove the tech, and reload it after the resize has finished. - if (this.tech.support.fullscreenResize === false && this.options.flash.iFrameMode != true) { - - this.pause(); - this.unloadTech(); - - _V_.addEvent(document, requestFullScreen.eventName, this.proxy(function(){ - _V_.removeEvent(document, requestFullScreen.eventName, arguments.callee); - this.loadTech(this.techName, { src: this.values.src }) - })); - - document[requestFullScreen.cancelFn](); - - } else { - document[requestFullScreen.cancelFn](); - } - - } else if (this.tech.supportsFullScreen()) { - this.apiCall("exitFullScreen"); - - } else { - this.exitFullWindow(); - } - - this.isFullScreen = false; - this.triggerEvent("fullscreenchange"); - - return this; - }, - - enterFullWindow: function(){ - this.isFullWindow = true; - - // Storing original doc overflow value to return to when fullscreen is off - this.docOrigOverflow = document.documentElement.style.overflow; - - // Add listener for esc key to exit fullscreen - _V_.addEvent(document, "keydown", _V_.proxy(this, this.fullWindowOnEscKey)); - - // Hide any scroll bars - document.documentElement.style.overflow = 'hidden'; - - // Apply fullscreen styles - _V_.addClass(document.body, "vjs-full-window"); - _V_.addClass(this.el, "vjs-fullscreen"); - - this.triggerEvent("enterFullWindow"); - }, - - fullWindowOnEscKey: function(event){ - if (event.keyCode == 27) { - if (this.isFullScreen == true) { - this.cancelFullScreen(); - } else { - this.exitFullWindow(); - } - } - }, - - exitFullWindow: function(){ - this.isFullWindow = false; - _V_.removeEvent(document, "keydown", this.fullWindowOnEscKey); - - // Unhide scroll bars. - document.documentElement.style.overflow = this.docOrigOverflow; - - // Remove fullscreen styles - _V_.removeClass(document.body, "vjs-full-window"); - _V_.removeClass(this.el, "vjs-fullscreen"); - - // Resize the box, controller, and poster to original sizes - // this.positionAll(); - this.triggerEvent("exitFullWindow"); - }, - - // src is a pretty powerful function - // If you pass it an array of source objects, it will find the best source to play and use that object.src - // If the new source requires a new playback technology, it will switch to that. - // If you pass it an object, it will set the source to object.src - // If you pass it anything else (url string) it will set the video source to that - src: function(source){ - // Case: Array of source objects to choose from and pick the best to play - if (source instanceof Array) { - - var sources = source; - - techLoop: // Named loop for breaking both loops - // Loop through each playback technology in the options order - for (var i=0,j=this.options.techOrder;i<j.length;i++) { - var techName = j[i], - tech = _V_[techName]; - // tech = _V_.tech[techName]; - - // Check if the browser supports this technology - if (tech.isSupported()) { - - // Loop through each source object - for (var a=0,b=sources;a<b.length;a++) { - var source = b[a]; - - // Check if source can be played with this technology - if (tech.canPlaySource.call(this, source)) { - - // If this technology is already loaded, set source - if (techName == this.techName) { - this.src(source); // Passing the source object - - // Otherwise load this technology with chosen source - } else { - this.loadTech(techName, source); - } - - break techLoop; // Break both loops - } - } - } - } - - // Case: Source object { src: "", type: "" ... } - } else if (source instanceof Object) { - if (_V_[this.techName].canPlaySource(source)) { - this.src(source.src); - } else { - // Send through tech loop to check for a compatible technology. - this.src([source]); - } - // Case: URL String (http://myvideo...) - } else { - // Cache for getting last set source - this.values.src = source; - - if (!this.isReady) { - this.ready(function(){ - this.src(source); - }); - } else { - this.apiCall("src", source); - if (this.options.preload == "auto") { - this.load(); - } - if (this.options.autoplay) { - this.play(); - } - } - } - return this; - }, - - // Begin loading the src data - load: function(){ - this.apiCall("load"); - return this; - }, - currentSrc: function(){ - return this.apiCall("currentSrc"); - }, - - textTrackValue: function(kind, value){ - if (value !== undefined) { - this.values[kind] = value; - this.triggerEvent(kind+"update"); - return this; - } - return this.values[kind]; - }, - - // Attributes/Options - preload: function(value){ - if (value !== undefined) { - this.apiCall("setPreload", value); - this.options.preload = value; - return this; - } - return this.apiCall("preload", value); - }, - autoplay: function(value){ - if (value !== undefined) { - this.apiCall("setAutoplay", value); - this.options.autoplay = value; - return this; - } - return this.apiCall("autoplay", value); - }, - loop: function(value){ - if (value !== undefined) { - this.apiCall("setLoop", value); - this.options.loop = value; - return this; - } - return this.apiCall("loop", value); - }, - - controls: function(){ return this.options.controls; }, - textTracks: function(){ return this.options.tracks; }, - poster: function(){ return this.apiCall("poster"); }, - - error: function(){ return this.apiCall("error"); }, - networkState: function(){ return this.apiCall("networkState"); }, - readyState: function(){ return this.apiCall("readyState"); }, - seeking: function(){ return this.apiCall("seeking"); }, - initialTime: function(){ return this.apiCall("initialTime"); }, - startOffsetTime: function(){ return this.apiCall("startOffsetTime"); }, - played: function(){ return this.apiCall("played"); }, - seekable: function(){ return this.apiCall("seekable"); }, - ended: function(){ return this.apiCall("ended"); }, - videoTracks: function(){ return this.apiCall("videoTracks"); }, - audioTracks: function(){ return this.apiCall("audioTracks"); }, - videoWidth: function(){ return this.apiCall("videoWidth"); }, - videoHeight: function(){ return this.apiCall("videoHeight"); }, - defaultPlaybackRate: function(){ return this.apiCall("defaultPlaybackRate"); }, - playbackRate: function(){ return this.apiCall("playbackRate"); }, - // mediaGroup: function(){ return this.apiCall("mediaGroup"); }, - // controller: function(){ return this.apiCall("controller"); }, - controls: function(){ return this.apiCall("controls"); }, - defaultMuted: function(){ return this.apiCall("defaultMuted"); } -}); - -// RequestFullscreen API -(function(){ - var requestFn, - cancelFn, - eventName, - isFullScreen, - playerProto = _V_.Player.prototype; - - // Current W3C Spec - // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api - // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event - if (document.cancelFullscreen !== undefined) { - requestFn = "requestFullscreen"; - cancelFn = "exitFullscreen"; - eventName = "fullscreenchange"; - isFullScreen = "fullScreen"; - - // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementaitons - // that use prefixes and vary slightly from the new W3C spec. Specifically, using 'exit' instead of 'cancel', - // and lowercasing the 'S' in Fullscreen. - // Other browsers don't have any hints of which version they might follow yet, so not going to try to predict by loopeing through all prefixes. - } else { - - _V_.each(["moz", "webkit"], function(prefix){ - - // https://github.com/zencoder/video-js/pull/128 - if ((prefix != "moz" || document.mozFullScreenEnabled) && document[prefix + "CancelFullScreen"] !== undefined) { - requestFn = prefix + "RequestFullScreen"; - cancelFn = prefix + "CancelFullScreen"; - eventName = prefix + "fullscreenchange"; - - if (prefix == "webkit") { - isFullScreen = prefix + "IsFullScreen"; - } else { - _V_.log("moz here") - isFullScreen = prefix + "FullScreen"; - } - } - - }); - - } - - if (requestFn) { - _V_.support.requestFullScreen = { - requestFn: requestFn, - cancelFn: cancelFn, - eventName: eventName, - isFullScreen: isFullScreen - }; - } - -})();/* Playback Technology - Base class for playback technologies -================================================================================ */ -_V_.PlaybackTech = _V_.Component.extend({ - init: function(player, options){ - // this._super(player, options); - - // Make playback element clickable - // _V_.addEvent(this.el, "click", _V_.proxy(this, _V_.PlayToggle.prototype.onClick)); - - // this.addEvent("click", this.proxy(this.onClick)); - - // player.triggerEvent("techready"); - }, - // destroy: function(){}, - // createElement: function(){}, - onClick: function(){ - if (this.player.options.controls) { - _V_.PlayToggle.prototype.onClick.call(this); - } - } -}); - -// Create placeholder methods for each that warn when a method isn't supported by the current playback technology -_V_.apiMethods = "play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(","); -_V_.each(_V_.apiMethods, function(methodName){ - _V_.PlaybackTech.prototype[methodName] = function(){ - throw new Error("The '"+method+"' method is not available on the playback technology's API"); - } -}); - -/* HTML5 Playback Technology - Wrapper for HTML5 Media API -================================================================================ */ -_V_.html5 = _V_.PlaybackTech.extend({ - - init: function(player, options, ready){ - this.player = player; - this.el = this.createElement(); - this.ready(ready); - - this.addEvent("click", this.proxy(this.onClick)); - - var source = options.source; - - // If the element source is already set, we may have missed the loadstart event, and want to trigger it. - // We don't want to set the source again and interrupt playback. - if (source && this.el.currentSrc == source.src) { - player.triggerEvent("loadstart"); - - // Otherwise set the source if one was provided. - } else if (source) { - this.el.src = source.src; - } - - // Chrome and Safari both have issues with autoplay. - // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work. - // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays) - // This fixes both issues. Need to wait for API, so it updates displays correctly - player.ready(function(){ - if (this.options.autoplay && this.paused()) { - this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16. - this.play(); - } - }); - - this.setupTriggers(); - - this.triggerReady(); - }, - - destroy: function(){ - this.player.tag = false; - this.removeTriggers(); - this.el.parentNode.removeChild(this.el); - }, - - createElement: function(){ - var html5 = _V_.html5, - player = this.player, - - // If possible, reuse original tag for HTML5 playback technology element - el = player.tag, - newEl; - - // Check if this browser supports moving the element into the box. - // On the iPhone video will break if you move the element, - // So we have to create a brand new element. - if (!el || this.support.movingElementInDOM === false) { - - // If the original tag is still there, remove it. - if (el) { - player.el.removeChild(el); - } - - newEl = _V_.createElement("video", { - id: el.id || player.el.id + "_html5_api", - className: el.className || "vjs-tech" - }); - - el = newEl; - _V_.insertFirst(el, player.el); - } - - // Update tag settings, in case they were overridden - _V_.each(["autoplay","preload","loop","muted"], function(attr){ // ,"poster" - el[attr] = player.options[attr]; - }, this); - - return el; - }, - - // Make video events trigger player events - // May seem verbose here, but makes other APIs possible. - setupTriggers: function(){ - _V_.each.call(this, _V_.html5.events, function(type){ - _V_.addEvent(this.el, type, _V_.proxy(this.player, this.eventHandler)); - }); - }, - removeTriggers: function(){ - _V_.each.call(this, _V_.html5.events, function(type){ - _V_.removeEvent(this.el, type, _V_.proxy(this.player, this.eventHandler)); - }); - }, - eventHandler: function(e){ - e.stopPropagation(); - this.triggerEvent(e); - }, - - play: function(){ this.el.play(); }, - pause: function(){ this.el.pause(); }, - paused: function(){ return this.el.paused; }, - - currentTime: function(){ return this.el.currentTime; }, - setCurrentTime: function(seconds){ - try { - this.el.currentTime = seconds; - } catch(e) { - _V_.log(e, "Video isn't ready. (VideoJS)"); - // this.warning(VideoJS.warnings.videoNotReady); - } - }, - - duration: function(){ return this.el.duration || 0; }, - buffered: function(){ return this.el.buffered; }, - - volume: function(){ return this.el.volume; }, - setVolume: function(percentAsDecimal){ this.el.volume = percentAsDecimal; }, - muted: function(){ return this.el.muted; }, - setMuted: function(muted){ this.el.muted = muted }, - - width: function(){ return this.el.offsetWidth; }, - height: function(){ return this.el.offsetHeight; }, - - supportsFullScreen: function(){ - if (typeof this.el.webkitEnterFullScreen == 'function') { - - // Seems to be broken in Chromium/Chrome && Safari in Leopard - if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) { - return true; - } - } - return false; - }, - - enterFullScreen: function(){ - try { - this.el.webkitEnterFullScreen(); - } catch (e) { - if (e.code == 11) { - // this.warning(VideoJS.warnings.videoNotReady); - _V_.log("VideoJS: Video not ready.") - } - } - }, - src: function(src){ this.el.src = src; }, - load: function(){ this.el.load(); }, - currentSrc: function(){ return this.el.currentSrc; }, - - preload: function(){ return this.el.preload; }, - setPreload: function(val){ this.el.preload = val; }, - autoplay: function(){ return this.el.autoplay; }, - setAutoplay: function(val){ this.el.autoplay = val; }, - loop: function(){ return this.el.loop; }, - setLoop: function(val){ this.el.loop = val; }, - - error: function(){ return this.el.error; }, - // networkState: function(){ return this.el.networkState; }, - // readyState: function(){ return this.el.readyState; }, - seeking: function(){ return this.el.seeking; }, - // initialTime: function(){ return this.el.initialTime; }, - // startOffsetTime: function(){ return this.el.startOffsetTime; }, - // played: function(){ return this.el.played; }, - // seekable: function(){ return this.el.seekable; }, - ended: function(){ return this.el.ended; }, - // videoTracks: function(){ return this.el.videoTracks; }, - // audioTracks: function(){ return this.el.audioTracks; }, - // videoWidth: function(){ return this.el.videoWidth; }, - // videoHeight: function(){ return this.el.videoHeight; }, - // textTracks: function(){ return this.el.textTracks; }, - // defaultPlaybackRate: function(){ return this.el.defaultPlaybackRate; }, - // playbackRate: function(){ return this.el.playbackRate; }, - // mediaGroup: function(){ return this.el.mediaGroup; }, - // controller: function(){ return this.el.controller; }, - controls: function(){ return this.player.options.controls; }, - defaultMuted: function(){ return this.el.defaultMuted; } -}); - -/* HTML5 Support Testing -------------------------------------------------------- */ - -_V_.html5.isSupported = function(){ - return !!document.createElement("video").canPlayType; -}; - -_V_.html5.canPlaySource = function(srcObj){ - return !!document.createElement("video").canPlayType(srcObj.type); - // TODO: Check Type - // If no Type, check ext - // Check Media Type -}; - -// List of all HTML5 events (various uses). -_V_.html5.events = "loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange".split(","); - -/* HTML5 Device Fixes ---------------------------------------------------------- */ - -_V_.html5.prototype.support = { - - // Support for tech specific full screen. (webkitEnterFullScreen, not requestFullscreen) - // http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLVideoElementClassReference/HTMLVideoElement/HTMLVideoElement.html - // Seems to be broken in Chromium/Chrome && Safari in Leopard - fullscreen: (typeof _V_.testVid.webkitEnterFullScreen !== undefined) ? (!_V_.ua.match("Chrome") && !_V_.ua.match("Mac OS X 10.5") ? true : false) : false, - - // In iOS, if you move a video element in the DOM, it breaks video playback. - movingElementInDOM: !_V_.isIOS() - -}; - -// Android -if (_V_.isAndroid()) { - - // Override Android 2.2 and less canPlayType method which is broken - if (_V_.androidVersion() < 3) { - document.createElement("video").constructor.prototype.canPlayType = function(type){ - return (type && type.toLowerCase().indexOf("video/mp4") != -1) ? "maybe" : ""; - }; - } -} - - -/* VideoJS-SWF - Custom Flash Player with HTML5-ish API - https://github.com/zencoder/video-js-swf -================================================================================ */ -_V_.flash = _V_.PlaybackTech.extend({ - - init: function(player, options){ - this.player = player; - - var source = options.source, - - // Which element to embed in - parentEl = options.parentEl, - - // Create a temporary element to be replaced by swf object - placeHolder = this.el = _V_.createElement("div", { id: parentEl.id + "_temp_flash" }), - - // Generate ID for swf object - objId = player.el.id+"_flash_api", - - // Store player options in local var for optimization - playerOptions = player.options, - - // Merge default flashvars with ones passed in to init - flashVars = _V_.merge({ - - // SWF Callback Functions - readyFunction: "_V_.flash.onReady", - eventProxyFunction: "_V_.flash.onEvent", - errorEventProxyFunction: "_V_.flash.onError", - - // Player Settings - autoplay: playerOptions.autoplay, - preload: playerOptions.preload, - loop: playerOptions.loop, - muted: playerOptions.muted - - }, options.flashVars), - - // Merge default parames with ones passed in - params = _V_.merge({ - wmode: "opaque", // Opaque is needed to overlay controls, but can affect playback performance - bgcolor: "#000000" // Using bgcolor prevents a white flash when the object is loading - }, options.params), - - // Merge default attributes with ones passed in - attributes = _V_.merge({ - id: objId, - name: objId, // Both ID and Name needed or swf to identifty itself - 'class': 'vjs-tech' - }, options.attributes) - ; - - // If source was supplied pass as a flash var. - if (source) { - flashVars.src = encodeURIComponent(source.src); - } - - // Add placeholder to player div - _V_.insertFirst(placeHolder, parentEl); - - // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers - // This allows resetting the playhead when we catch the reload - if (options.startTime) { - this.ready(function(){ - this.load(); - this.play(); - this.currentTime(options.startTime); - }); - } - - // Flash iFrame Mode - // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload. - // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13) - // - Webkit when hiding the plugin - // - Webkit and Firefox when using requestFullScreen on a parent element - // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues. - // Issues that remain include hiding the element and requestFullScreen in Firefox specifically - - // There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame. - // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file. - // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain. - // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe - // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object. - // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is. - - // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame - // Firefox 9 throws a security error, unleess you call location.href right before doc.write. - // Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page. - // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop. - - if (options.iFrameMode == true && !_V_.isFF) { - - // Create iFrame with vjs-tech class so it's 100% width/height - var iFrm = _V_.createElement("iframe", { - id: objId + "_iframe", - name: objId + "_iframe", - className: "vjs-tech", - scrolling: "no", - marginWidth: 0, - marginHeight: 0, - frameBorder: 0 - }); - - // Update ready function names in flash vars for iframe window - flashVars.readyFunction = "ready"; - flashVars.eventProxyFunction = "events"; - flashVars.errorEventProxyFunction = "errors"; - - // Tried multiple methods to get this to work in all browsers - - // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object. - // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error - // var newObj = _V_.flash.embed(options.swf, placeHolder, flashVars, params, attributes); - // (in onload) - // var temp = _V_.createElement("a", { id:"asdf", innerHTML: "asdf" } ); - // iDoc.body.appendChild(temp); - - // Tried embedding the flash object through javascript in the iframe source. - // This works in webkit but still triggers the firefox security error - // iFrm.src = "javascript: document.write('"+_V_.flash.getEmbedCode(options.swf, flashVars, params, attributes)+"');"; - - // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe - // We should add an option to host the iframe locally though, because it could help a lot of issues. - // iFrm.src = "iframe.html"; - - // Wait until iFrame has loaded to write into it. - _V_.addEvent(iFrm, "load", _V_.proxy(this, function(){ - - var iDoc, objTag, swfLoc, - iWin = iFrm.contentWindow, - varString = ""; - - - // The one working method I found was to use the iframe's document.write() to create the swf object - // This got around the security issue in all browsers except firefox. - // I did find a hack where if I call the iframe's window.location.href="", it would get around the security error - // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop) - // Plus Firefox 3.6 didn't work no matter what I tried. - // if (_V_.ua.match("Firefox")) { - // iWin.location.href = ""; - // } - - // Get the iFrame's document depending on what the browser supports - iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document; - - // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue. - // Even tried adding /. that was mentioned in a browser security writeup - // document.domain = document.domain+"/."; - // iDoc.domain = document.domain+"/."; - - // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers. - // iDoc.body.innerHTML = swfObjectHTML; - - // Tried appending the object to the iframe doc's body. Security error in all browsers. - // iDoc.body.appendChild(swfObject); - - // Using document.write actually got around the security error that browsers were throwing. - // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf. - // Not sure why that's a security issue, but apparently it is. - iDoc.write(_V_.flash.getEmbedCode(options.swf, flashVars, params, attributes)); - - // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers - // So far no issues with swf ready event being called before it's set on the window. - iWin.player = this.player; - - // Create swf ready function for iFrame window - iWin.ready = _V_.proxy(this.player, function(currSwf){ - var el = iDoc.getElementById(currSwf), - player = this, - tech = player.tech; - - // Update reference to playback technology element - tech.el = el; - - // Now that the element is ready, make a click on the swf play the video - _V_.addEvent(el, "click", tech.proxy(tech.onClick)); - - // Make sure swf is actually ready. Sometimes the API isn't actually yet. - _V_.flash.checkReady(tech); - }); - - // Create event listener for all swf events - iWin.events = _V_.proxy(this.player, function(swfID, eventName, other){ - var player = this; - if (player && player.techName == "flash") { - player.triggerEvent(eventName); - } - }); - - // Create error listener for all swf errors - iWin.errors = _V_.proxy(this.player, function(swfID, eventName){ - _V_.log("Flash Error", eventName); - }); - - })); - - // Replace placeholder with iFrame (it will load now) - placeHolder.parentNode.replaceChild(iFrm, placeHolder); - - // If not using iFrame mode, embed as normal object - } else { - _V_.flash.embed(options.swf, placeHolder, flashVars, params, attributes); - } - }, - - destroy: function(){ - this.el.parentNode.removeChild(this.el); - }, - - // setupTriggers: function(){}, // Using global onEvent func to distribute events - - play: function(){ this.el.vjs_play(); }, - pause: function(){ this.el.vjs_pause(); }, - src: function(src){ - this.el.vjs_src(src); - - // Currently the SWF doesn't autoplay if you load a source later. - // e.g. Load player w/ no source, wait 2s, set src. - if (this.player.autoplay) { - var tech = this; - setTimeout(function(){ tech.play(); }, 0); - } - }, - load: function(){ this.el.vjs_load(); }, - poster: function(){ this.el.vjs_getProperty("poster"); }, - - buffered: function(){ - return _V_.createTimeRange(0, this.el.vjs_getProperty("buffered")); - }, - - supportsFullScreen: function(){ - return false; // Flash does not allow fullscreen through javascript - }, - enterFullScreen: function(){ - return false; - } -}); - -// Create setters and getters for attributes -(function(){ - - var api = _V_.flash.prototype, - readWrite = "preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted".split(","), - readOnly = "error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks".split(","), - callOnly = "load,play,pause".split(","); - // Overridden: buffered - - createSetter = function(attr){ - var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1); - api["set"+attrUpper] = function(val){ return this.el.vjs_setProperty(attr, val); }; - }, - - createGetter = function(attr){ - api[attr] = function(){ return this.el.vjs_getProperty(attr); }; - } - ; - - // Create getter and setters for all read/write attributes - _V_.each(readWrite, function(attr){ - createGetter(attr); - createSetter(attr); - }); - - // Create getters for read-only attributes - _V_.each(readOnly, function(attr){ - createGetter(attr); - }); - -})(); - -/* Flash Support Testing -------------------------------------------------------- */ - -_V_.flash.isSupported = function(){ - return _V_.flash.version()[0] >= 10; - // return swfobject.hasFlashPlayerVersion("10"); -}; - -_V_.flash.canPlaySource = function(srcObj){ - if (srcObj.type in _V_.flash.prototype.support.formats) { return "maybe"; } -}; - -_V_.flash.prototype.support = { - formats: { - "video/flv": "FLV", - "video/x-flv": "FLV", - "video/mp4": "MP4", - "video/m4v": "MP4" - }, - - // Optional events that we can manually mimic with timers - progressEvent: false, - timeupdateEvent: false, - - // Resizing plugins using request fullscreen reloads the plugin - fullscreenResize: false, - - // Resizing plugins in Firefox always reloads the plugin (e.g. full window mode) - parentResize: !(_V_.ua.match("Firefox")) -}; - -_V_.flash.onReady = function(currSwf){ - - var el = _V_.el(currSwf); - - // Get player from box - // On firefox reloads, el might already have a player - var player = el.player || el.parentNode.player, - tech = player.tech; - - // Reference player on tech element - el.player = player; - - // Update reference to playback technology element - tech.el = el; - - // Now that the element is ready, make a click on the swf play the video - tech.addEvent("click", tech.onClick); - - _V_.flash.checkReady(tech); -}; - -// The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object. -// If it's not ready, we set a timeout to check again shortly. -_V_.flash.checkReady = function(tech){ - - // Check if API property exists - if (tech.el.vjs_getProperty) { - - // If so, tell tech it's ready - tech.triggerReady(); - - // Otherwise wait longer. - } else { - - setTimeout(function(){ - _V_.flash.checkReady(tech); - }, 50); - - } -}; - -// Trigger events from the swf on the player -_V_.flash.onEvent = function(swfID, eventName){ - var player = _V_.el(swfID).player; - player.triggerEvent(eventName); -}; - -// Log errors from the swf -_V_.flash.onError = function(swfID, err){ - _V_.log("Flash Error", err, swfID); -}; - -// Flash Version Check -_V_.flash.version = function(){ - var version = '0,0,0' - - // IE - try { - version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1]; - - // other browsers - } catch(e) { - try { - if (navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){ - version = (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1]; - } - } catch(e) {} - } - return version.split(","); -} - -// Flash embedding method. Only used in non-iframe mode -_V_.flash.embed = function(swf, placeHolder, flashVars, params, attributes){ - var code = _V_.flash.getEmbedCode(swf, flashVars, params, attributes), - - // Get element by embedding code and retrieving created element - obj = _V_.createElement("div", { innerHTML: code }).childNodes[0], - - par = placeHolder.parentNode - ; - - placeHolder.parentNode.replaceChild(obj, placeHolder); - - // IE6 seems to have an issue where it won't initialize the swf object after injecting it. - // This is a dumb temporary fix - if (_V_.isIE()) { - var newObj = par.childNodes[0]; - setTimeout(function(){ - newObj.style.display = "block"; - }, 1000); - } - - return obj; - -}; - -_V_.flash.getEmbedCode = function(swf, flashVars, params, attributes){ - - var objTag = '<object type="application/x-shockwave-flash"', - flashVarsString = '', - paramsString = '' - attrsString = ''; - - // Convert flash vars to string - if (flashVars) { - _V_.eachProp(flashVars, function(key, val){ - flashVarsString += (key + "=" + val + "&"); - }); - } - - // Add swf, flashVars, and other default params - params = _V_.merge({ - movie: swf, - flashvars: flashVarsString, - allowScriptAccess: "always", // Required to talk to swf - allowNetworking: "all" // All should be default, but having security issues. - }, params); - - // Create param tags string - _V_.eachProp(params, function(key, val){ - paramsString += '<param name="'+key+'" value="'+val+'" />'; - }); - - attributes = _V_.merge({ - // Add swf to attributes (need both for IE and Others to work) - data: swf, - - // Default to 100% width/height - width: "100%", - height: "100%" - - }, attributes); - - // Create Attributes string - _V_.eachProp(attributes, function(key, val){ - attrsString += (key + '="' + val + '" '); - }); - - return objTag + attrsString + '>' + paramsString + '</object>'; -} -_V_.Track = function(attributes, player){ - // Store reference to the parent player - this.player = player; - - this.src = attributes.src; - this.kind = attributes.kind; - this.srclang = attributes.srclang; - this.label = attributes.label; - this["default"] = attributes["default"]; // 'default' is reserved-ish - this.title = attributes.title; - - this.cues = []; - this.currentCue = false; - this.lastCueIndex = 0; - - // Update current cue on timeupdate - player.addEvent("timeupdate", _V_.proxy(this, this.update)); - - // Reset cue time on media end - player.addEvent("ended", _V_.proxy(this, function() { this.lastCueIndex = 0; })); - - // Load Track File - _V_.get(attributes.src, _V_.proxy(this, this.parseCues)); -}; - -_V_.Track.prototype = { - - parseCues: function(srcContent) { - var cue, time, text, - lines = srcContent.split("\n"), - line = ""; - - for (var i=0; i<lines.length; i++) { - line = _V_.trim(lines[i]); // Trim whitespace and linebreaks - if (line) { // Loop until a line with content - - // First line - Number - cue = { - id: line, // Cue Number - index: this.cues.length // Position in Array - }; - - // Second line - Time - line = _V_.trim(lines[++i]); - time = line.split(" --> "); - cue.startTime = this.parseCueTime(time[0]); - cue.endTime = this.parseCueTime(time[1]); - - // Additional lines - Cue Text - text = []; - for (var j=i; j<lines.length; j++) { // Loop until a blank line or end of lines - line = _V_.trim(lines[++i]); - if (!line) { break; } - text.push(line); - } - cue.text = text.join('<br/>'); - - // Add this cue - this.cues.push(cue); - } - } - }, - - parseCueTime: function(timeText) { - var parts = timeText.split(':'), - time = 0; - // hours => seconds - time += parseFloat(parts[0])*60*60; - // minutes => seconds - time += parseFloat(parts[1])*60; - // get seconds - var seconds = parts[2].split(/\.|,/); // Either . or , - time += parseFloat(seconds[0]); - // add miliseconds - ms = parseFloat(seconds[1]); - if (ms) { time += ms/1000; } - return time; - }, - - update: function(){ - // Assuming all cues are in order by time, and do not overlap - if (this.cues && this.cues.length > 0) { - var time = this.player.currentTime(); - // If current cue should stay showing, don't do anything. Otherwise, find new cue. - if (!this.currentCue || this.currentCue.startTime >= time || this.currentCue.endTime < time) { - var newSubIndex = false, - // Loop in reverse if lastCue is after current time (optimization) - // Meaning the user is scrubbing in reverse or rewinding - reverse = (this.cues[this.lastCueIndex].startTime > time), - // If reverse, step back 1 becase we know it's not the lastCue - i = this.lastCueIndex - (reverse ? 1 : 0); - while (true) { // Loop until broken - if (reverse) { // Looping in reverse - // Stop if no more, or this cue ends before the current time (no earlier cues should apply) - if (i < 0 || this.cues[i].endTime < time) { break; } - // End is greater than time, so if start is less, show this cue - if (this.cues[i].startTime < time) { - newSubIndex = i; - break; - } - i--; - } else { // Looping forward - // Stop if no more, or this cue starts after time (no later cues should apply) - if (i >= this.cues.length || this.cues[i].startTime > time) { break; } - // Start is less than time, so if end is later, show this cue - if (this.cues[i].endTime > time) { - newSubIndex = i; - break; - } - i++; - } - } - - // Set or clear current cue - if (newSubIndex !== false) { - this.currentCue = this.cues[newSubIndex]; - this.lastCueIndex = newSubIndex; - this.updatePlayer(this.currentCue.text); - } else if (this.currentCue) { - this.currentCue = false; - this.updatePlayer(""); - } - } - } - }, - - // Update the stored value for the current track kind - // and trigger an event to update all text track displays. - updatePlayer: function(text){ - this.player.textTrackValue(this.kind, text); - } -}; -_V_.addEvent(window, "load", function(){ - _V_.windowLoaded = true; -}); - -// Run Auto-load players -_V_.autoSetup(); -// Expose to global -window.VideoJS = window._V_ = VideoJS; - -// End self-executing function -})(window); diff --git a/extlib/video-js/video.min.js b/extlib/video-js/video.min.js index 026a0126..1c33af55 100644 --- a/extlib/video-js/video.min.js +++ b/extlib/video-js/video.min.js @@ -1,6 +1,6 @@ /*! Video.js - HTML5 Video Player -Version 3.1.0 +Version GENERATED_AT_BUILD LGPL v3 LICENSE INFO This file is part of Video.js. Copyright 2011 Zencoder, Inc. @@ -18,4 +18,4 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Video.js. If not, see <http://www.gnu.org/licenses/>. */ -(function(window,undefined){var document=window.document;document.createElement("video");document.createElement("audio");var VideoJS=function(id,addOptions,ready){var tag;if(typeof id=="string"){if(id.indexOf("#")===0){id=id.slice(1)}if(_V_.players[id]){return _V_.players[id]}else{tag=_V_.el(id)}}else{tag=id}if(!tag||!tag.nodeName){throw new TypeError("The element or ID supplied is not valid. (VideoJS)")}return tag.player||new _V_.Player(tag,addOptions,ready)},_V_=VideoJS,CDN_VERSION="3.1";VideoJS.players={};VideoJS.options={techOrder:["html5","flash"],html5:{},flash:{swf:"http://vjs.zencdn.net/c/video-js.swf"},width:"auto",height:"auto",defaultVolume:0,components:{poster:{},loadingSpinner:{},bigPlayButton:{},controlBar:{},subtitlesDisplay:{}}};if(CDN_VERSION!="GENERATED_CDN_VSN"){_V_.options.flash.swf="http://vjs.zencdn.net/"+CDN_VERSION+"/video-js.swf"}_V_.autoSetup=function(){var options,vid,player,vids=document.getElementsByTagName("video");if(vids&&vids.length>0){for(var i=0,j=vids.length;i<j;i++){vid=vids[i];if(vid&&vid.getAttribute){if(vid.player===undefined){options=vid.getAttribute("data-setup");if(options!==null){options=JSON.parse(options||"{}");player=_V_(vid,options)}}}else{_V_.autoSetupTimeout(1);break}}}else{if(!_V_.windowLoaded){_V_.autoSetupTimeout(1)}}};_V_.autoSetupTimeout=function(wait){setTimeout(_V_.autoSetup,wait)};_V_.merge=function(obj1,obj2,safe){if(!obj2){obj2={}}for(var attrname in obj2){if(obj2.hasOwnProperty(attrname)&&(!safe||!obj1.hasOwnProperty(attrname))){obj1[attrname]=obj2[attrname]}}return obj1};_V_.extend=function(obj){this.merge(this,obj,true)};_V_.extend({tech:{},controlSets:{},isIE:function(){return !+"\v1"},isFF:function(){return !!_V_.ua.match("Firefox")},isIPad:function(){return navigator.userAgent.match(/iPad/i)!==null},isIPhone:function(){return navigator.userAgent.match(/iPhone/i)!==null},isIOS:function(){return VideoJS.isIPhone()||VideoJS.isIPad()},iOSVersion:function(){var match=navigator.userAgent.match(/OS (\d+)_/i);if(match&&match[1]){return match[1]}},isAndroid:function(){return navigator.userAgent.match(/Android.*AppleWebKit/i)!==null},androidVersion:function(){var match=navigator.userAgent.match(/Android (\d+)\./i);if(match&&match[1]){return match[1]}},testVid:document.createElement("video"),ua:navigator.userAgent,support:{},each:function(arr,fn){if(!arr||arr.length===0){return}for(var i=0,j=arr.length;i<j;i++){fn.call(this,arr[i],i)}},eachProp:function(obj,fn){if(!obj){return}for(var name in obj){if(obj.hasOwnProperty(name)){fn.call(this,name,obj[name])}}},el:function(id){return document.getElementById(id)},createElement:function(tagName,attributes){var el=document.createElement(tagName),attrname;for(attrname in attributes){if(attributes.hasOwnProperty(attrname)){if(attrname.indexOf("-")!==-1){el.setAttribute(attrname,attributes[attrname])}else{el[attrname]=attributes[attrname]}}}return el},insertFirst:function(node,parent){if(parent.firstChild){parent.insertBefore(node,parent.firstChild)}else{parent.appendChild(node)}},addClass:function(element,classToAdd){if((" "+element.className+" ").indexOf(" "+classToAdd+" ")==-1){element.className=element.className===""?classToAdd:element.className+" "+classToAdd}},removeClass:function(element,classToRemove){if(element.className.indexOf(classToRemove)==-1){return}var classNames=element.className.split(" ");classNames.splice(classNames.indexOf(classToRemove),1);element.className=classNames.join(" ")},remove:function(item,array){if(!array){return}var i=array.indexOf(item);if(i!=-1){return array.splice(i,1)}},blockTextSelection:function(){document.body.focus();document.onselectstart=function(){return false}},unblockTextSelection:function(){document.onselectstart=function(){return true}},formatTime:function(seconds,guide){guide=guide||seconds;var s=Math.floor(seconds%60),m=Math.floor(seconds/60%60),h=Math.floor(seconds/3600),gm=Math.floor(guide/60%60),gh=Math.floor(guide/3600);h=(h>0||gh>0)?h+":":"";m=(((h||gm>=10)&&m<10)?"0"+m:m)+":";s=(s<10)?"0"+s:s;return h+m+s},capitalize:function(string){return string.charAt(0).toUpperCase()+string.slice(1)},getRelativePosition:function(x,relativeElement){return Math.max(0,Math.min(1,(x-_V_.findPosX(relativeElement))/relativeElement.offsetWidth))},getComputedStyleValue:function(element,style){return window.getComputedStyle(element,null).getPropertyValue(style)},trim:function(string){return string.toString().replace(/^\s+/,"").replace(/\s+$/,"")},round:function(num,dec){if(!dec){dec=0}return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec)},isEmpty:function(object){for(var prop in object){return false}return true},createTimeRange:function(start,end){return{length:1,start:function(){return start},end:function(){return end}}},cache:{},guid:1,expando:"vdata"+(new Date).getTime(),getData:function(elem){var id=elem[_V_.expando];if(!id){id=elem[_V_.expando]=_V_.guid++;_V_.cache[id]={}}return _V_.cache[id]},removeData:function(elem){var id=elem[_V_.expando];if(!id){return}delete _V_.cache[id];try{delete elem[_V_.expando]}catch(e){if(elem.removeAttribute){elem.removeAttribute(_V_.expando)}else{elem[_V_.expando]=null}}},proxy:function(context,fn){if(!fn.guid){fn.guid=_V_.guid++}var ret=function(){return fn.apply(context,arguments)};ret.guid=fn.guid;return ret},get:function(url,onSuccess,onError){var local=(url.indexOf("file:")==0||(window.location.href.indexOf("file:")==0&&url.indexOf("http:")==-1));if(typeof XMLHttpRequest=="undefined"){XMLHttpRequest=function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(f){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(g){}throw new Error("This browser does not support XMLHttpRequest.")}}var request=new XMLHttpRequest();try{request.open("GET",url)}catch(e){_V_.log("VideoJS XMLHttpRequest (open)",e);return false}request.onreadystatechange=_V_.proxy(this,function(){if(request.readyState==4){if(request.status==200||local&&request.status==0){onSuccess(request.responseText)}else{if(onError){onError()}}}});try{request.send()}catch(e){_V_.log("VideoJS XMLHttpRequest (send)",e);if(onError){onError(e)}}},setLocalStorage:function(key,value){var localStorage=localStorage||false;if(!localStorage){return}try{localStorage[key]=value}catch(e){if(e.code==22||e.code==1014){_V_.log("LocalStorage Full (VideoJS)",e)}else{_V_.log("LocalStorage Error (VideoJS)",e)}}}});_V_.log=function(){_V_.log.history=_V_.log.history||[];_V_.log.history.push(arguments);if(window.console){arguments.callee=arguments.callee.caller;var newarr=[].slice.call(arguments);(typeof console.log==="object"?_V_.log.apply.call(console.log,console,newarr):console.log.apply(console,newarr))}};(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try{console.log();return window.console}catch(err){return window.console={}}})());if("getBoundingClientRect" in document.documentElement){_V_.findPosX=function(el){var box;try{box=el.getBoundingClientRect()}catch(e){}if(!box){return 0}var docEl=document.documentElement,body=document.body,clientLeft=docEl.clientLeft||body.clientLeft||0,scrollLeft=window.pageXOffset||body.scrollLeft,left=box.left+scrollLeft-clientLeft;return left}}else{_V_.findPosX=function(el){var curleft=el.offsetLeft;while(el=obj.offsetParent){if(el.className.indexOf("video-js")==-1){}else{}curleft+=el.offsetLeft}return curleft}}(function(){var initializing=false,fnTest=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;_V_.Class=function(){};_V_.Class.extend=function(prop){var _super=this.prototype;initializing=true;var prototype=new this();initializing=false;for(var name in prop){prototype[name]=typeof prop[name]=="function"&&typeof _super[name]=="function"&&fnTest.test(prop[name])?(function(name,fn){return function(){var tmp=this._super;this._super=_super[name];var ret=fn.apply(this,arguments);this._super=tmp;return ret}})(name,prop[name]):prop[name]}function Class(){if(!initializing&&this.init){return this.init.apply(this,arguments)}else{if(!initializing){return arguments.callee.prototype.init()}}}Class.prototype=prototype;Class.constructor=Class;Class.extend=arguments.callee;return Class}})();_V_.Component=_V_.Class.extend({init:function(player,options){this.player=player;options=this.options=_V_.merge(this.options||{},options);if(options.el){this.el=options.el}else{this.el=this.createElement()}this.initComponents()},destroy:function(){},createElement:function(type,attrs){return _V_.createElement(type||"div",attrs)},buildCSSClass:function(){return""},initComponents:function(){var options=this.options;if(options&&options.components){this.eachProp(options.components,function(name,opts){var tempAdd=this.proxy(function(){this.addComponent(name,opts)});if(opts.loadEvent){this.one(opts.loadEvent,tempAdd)}else{tempAdd()}})}},addComponent:function(name,options){var componentClass,component;options=options||{};componentClass=options.componentClass||_V_.capitalize(name);component=new _V_[componentClass](this.player||this,options);this.el.appendChild(component.el);this[name]=component},show:function(){this.el.style.display="block"},hide:function(){this.el.style.display="none"},fadeIn:function(){this.removeClass("vjs-fade-out");this.addClass("vjs-fade-in")},fadeOut:function(){this.removeClass("vjs-fade-in");this.addClass("vjs-fade-out")},addClass:function(classToAdd){_V_.addClass(this.el,classToAdd)},removeClass:function(classToRemove){_V_.removeClass(this.el,classToRemove)},addEvent:function(type,fn){return _V_.addEvent(this.el,type,_V_.proxy(this,fn))},removeEvent:function(type,fn){return _V_.removeEvent(this.el,type,fn)},triggerEvent:function(type,e){return _V_.triggerEvent(this.el,type,e)},one:function(type,fn){_V_.one.call(this,this.el,type,fn)},ready:function(fn){if(!fn){return this}if(this.isReady){fn.call(this)}else{if(this.readyQueue===undefined){this.readyQueue=[]}this.readyQueue.push(fn)}return this},triggerReady:function(){this.isReady=true;if(this.readyQueue&&this.readyQueue.length>0){this.each(this.readyQueue,function(fn){fn.call(this)});this.readyQueue=[]}},each:function(arr,fn){_V_.each.call(this,arr,fn)},eachProp:function(obj,fn){_V_.eachProp.call(this,obj,fn)},extend:function(obj){_V_.merge(this,obj)},proxy:function(fn){return _V_.proxy(this,fn)}});_V_.Control=_V_.Component.extend({buildCSSClass:function(){return"vjs-control "+this._super()}});_V_.Button=_V_.Control.extend({init:function(player,options){this._super(player,options);this.addEvent("click",this.onClick);this.addEvent("focus",this.onFocus);this.addEvent("blur",this.onBlur)},createElement:function(type,attrs){attrs=_V_.merge({className:this.buildCSSClass(),innerHTML:'<div><span class="vjs-control-text">'+(this.buttonText||"Need Text")+"</span></div>",role:"button",tabIndex:0},attrs);return this._super(type,attrs)},onClick:function(){},onFocus:function(){_V_.addEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))},onKeyPress:function(event){if(event.which==32||event.which==13){event.preventDefault();this.onClick()}},onBlur:function(){_V_.removeEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))}});_V_.PlayButton=_V_.Button.extend({buttonText:"Play",buildCSSClass:function(){return"vjs-play-button "+this._super()},onClick:function(){this.player.play()}});_V_.PauseButton=_V_.Button.extend({buttonText:"Pause",buildCSSClass:function(){return"vjs-pause-button "+this._super()},onClick:function(){this.player.pause()}});_V_.PlayToggle=_V_.Button.extend({buttonText:"Play",init:function(player,options){this._super(player,options);player.addEvent("play",_V_.proxy(this,this.onPlay));player.addEvent("pause",_V_.proxy(this,this.onPause))},buildCSSClass:function(){return"vjs-play-control "+this._super()},onClick:function(){if(this.player.paused()){this.player.play()}else{this.player.pause()}},onPlay:function(){_V_.removeClass(this.el,"vjs-paused");_V_.addClass(this.el,"vjs-playing")},onPause:function(){_V_.removeClass(this.el,"vjs-playing");_V_.addClass(this.el,"vjs-paused")}});_V_.FullscreenToggle=_V_.Button.extend({buttonText:"Fullscreen",buildCSSClass:function(){return"vjs-fullscreen-control "+this._super()},onClick:function(){if(!this.player.isFullScreen){this.player.requestFullScreen()}else{this.player.cancelFullScreen()}}});_V_.BigPlayButton=_V_.Button.extend({init:function(player,options){this._super(player,options);player.addEvent("play",_V_.proxy(this,this.hide));player.addEvent("ended",_V_.proxy(this,this.show))},createElement:function(){return this._super("div",{className:"vjs-big-play-button",innerHTML:"<span></span>"})},onClick:function(){if(this.player.currentTime()){this.player.currentTime(0)}this.player.play()}});_V_.LoadingSpinner=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("canplay",_V_.proxy(this,this.hide));player.addEvent("canplaythrough",_V_.proxy(this,this.hide));player.addEvent("playing",_V_.proxy(this,this.hide));player.addEvent("seeking",_V_.proxy(this,this.show));player.addEvent("error",_V_.proxy(this,this.show));player.addEvent("waiting",_V_.proxy(this,this.show))},createElement:function(){var classNameSpinner,innerHtmlSpinner;if(typeof this.player.el.style.WebkitBorderRadius=="string"||typeof this.player.el.style.MozBorderRadius=="string"||typeof this.player.el.style.KhtmlBorderRadius=="string"||typeof this.player.el.style.borderRadius=="string"){classNameSpinner="vjs-loading-spinner";innerHtmlSpinner="<div class='ball1'></div><div class='ball2'></div><div class='ball3'></div><div class='ball4'></div><div class='ball5'></div><div class='ball6'></div><div class='ball7'></div><div class='ball8'></div>"}else{classNameSpinner="vjs-loading-spinner-fallback";innerHtmlSpinner=""}return this._super("div",{className:classNameSpinner,innerHTML:innerHtmlSpinner})}});_V_.ControlBar=_V_.Component.extend({options:{loadEvent:"play",components:{playToggle:{},fullscreenToggle:{},currentTimeDisplay:{},timeDivider:{},durationDisplay:{},remainingTimeDisplay:{},progressControl:{},volumeControl:{},muteToggle:{}}},init:function(player,options){this._super(player,options);player.addEvent("play",this.proxy(function(){this.fadeIn();this.player.addEvent("mouseover",this.proxy(this.fadeIn));this.player.addEvent("mouseout",this.proxy(this.fadeOut))}))},createElement:function(){return _V_.createElement("div",{className:"vjs-controls"})},fadeIn:function(){this._super();this.player.triggerEvent("controlsvisible")},fadeOut:function(){this._super();this.player.triggerEvent("controlshidden")}});_V_.CurrentTimeDisplay=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("timeupdate",_V_.proxy(this,this.updateContent))},createElement:function(){var el=this._super("div",{className:"vjs-current-time vjs-time-controls vjs-control"});this.content=_V_.createElement("div",{className:"vjs-current-time-display",innerHTML:"0:00"});el.appendChild(_V_.createElement("div").appendChild(this.content));return el},updateContent:function(){var time=(this.player.scrubbing)?this.player.values.currentTime:this.player.currentTime();this.content.innerHTML=_V_.formatTime(time,this.player.duration())}});_V_.DurationDisplay=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("timeupdate",_V_.proxy(this,this.updateContent))},createElement:function(){var el=this._super("div",{className:"vjs-duration vjs-time-controls vjs-control"});this.content=_V_.createElement("div",{className:"vjs-duration-display",innerHTML:"0:00"});el.appendChild(_V_.createElement("div").appendChild(this.content));return el},updateContent:function(){if(this.player.duration()){this.content.innerHTML=_V_.formatTime(this.player.duration())}}});_V_.TimeDivider=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-time-divider",innerHTML:"<div><span>/</span></div>"})}});_V_.RemainingTimeDisplay=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("timeupdate",_V_.proxy(this,this.updateContent))},createElement:function(){var el=this._super("div",{className:"vjs-remaining-time vjs-time-controls vjs-control"});this.content=_V_.createElement("div",{className:"vjs-remaining-time-display",innerHTML:"-0:00"});el.appendChild(_V_.createElement("div").appendChild(this.content));return el},updateContent:function(){if(this.player.duration()){this.content.innerHTML="-"+_V_.formatTime(this.player.remainingTime())}}});_V_.Slider=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent(this.playerEvent,_V_.proxy(this,this.update));this.addEvent("mousedown",this.onMouseDown);this.addEvent("focus",this.onFocus);this.addEvent("blur",this.onBlur);this.player.addEvent("controlsvisible",this.proxy(this.update));this.update()},createElement:function(type,attrs){attrs=_V_.merge({role:"slider","aria-valuenow":0,"aria-valuemin":0,"aria-valuemax":100,tabIndex:0},attrs);return this._super(type,attrs)},onMouseDown:function(event){event.preventDefault();_V_.blockTextSelection();_V_.addEvent(document,"mousemove",_V_.proxy(this,this.onMouseMove));_V_.addEvent(document,"mouseup",_V_.proxy(this,this.onMouseUp));this.onMouseMove(event)},onMouseUp:function(event){_V_.unblockTextSelection();_V_.removeEvent(document,"mousemove",this.onMouseMove,false);_V_.removeEvent(document,"mouseup",this.onMouseUp,false);this.update()},update:function(){var barProgress,progress=this.getPercent();handle=this.handle,bar=this.bar;if(isNaN(progress)){progress=0}barProgress=progress;if(handle){var box=this.el,boxWidth=box.offsetWidth,handleWidth=handle.el.offsetWidth,handlePercent=(handleWidth)?handleWidth/boxWidth:0,boxAdjustedPercent=1-handlePercent;adjustedProgress=progress*boxAdjustedPercent,barProgress=adjustedProgress+(handlePercent/2);handle.el.style.left=_V_.round(adjustedProgress*100,2)+"%"}bar.el.style.width=_V_.round(barProgress*100,2)+"%"},calculateDistance:function(event){var box=this.el,boxX=_V_.findPosX(box),boxW=box.offsetWidth,handle=this.handle;if(handle){var handleW=handle.el.offsetWidth;boxX=boxX+(handleW/2);boxW=boxW-handleW}return Math.max(0,Math.min(1,(event.pageX-boxX)/boxW))},onFocus:function(event){_V_.addEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))},onKeyPress:function(event){if(event.which==37){event.preventDefault();this.stepBack()}else{if(event.which==39){event.preventDefault();this.stepForward()}}},onBlur:function(event){_V_.removeEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))}});_V_.ProgressControl=_V_.Component.extend({options:{components:{seekBar:{}}},createElement:function(){return this._super("div",{className:"vjs-progress-control vjs-control"})}});_V_.SeekBar=_V_.Slider.extend({options:{components:{loadProgressBar:{},bar:{componentClass:"PlayProgressBar"},handle:{componentClass:"SeekHandle"}}},playerEvent:"timeupdate",init:function(player,options){this._super(player,options)},createElement:function(){return this._super("div",{className:"vjs-progress-holder"})},getPercent:function(){return this.player.currentTime()/this.player.duration()},onMouseDown:function(event){this._super(event);this.player.scrubbing=true;this.videoWasPlaying=!this.player.paused();this.player.pause()},onMouseMove:function(event){var newTime=this.calculateDistance(event)*this.player.duration();if(newTime==this.player.duration()){newTime=newTime-0.1}this.player.currentTime(newTime)},onMouseUp:function(event){this._super(event);this.player.scrubbing=false;if(this.videoWasPlaying){this.player.play()}},stepForward:function(){this.player.currentTime(this.player.currentTime()+1)},stepBack:function(){this.player.currentTime(this.player.currentTime()-1)}});_V_.LoadProgressBar=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("progress",_V_.proxy(this,this.update))},createElement:function(){return this._super("div",{className:"vjs-load-progress",innerHTML:'<span class="vjs-control-text">Loaded: 0%</span>'})},update:function(){if(this.el.style){this.el.style.width=_V_.round(this.player.bufferedPercent()*100,2)+"%"}}});_V_.PlayProgressBar=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-play-progress",innerHTML:'<span class="vjs-control-text">Progress: 0%</span>'})}});_V_.SeekHandle=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-seek-handle",innerHTML:'<span class="vjs-control-text">00:00</span>'})}});_V_.VolumeControl=_V_.Component.extend({options:{components:{volumeBar:{}}},createElement:function(){return this._super("div",{className:"vjs-volume-control vjs-control"})}});_V_.VolumeBar=_V_.Slider.extend({options:{components:{bar:{componentClass:"VolumeLevel"},handle:{componentClass:"VolumeHandle"}}},playerEvent:"volumechange",createElement:function(){return this._super("div",{className:"vjs-volume-bar"})},onMouseMove:function(event){this.player.volume(this.calculateDistance(event))},getPercent:function(){return this.player.volume()},stepForward:function(){this.player.volume(this.player.volume()+0.1)},stepBack:function(){this.player.volume(this.player.volume()-0.1)}});_V_.VolumeLevel=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-volume-level",innerHTML:'<span class="vjs-control-text"></span>'})}});_V_.VolumeHandle=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-volume-handle",innerHTML:'<span class="vjs-control-text"></span>'})}});_V_.MuteToggle=_V_.Button.extend({init:function(player,options){this._super(player,options);player.addEvent("volumechange",_V_.proxy(this,this.update))},createElement:function(){return this._super("div",{className:"vjs-mute-control vjs-control",innerHTML:'<div><span class="vjs-control-text">Mute</span></div>'})},onClick:function(event){this.player.muted(this.player.muted()?false:true)},update:function(event){var vol=this.player.volume(),level=3;if(vol==0||this.player.muted()){level=0}else{if(vol<0.33){level=1}else{if(vol<0.67){level=2}}}_V_.each.call(this,[0,1,2,3],function(i){_V_.removeClass(this.el,"vjs-vol-"+i)});_V_.addClass(this.el,"vjs-vol-"+level)}});_V_.Poster=_V_.Button.extend({init:function(player,options){this._super(player,options);if(!this.player.options.poster){this.hide()}player.addEvent("play",_V_.proxy(this,this.hide))},createElement:function(){return _V_.createElement("img",{className:"vjs-poster",src:this.player.options.poster,tabIndex:-1})},onClick:function(){this.player.play()}});_V_.TextTrackDisplay=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent(this.trackType+"update",_V_.proxy(this,this.update))},createElement:function(){return this._super("div",{className:"vjs-"+this.trackType})},update:function(){this.el.innerHTML=this.player.textTrackValue(this.trackType)}});_V_.SubtitlesDisplay=_V_.TextTrackDisplay.extend({trackType:"subtitles"});_V_.CaptionsDisplay=_V_.TextTrackDisplay.extend({trackType:"captions"});_V_.ChaptersDisplay=_V_.TextTrackDisplay.extend({trackType:"chapters"});_V_.DescriptionsDisplay=_V_.TextTrackDisplay.extend({trackType:"descriptions"});if(!Array.prototype.indexOf){Array.prototype.indexOf=function(searchElement){if(this===void 0||this===null){throw new TypeError()}var t=Object(this);var len=t.length>>>0;if(len===0){return -1}var n=0;if(arguments.length>0){n=Number(arguments[1]);if(n!==n){n=0}else{if(n!==0&&n!==(1/0)&&n!==-(1/0)){n=(n>0||-1)*Math.floor(Math.abs(n))}}}if(n>=len){return -1}var k=n>=0?n:Math.max(len-Math.abs(n),0);for(;k<len;k++){if(k in t&&t[k]===searchElement){return k}}return -1}}_V_.extend({addEvent:function(elem,type,fn){var data=_V_.getData(elem),handlers;if(data&&!data.handler){data.handler=function(event){event=_V_.fixEvent(event);var handlers=_V_.getData(elem).events[event.type];if(handlers){var handlersCopy=[];_V_.each(handlers,function(handler,i){handlersCopy[i]=handler});for(var i=0,l=handlersCopy.length;i<l;i++){handlersCopy[i].call(elem,event)}}}}if(!data.events){data.events={}}handlers=data.events[type];if(!handlers){handlers=data.events[type]=[];if(document.addEventListener){elem.addEventListener(type,data.handler,false)}else{if(document.attachEvent){elem.attachEvent("on"+type,data.handler)}}}if(!fn.guid){fn.guid=_V_.guid++}handlers.push(fn)},removeEvent:function(elem,type,fn){var data=_V_.getData(elem),handlers;if(!data.events){return}if(!type){for(type in data.events){_V_.cleanUpEvents(elem,type)}return}handlers=data.events[type];if(!handlers){return}if(fn&&fn.guid){for(var i=0;i<handlers.length;i++){if(handlers[i].guid===fn.guid){handlers.splice(i--,1)}}}_V_.cleanUpEvents(elem,type)},cleanUpEvents:function(elem,type){var data=_V_.getData(elem);if(data.events[type].length===0){delete data.events[type];if(document.removeEventListener){elem.removeEventListener(type,data.handler,false)}else{if(document.detachEvent){elem.detachEvent("on"+type,data.handler)}}}if(_V_.isEmpty(data.events)){delete data.events;delete data.handler}if(_V_.isEmpty(data)){_V_.removeData(elem)}},fixEvent:function(event){if(event[_V_.expando]){return event}var originalEvent=event;event=new _V_.Event(originalEvent);for(var i=_V_.Event.props.length,prop;i;){prop=_V_.Event.props[--i];event[prop]=originalEvent[prop]}if(!event.target){event.target=event.srcElement||document}if(event.target.nodeType===3){event.target=event.target.parentNode}if(!event.relatedTarget&&event.fromElement){event.relatedTarget=event.fromElement===event.target?event.toElement:event.fromElement}if(event.pageX==null&&event.clientX!=null){var eventDocument=event.target.ownerDocument||document,doc=eventDocument.documentElement,body=eventDocument.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc&&doc.clientLeft||body&&body.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc&&doc.clientTop||body&&body.clientTop||0)}if(event.which==null&&(event.charCode!=null||event.keyCode!=null)){event.which=event.charCode!=null?event.charCode:event.keyCode}if(!event.metaKey&&event.ctrlKey){event.metaKey=event.ctrlKey}if(!event.which&&event.button!==undefined){event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)))}return event},triggerEvent:function(elem,event){var data=_V_.getData(elem),parent=elem.parentNode||elem.ownerDocument,type=event.type||event,handler;if(data){handler=data.handler}event=typeof event==="object"?event[_V_.expando]?event:new _V_.Event(type,event):new _V_.Event(type);event.type=type;if(handler){handler.call(elem,event)}event.result=undefined;event.target=elem},one:function(elem,type,fn){_V_.addEvent(elem,type,function(){_V_.removeEvent(elem,type,arguments.callee);fn.apply(this,arguments)})}});_V_.Event=function(src,props){if(src&&src.type){this.originalEvent=src;this.type=src.type;this.isDefaultPrevented=(src.defaultPrevented||src.returnValue===false||src.getPreventDefault&&src.getPreventDefault())?returnTrue:returnFalse}else{this.type=src}if(props){_V_.merge(this,props)}this.timeStamp=(new Date).getTime();this[_V_.expando]=true};_V_.Event.prototype={preventDefault:function(){this.isDefaultPrevented=returnTrue;var e=this.originalEvent;if(!e){return}if(e.preventDefault){e.preventDefault()}else{e.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=returnTrue;var e=this.originalEvent;if(!e){return}if(e.stopPropagation){e.stopPropagation()}e.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=returnTrue;this.stopPropagation()},isDefaultPrevented:returnFalse,isPropagationStopped:returnFalse,isImmediatePropagationStopped:returnFalse};_V_.Event.props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" ");function returnTrue(){return true}function returnFalse(){return false}var JSON;if(!JSON){JSON={}}(function(){var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());_V_.Player=_V_.Component.extend({init:function(tag,addOptions,ready){this.tag=tag;var el=this.el=_V_.createElement("div"),options=this.options={},width=options.width=tag.getAttribute("width"),height=options.height=tag.getAttribute("height"),initWidth=width||300,initHeight=height||150;tag.player=el.player=this;this.ready(ready);tag.parentNode.insertBefore(el,tag);el.appendChild(tag);el.id=this.id=tag.id;el.className=tag.className;tag.id+="_html5_api";tag.className="vjs-tech";_V_.players[el.id]=this;el.setAttribute("width",initWidth);el.setAttribute("height",initHeight);el.style.width=initWidth+"px";el.style.height=initHeight+"px";tag.removeAttribute("width");tag.removeAttribute("height");_V_.merge(options,_V_.options);_V_.merge(options,this.getVideoTagSettings());_V_.merge(options,addOptions);tag.removeAttribute("controls");tag.removeAttribute("poster");if(tag.hasChildNodes()){for(var i=0,j=tag.childNodes;i<j.length;i++){if(j[i].nodeName=="SOURCE"||j[i].nodeName=="TRACK"){tag.removeChild(j[i])}}}this.techs={};this.values={};this.addClass("vjs-paused");this.addEvent("ended",this.onEnded);this.addEvent("play",this.onPlay);this.addEvent("pause",this.onPause);this.addEvent("error",this.onError);if(options.controls){this.ready(function(){this.initComponents()})}if(!options.sources||options.sources.length==0){for(var i=0,j=options.techOrder;i<j.length;i++){var techName=j[i],tech=_V_[techName];if(tech.isSupported()){this.loadTech(techName);break}}}else{this.src(options.sources)}},values:{},destroy:function(){this.stopTrackingProgress();this.stopTrackingCurrentTime();delete _V_.players[this.id]},createElement:function(type,options){},getVideoTagSettings:function(){var options={sources:[],tracks:[]};options.src=this.tag.getAttribute("src");options.controls=this.tag.getAttribute("controls")!==null;options.poster=this.tag.getAttribute("poster");options.preload=this.tag.getAttribute("preload");options.autoplay=this.tag.getAttribute("autoplay")!==null;options.loop=this.tag.getAttribute("loop")!==null;options.muted=this.tag.getAttribute("muted")!==null;if(this.tag.hasChildNodes()){for(var c,i=0,j=this.tag.childNodes;i<j.length;i++){c=j[i];if(c.nodeName=="SOURCE"){options.sources.push({src:c.getAttribute("src"),type:c.getAttribute("type"),media:c.getAttribute("media"),title:c.getAttribute("title")})}if(c.nodeName=="TRACK"){options.tracks.push(new _V_.Track({src:c.getAttribute("src"),kind:c.getAttribute("kind"),srclang:c.getAttribute("srclang"),label:c.getAttribute("label"),"default":c.getAttribute("default")!==null,title:c.getAttribute("title")},this))}}}return options},loadTech:function(techName,source){if(this.tech){this.unloadTech()}else{if(techName!="html5"&&this.tag){this.el.removeChild(this.tag);this.tag=false}}this.techName=techName;this.isReady=false;var techReady=function(){this.player.triggerReady();if(!this.support.progressEvent){this.player.manualProgressOn()}if(!this.support.timeupdateEvent){this.player.manualTimeUpdatesOn()}};var techOptions=_V_.merge({source:source,parentEl:this.el},this.options[techName]);if(source){if(source.src==this.values.src&&this.values.currentTime>0){techOptions.startTime=this.values.currentTime}this.values.src=source.src}this.tech=new _V_[techName](this,techOptions);this.tech.ready(techReady)},unloadTech:function(){this.tech.destroy();if(this.manualProgress){this.manualProgressOff()}if(this.manualTimeUpdates){this.manualTimeUpdatesOff()}this.tech=false},manualProgressOn:function(){this.manualProgress=true;this.trackProgress();this.tech.addEvent("progress",function(){this.removeEvent("progress",arguments.callee);this.support.progressEvent=true;this.player.manualProgressOff()})},manualProgressOff:function(){this.manualProgress=false;this.stopTrackingProgress()},trackProgress:function(){this.progressInterval=setInterval(_V_.proxy(this,function(){if(this.values.bufferEnd<this.buffered().end(0)){this.triggerEvent("progress")}else{if(this.bufferedPercent()==1){this.stopTrackingProgress();this.triggerEvent("progress")}}}),500)},stopTrackingProgress:function(){clearInterval(this.progressInterval)},manualTimeUpdatesOn:function(){this.manualTimeUpdates=true;this.addEvent("play",this.trackCurrentTime);this.addEvent("pause",this.stopTrackingCurrentTime);this.tech.addEvent("timeupdate",function(){this.removeEvent("timeupdate",arguments.callee);this.support.timeupdateEvent=true;this.player.manualTimeUpdatesOff()})},manualTimeUpdatesOff:function(){this.manualTimeUpdates=false;this.stopTrackingCurrentTime();this.removeEvent("play",this.trackCurrentTime);this.removeEvent("pause",this.stopTrackingCurrentTime)},trackCurrentTime:function(){if(this.currentTimeInterval){this.stopTrackingCurrentTime()}this.currentTimeInterval=setInterval(_V_.proxy(this,function(){this.triggerEvent("timeupdate")}),250)},stopTrackingCurrentTime:function(){clearInterval(this.currentTimeInterval)},onEnded:function(){if(this.options.loop){this.currentTime(0);this.play()}else{this.pause();this.currentTime(0);this.pause()}},onPlay:function(){_V_.removeClass(this.el,"vjs-paused");_V_.addClass(this.el,"vjs-playing")},onPause:function(){_V_.removeClass(this.el,"vjs-playing");_V_.addClass(this.el,"vjs-paused")},onError:function(e){_V_.log("Video Error",e)},apiCall:function(method,arg){if(this.isReady){return this.tech[method](arg)}else{_V_.log("The playback technology API is not ready yet. Use player.ready(myFunction). ["+method+"]",arguments.callee.caller.arguments.callee.caller.arguments.callee.caller);return false}},play:function(){this.apiCall("play");return this},pause:function(){this.apiCall("pause");return this},paused:function(){return this.apiCall("paused")},currentTime:function(seconds){if(seconds!==undefined){this.values.lastSetCurrentTime=seconds;this.apiCall("setCurrentTime",seconds);if(this.manualTimeUpdates){this.triggerEvent("timeupdate")}return this}return this.values.currentTime=this.apiCall("currentTime")},duration:function(){return this.apiCall("duration")},remainingTime:function(){return this.duration()-this.currentTime()},buffered:function(){var buffered=this.apiCall("buffered"),start=0,end=this.values.bufferEnd=this.values.bufferEnd||0,timeRange;if(buffered&&buffered.length>0&&buffered.end(0)!==end){end=buffered.end(0);this.values.bufferEnd=end}return _V_.createTimeRange(start,end)},bufferedPercent:function(){return(this.duration())?this.buffered().end(0)/this.duration():0},volume:function(percentAsDecimal){if(percentAsDecimal!==undefined){var vol=Math.max(0,Math.min(1,parseFloat(percentAsDecimal)));this.values.volume=vol;this.apiCall("setVolume",vol);_V_.setLocalStorage("volume",vol);return this}return this.apiCall("volume")},muted:function(muted){if(muted!==undefined){this.apiCall("setMuted",muted);return this}return this.apiCall("muted")},width:function(width,skipListeners){if(width!==undefined){this.el.width=width;this.el.style.width=width+"px";if(!skipListeners){this.triggerEvent("resize")}return this}return parseInt(this.el.getAttribute("width"))},height:function(height){if(height!==undefined){this.el.height=height;this.el.style.height=height+"px";this.triggerEvent("resize");return this}return parseInt(this.el.getAttribute("height"))},size:function(width,height){return this.width(width,true).height(height)},supportsFullScreen:function(){return this.apiCall("supportsFullScreen")},requestFullScreen:function(){var requestFullScreen=_V_.support.requestFullScreen;this.isFullScreen=true;if(requestFullScreen){if(this.tech.support.fullscreenResize===false&&this.options.flash.iFrameMode!=true){this.pause();this.unloadTech();_V_.addEvent(document,requestFullScreen.eventName,this.proxy(function(){_V_.removeEvent(document,requestFullScreen.eventName,arguments.callee);this.loadTech(this.techName,{src:this.values.src})}));this.el[requestFullScreen.requestFn]()}else{this.el[requestFullScreen.requestFn]()}_V_.addEvent(document,requestFullScreen.eventName,this.proxy(function(){this.isFullScreen=document[requestFullScreen.isFullScreen]}))}else{if(this.tech.supportsFullScreen()){this.apiCall("enterFullScreen")}else{this.enterFullWindow()}}this.triggerEvent("fullscreenchange");return this},cancelFullScreen:function(){var requestFullScreen=_V_.support.requestFullScreen;if(requestFullScreen){if(this.tech.support.fullscreenResize===false&&this.options.flash.iFrameMode!=true){this.pause();this.unloadTech();_V_.addEvent(document,requestFullScreen.eventName,this.proxy(function(){_V_.removeEvent(document,requestFullScreen.eventName,arguments.callee);this.loadTech(this.techName,{src:this.values.src})}));document[requestFullScreen.cancelFn]()}else{document[requestFullScreen.cancelFn]()}}else{if(this.tech.supportsFullScreen()){this.apiCall("exitFullScreen")}else{this.exitFullWindow()}}this.isFullScreen=false;this.triggerEvent("fullscreenchange");return this},enterFullWindow:function(){this.isFullWindow=true;this.docOrigOverflow=document.documentElement.style.overflow;_V_.addEvent(document,"keydown",_V_.proxy(this,this.fullWindowOnEscKey));document.documentElement.style.overflow="hidden";_V_.addClass(document.body,"vjs-full-window");_V_.addClass(this.el,"vjs-fullscreen");this.triggerEvent("enterFullWindow")},fullWindowOnEscKey:function(event){if(event.keyCode==27){if(this.isFullScreen==true){this.cancelFullScreen()}else{this.exitFullWindow()}}},exitFullWindow:function(){this.isFullWindow=false;_V_.removeEvent(document,"keydown",this.fullWindowOnEscKey);document.documentElement.style.overflow=this.docOrigOverflow;_V_.removeClass(document.body,"vjs-full-window");_V_.removeClass(this.el,"vjs-fullscreen");this.triggerEvent("exitFullWindow")},src:function(source){if(source instanceof Array){var sources=source;techLoop:for(var i=0,j=this.options.techOrder;i<j.length;i++){var techName=j[i],tech=_V_[techName];if(tech.isSupported()){for(var a=0,b=sources;a<b.length;a++){var source=b[a];if(tech.canPlaySource.call(this,source)){if(techName==this.techName){this.src(source)}else{this.loadTech(techName,source)}break techLoop}}}}}else{if(source instanceof Object){if(_V_[this.techName].canPlaySource(source)){this.src(source.src)}else{this.src([source])}}else{this.values.src=source;if(!this.isReady){this.ready(function(){this.src(source)})}else{this.apiCall("src",source);if(this.options.preload=="auto"){this.load()}if(this.options.autoplay){this.play()}}}}return this},load:function(){this.apiCall("load");return this},currentSrc:function(){return this.apiCall("currentSrc")},textTrackValue:function(kind,value){if(value!==undefined){this.values[kind]=value;this.triggerEvent(kind+"update");return this}return this.values[kind]},preload:function(value){if(value!==undefined){this.apiCall("setPreload",value);this.options.preload=value;return this}return this.apiCall("preload",value)},autoplay:function(value){if(value!==undefined){this.apiCall("setAutoplay",value);this.options.autoplay=value;return this}return this.apiCall("autoplay",value)},loop:function(value){if(value!==undefined){this.apiCall("setLoop",value);this.options.loop=value;return this}return this.apiCall("loop",value)},controls:function(){return this.options.controls},textTracks:function(){return this.options.tracks},poster:function(){return this.apiCall("poster")},error:function(){return this.apiCall("error")},networkState:function(){return this.apiCall("networkState")},readyState:function(){return this.apiCall("readyState")},seeking:function(){return this.apiCall("seeking")},initialTime:function(){return this.apiCall("initialTime")},startOffsetTime:function(){return this.apiCall("startOffsetTime")},played:function(){return this.apiCall("played")},seekable:function(){return this.apiCall("seekable")},ended:function(){return this.apiCall("ended")},videoTracks:function(){return this.apiCall("videoTracks")},audioTracks:function(){return this.apiCall("audioTracks")},videoWidth:function(){return this.apiCall("videoWidth")},videoHeight:function(){return this.apiCall("videoHeight")},defaultPlaybackRate:function(){return this.apiCall("defaultPlaybackRate")},playbackRate:function(){return this.apiCall("playbackRate")},controls:function(){return this.apiCall("controls")},defaultMuted:function(){return this.apiCall("defaultMuted")}});(function(){var requestFn,cancelFn,eventName,isFullScreen,playerProto=_V_.Player.prototype;if(document.cancelFullscreen!==undefined){requestFn="requestFullscreen";cancelFn="exitFullscreen";eventName="fullscreenchange";isFullScreen="fullScreen"}else{_V_.each(["moz","webkit"],function(prefix){if((prefix!="moz"||document.mozFullScreenEnabled)&&document[prefix+"CancelFullScreen"]!==undefined){requestFn=prefix+"RequestFullScreen";cancelFn=prefix+"CancelFullScreen";eventName=prefix+"fullscreenchange";if(prefix=="webkit"){isFullScreen=prefix+"IsFullScreen"}else{_V_.log("moz here");isFullScreen=prefix+"FullScreen"}}})}if(requestFn){_V_.support.requestFullScreen={requestFn:requestFn,cancelFn:cancelFn,eventName:eventName,isFullScreen:isFullScreen}}})();_V_.PlaybackTech=_V_.Component.extend({init:function(player,options){},onClick:function(){if(this.player.options.controls){_V_.PlayToggle.prototype.onClick.call(this)}}});_V_.apiMethods="play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(",");_V_.each(_V_.apiMethods,function(methodName){_V_.PlaybackTech.prototype[methodName]=function(){throw new Error("The '"+method+"' method is not available on the playback technology's API")}});_V_.html5=_V_.PlaybackTech.extend({init:function(player,options,ready){this.player=player;this.el=this.createElement();this.ready(ready);this.addEvent("click",this.proxy(this.onClick));var source=options.source;if(source&&this.el.currentSrc==source.src){player.triggerEvent("loadstart")}else{if(source){this.el.src=source.src}}player.ready(function(){if(this.options.autoplay&&this.paused()){this.tag.poster=null;this.play()}});this.setupTriggers();this.triggerReady()},destroy:function(){this.player.tag=false;this.removeTriggers();this.el.parentNode.removeChild(this.el)},createElement:function(){var html5=_V_.html5,player=this.player,el=player.tag,newEl;if(!el||this.support.movingElementInDOM===false){if(el){player.el.removeChild(el)}newEl=_V_.createElement("video",{id:el.id||player.el.id+"_html5_api",className:el.className||"vjs-tech"});el=newEl;_V_.insertFirst(el,player.el)}_V_.each(["autoplay","preload","loop","muted"],function(attr){el[attr]=player.options[attr]},this);return el},setupTriggers:function(){_V_.each.call(this,_V_.html5.events,function(type){_V_.addEvent(this.el,type,_V_.proxy(this.player,this.eventHandler))})},removeTriggers:function(){_V_.each.call(this,_V_.html5.events,function(type){_V_.removeEvent(this.el,type,_V_.proxy(this.player,this.eventHandler))})},eventHandler:function(e){e.stopPropagation();this.triggerEvent(e)},play:function(){this.el.play()},pause:function(){this.el.pause()},paused:function(){return this.el.paused},currentTime:function(){return this.el.currentTime},setCurrentTime:function(seconds){try{this.el.currentTime=seconds}catch(e){_V_.log(e,"Video isn't ready. (VideoJS)")}},duration:function(){return this.el.duration||0},buffered:function(){return this.el.buffered},volume:function(){return this.el.volume},setVolume:function(percentAsDecimal){this.el.volume=percentAsDecimal},muted:function(){return this.el.muted},setMuted:function(muted){this.el.muted=muted},width:function(){return this.el.offsetWidth},height:function(){return this.el.offsetHeight},supportsFullScreen:function(){if(typeof this.el.webkitEnterFullScreen=="function"){if(!navigator.userAgent.match("Chrome")&&!navigator.userAgent.match("Mac OS X 10.5")){return true}}return false},enterFullScreen:function(){try{this.el.webkitEnterFullScreen()}catch(e){if(e.code==11){_V_.log("VideoJS: Video not ready.")}}},src:function(src){this.el.src=src},load:function(){this.el.load()},currentSrc:function(){return this.el.currentSrc},preload:function(){return this.el.preload},setPreload:function(val){this.el.preload=val},autoplay:function(){return this.el.autoplay},setAutoplay:function(val){this.el.autoplay=val},loop:function(){return this.el.loop},setLoop:function(val){this.el.loop=val},error:function(){return this.el.error},seeking:function(){return this.el.seeking},ended:function(){return this.el.ended},controls:function(){return this.player.options.controls},defaultMuted:function(){return this.el.defaultMuted}});_V_.html5.isSupported=function(){return !!document.createElement("video").canPlayType};_V_.html5.canPlaySource=function(srcObj){return !!document.createElement("video").canPlayType(srcObj.type)};_V_.html5.events="loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange".split(",");_V_.html5.prototype.support={fullscreen:(typeof _V_.testVid.webkitEnterFullScreen!==undefined)?(!_V_.ua.match("Chrome")&&!_V_.ua.match("Mac OS X 10.5")?true:false):false,movingElementInDOM:!_V_.isIOS()};if(_V_.isAndroid()){if(_V_.androidVersion()<3){document.createElement("video").constructor.prototype.canPlayType=function(type){return(type&&type.toLowerCase().indexOf("video/mp4")!=-1)?"maybe":""}}}_V_.flash=_V_.PlaybackTech.extend({init:function(player,options){this.player=player;var source=options.source,parentEl=options.parentEl,placeHolder=this.el=_V_.createElement("div",{id:parentEl.id+"_temp_flash"}),objId=player.el.id+"_flash_api",playerOptions=player.options,flashVars=_V_.merge({readyFunction:"_V_.flash.onReady",eventProxyFunction:"_V_.flash.onEvent",errorEventProxyFunction:"_V_.flash.onError",autoplay:playerOptions.autoplay,preload:playerOptions.preload,loop:playerOptions.loop,muted:playerOptions.muted},options.flashVars),params=_V_.merge({wmode:"opaque",bgcolor:"#000000"},options.params),attributes=_V_.merge({id:objId,name:objId,"class":"vjs-tech"},options.attributes);if(source){flashVars.src=encodeURIComponent(source.src)}_V_.insertFirst(placeHolder,parentEl);if(options.startTime){this.ready(function(){this.load();this.play();this.currentTime(options.startTime)})}if(options.iFrameMode==true&&!_V_.isFF){var iFrm=_V_.createElement("iframe",{id:objId+"_iframe",name:objId+"_iframe",className:"vjs-tech",scrolling:"no",marginWidth:0,marginHeight:0,frameBorder:0});flashVars.readyFunction="ready";flashVars.eventProxyFunction="events";flashVars.errorEventProxyFunction="errors";_V_.addEvent(iFrm,"load",_V_.proxy(this,function(){var iDoc,objTag,swfLoc,iWin=iFrm.contentWindow,varString="";iDoc=iFrm.contentDocument?iFrm.contentDocument:iFrm.contentWindow.document;iDoc.write(_V_.flash.getEmbedCode(options.swf,flashVars,params,attributes));iWin.player=this.player;iWin.ready=_V_.proxy(this.player,function(currSwf){var el=iDoc.getElementById(currSwf),player=this,tech=player.tech;tech.el=el;_V_.addEvent(el,"click",tech.proxy(tech.onClick));_V_.flash.checkReady(tech)});iWin.events=_V_.proxy(this.player,function(swfID,eventName,other){var player=this;if(player&&player.techName=="flash"){player.triggerEvent(eventName)}});iWin.errors=_V_.proxy(this.player,function(swfID,eventName){_V_.log("Flash Error",eventName)})}));placeHolder.parentNode.replaceChild(iFrm,placeHolder)}else{_V_.flash.embed(options.swf,placeHolder,flashVars,params,attributes)}},destroy:function(){this.el.parentNode.removeChild(this.el)},play:function(){this.el.vjs_play()},pause:function(){this.el.vjs_pause()},src:function(src){this.el.vjs_src(src);if(this.player.autoplay){var tech=this;setTimeout(function(){tech.play()},0)}},load:function(){this.el.vjs_load()},poster:function(){this.el.vjs_getProperty("poster")},buffered:function(){return _V_.createTimeRange(0,this.el.vjs_getProperty("buffered"))},supportsFullScreen:function(){return false},enterFullScreen:function(){return false}});(function(){var api=_V_.flash.prototype,readWrite="preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted".split(","),readOnly="error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks".split(","),callOnly="load,play,pause".split(",");createSetter=function(attr){var attrUpper=attr.charAt(0).toUpperCase()+attr.slice(1);api["set"+attrUpper]=function(val){return this.el.vjs_setProperty(attr,val)}},createGetter=function(attr){api[attr]=function(){return this.el.vjs_getProperty(attr)}};_V_.each(readWrite,function(attr){createGetter(attr);createSetter(attr)});_V_.each(readOnly,function(attr){createGetter(attr)})})();_V_.flash.isSupported=function(){return _V_.flash.version()[0]>=10};_V_.flash.canPlaySource=function(srcObj){if(srcObj.type in _V_.flash.prototype.support.formats){return"maybe"}};_V_.flash.prototype.support={formats:{"video/flv":"FLV","video/x-flv":"FLV","video/mp4":"MP4","video/m4v":"MP4"},progressEvent:false,timeupdateEvent:false,fullscreenResize:false,parentResize:!(_V_.ua.match("Firefox"))};_V_.flash.onReady=function(currSwf){var el=_V_.el(currSwf);var player=el.player||el.parentNode.player,tech=player.tech;el.player=player;tech.el=el;tech.addEvent("click",tech.onClick);_V_.flash.checkReady(tech)};_V_.flash.checkReady=function(tech){if(tech.el.vjs_getProperty){tech.triggerReady()}else{setTimeout(function(){_V_.flash.checkReady(tech)},50)}};_V_.flash.onEvent=function(swfID,eventName){var player=_V_.el(swfID).player;player.triggerEvent(eventName)};_V_.flash.onError=function(swfID,err){_V_.log("Flash Error",err,swfID)};_V_.flash.version=function(){var version="0,0,0";try{version=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version").replace(/\D+/g,",").match(/^,?(.+),?$/)[1]}catch(e){try{if(navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){version=(navigator.plugins["Shockwave Flash 2.0"]||navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g,",").match(/^,?(.+),?$/)[1]}}catch(e){}}return version.split(",")};_V_.flash.embed=function(swf,placeHolder,flashVars,params,attributes){var code=_V_.flash.getEmbedCode(swf,flashVars,params,attributes),obj=_V_.createElement("div",{innerHTML:code}).childNodes[0],par=placeHolder.parentNode;placeHolder.parentNode.replaceChild(obj,placeHolder);if(_V_.isIE()){var newObj=par.childNodes[0];setTimeout(function(){newObj.style.display="block"},1000)}return obj};_V_.flash.getEmbedCode=function(swf,flashVars,params,attributes){var objTag='<object type="application/x-shockwave-flash"',flashVarsString="",paramsString="";attrsString="";if(flashVars){_V_.eachProp(flashVars,function(key,val){flashVarsString+=(key+"="+val+"&")})}params=_V_.merge({movie:swf,flashvars:flashVarsString,allowScriptAccess:"always",allowNetworking:"all"},params);_V_.eachProp(params,function(key,val){paramsString+='<param name="'+key+'" value="'+val+'" />'});attributes=_V_.merge({data:swf,width:"100%",height:"100%"},attributes);_V_.eachProp(attributes,function(key,val){attrsString+=(key+'="'+val+'" ')});return objTag+attrsString+">"+paramsString+"</object>"};_V_.Track=function(attributes,player){this.player=player;this.src=attributes.src;this.kind=attributes.kind;this.srclang=attributes.srclang;this.label=attributes.label;this["default"]=attributes["default"];this.title=attributes.title;this.cues=[];this.currentCue=false;this.lastCueIndex=0;player.addEvent("timeupdate",_V_.proxy(this,this.update));player.addEvent("ended",_V_.proxy(this,function(){this.lastCueIndex=0}));_V_.get(attributes.src,_V_.proxy(this,this.parseCues))};_V_.Track.prototype={parseCues:function(srcContent){var cue,time,text,lines=srcContent.split("\n"),line="";for(var i=0;i<lines.length;i++){line=_V_.trim(lines[i]);if(line){cue={id:line,index:this.cues.length};line=_V_.trim(lines[++i]);time=line.split(" --> ");cue.startTime=this.parseCueTime(time[0]);cue.endTime=this.parseCueTime(time[1]);text=[];for(var j=i;j<lines.length;j++){line=_V_.trim(lines[++i]);if(!line){break}text.push(line)}cue.text=text.join("<br/>");this.cues.push(cue)}}},parseCueTime:function(timeText){var parts=timeText.split(":"),time=0;time+=parseFloat(parts[0])*60*60;time+=parseFloat(parts[1])*60;var seconds=parts[2].split(/\.|,/);time+=parseFloat(seconds[0]);ms=parseFloat(seconds[1]);if(ms){time+=ms/1000}return time},update:function(){if(this.cues&&this.cues.length>0){var time=this.player.currentTime();if(!this.currentCue||this.currentCue.startTime>=time||this.currentCue.endTime<time){var newSubIndex=false,reverse=(this.cues[this.lastCueIndex].startTime>time),i=this.lastCueIndex-(reverse?1:0);while(true){if(reverse){if(i<0||this.cues[i].endTime<time){break}if(this.cues[i].startTime<time){newSubIndex=i;break}i--}else{if(i>=this.cues.length||this.cues[i].startTime>time){break}if(this.cues[i].endTime>time){newSubIndex=i;break}i++}}if(newSubIndex!==false){this.currentCue=this.cues[newSubIndex];this.lastCueIndex=newSubIndex;this.updatePlayer(this.currentCue.text)}else{if(this.currentCue){this.currentCue=false;this.updatePlayer("")}}}}},updatePlayer:function(text){this.player.textTrackValue(this.kind,text)}};_V_.addEvent(window,"load",function(){_V_.windowLoaded=true});_V_.autoSetup();window.VideoJS=window._V_=VideoJS})(window); +(function(window,undefined){var document=window.document;document.createElement("video");document.createElement("audio");var VideoJS=function(id,addOptions,ready){var tag;if(typeof id=="string"){if(id.indexOf("#")===0){id=id.slice(1)}if(_V_.players[id]){return _V_.players[id]}else{tag=_V_.el(id)}}else{tag=id}if(!tag||!tag.nodeName){throw new TypeError("The element or ID supplied is not valid. (VideoJS)")}return tag.player||new _V_.Player(tag,addOptions,ready)},_V_=VideoJS,CDN_VERSION="GENERATED_CDN_VSN";VideoJS.players={};VideoJS.options={techOrder:["html5","flash"],html5:{},flash:{swf:"http://vjs.zencdn.net/c/video-js.swf"},width:300,height:150,defaultVolume:0,components:{posterImage:{},textTrackDisplay:{},loadingSpinner:{},bigPlayButton:{},controlBar:{}}};if(CDN_VERSION!="GENERATED_CDN_VSN"){_V_.options.flash.swf="http://vjs.zencdn.net/"+CDN_VERSION+"/video-js.swf"}_V_.merge=function(obj1,obj2,safe){if(!obj2){obj2={}}for(var attrname in obj2){if(obj2.hasOwnProperty(attrname)&&(!safe||!obj1.hasOwnProperty(attrname))){obj1[attrname]=obj2[attrname]}}return obj1};_V_.extend=function(obj){this.merge(this,obj,true)};_V_.extend({tech:{},controlSets:{},isIE:function(){return !+"\v1"},isFF:function(){return !!_V_.ua.match("Firefox")},isIPad:function(){return navigator.userAgent.match(/iPad/i)!==null},isIPhone:function(){return navigator.userAgent.match(/iPhone/i)!==null},isIOS:function(){return VideoJS.isIPhone()||VideoJS.isIPad()},iOSVersion:function(){var match=navigator.userAgent.match(/OS (\d+)_/i);if(match&&match[1]){return match[1]}},isAndroid:function(){return navigator.userAgent.match(/Android.*AppleWebKit/i)!==null},androidVersion:function(){var match=navigator.userAgent.match(/Android (\d+)\./i);if(match&&match[1]){return match[1]}},testVid:document.createElement("video"),ua:navigator.userAgent,support:{},each:function(arr,fn){if(!arr||arr.length===0){return}for(var i=0,j=arr.length;i<j;i++){fn.call(this,arr[i],i)}},eachProp:function(obj,fn){if(!obj){return}for(var name in obj){if(obj.hasOwnProperty(name)){fn.call(this,name,obj[name])}}},el:function(id){return document.getElementById(id)},createElement:function(tagName,attributes){var el=document.createElement(tagName),attrname;for(attrname in attributes){if(attributes.hasOwnProperty(attrname)){if(attrname.indexOf("-")!==-1){el.setAttribute(attrname,attributes[attrname])}else{el[attrname]=attributes[attrname]}}}return el},insertFirst:function(node,parent){if(parent.firstChild){parent.insertBefore(node,parent.firstChild)}else{parent.appendChild(node)}},addClass:function(element,classToAdd){if((" "+element.className+" ").indexOf(" "+classToAdd+" ")==-1){element.className=element.className===""?classToAdd:element.className+" "+classToAdd}},removeClass:function(element,classToRemove){if(element.className.indexOf(classToRemove)==-1){return}var classNames=element.className.split(" ");classNames.splice(classNames.indexOf(classToRemove),1);element.className=classNames.join(" ")},remove:function(item,array){if(!array){return}var i=array.indexOf(item);if(i!=-1){return array.splice(i,1)}},blockTextSelection:function(){document.body.focus();document.onselectstart=function(){return false}},unblockTextSelection:function(){document.onselectstart=function(){return true}},formatTime:function(seconds,guide){guide=guide||seconds;var s=Math.floor(seconds%60),m=Math.floor(seconds/60%60),h=Math.floor(seconds/3600),gm=Math.floor(guide/60%60),gh=Math.floor(guide/3600);h=(h>0||gh>0)?h+":":"";m=(((h||gm>=10)&&m<10)?"0"+m:m)+":";s=(s<10)?"0"+s:s;return h+m+s},uc:function(string){return string.charAt(0).toUpperCase()+string.slice(1)},getRelativePosition:function(x,relativeElement){return Math.max(0,Math.min(1,(x-_V_.findPosX(relativeElement))/relativeElement.offsetWidth))},getComputedStyleValue:function(element,style){return window.getComputedStyle(element,null).getPropertyValue(style)},trim:function(string){return string.toString().replace(/^\s+/,"").replace(/\s+$/,"")},round:function(num,dec){if(!dec){dec=0}return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec)},isEmpty:function(object){for(var prop in object){return false}return true},createTimeRange:function(start,end){return{length:1,start:function(){return start},end:function(){return end}}},cache:{},guid:1,expando:"vdata"+(new Date).getTime(),getData:function(elem){var id=elem[_V_.expando];if(!id){id=elem[_V_.expando]=_V_.guid++;_V_.cache[id]={}}return _V_.cache[id]},removeData:function(elem){var id=elem[_V_.expando];if(!id){return}delete _V_.cache[id];try{delete elem[_V_.expando]}catch(e){if(elem.removeAttribute){elem.removeAttribute(_V_.expando)}else{elem[_V_.expando]=null}}},proxy:function(context,fn,uid){if(!fn.guid){fn.guid=_V_.guid++}var ret=function(){return fn.apply(context,arguments)};ret.guid=(uid)?uid+"_"+fn.guid:fn.guid;return ret},get:function(url,onSuccess,onError){var local=(url.indexOf("file:")==0||(window.location.href.indexOf("file:")==0&&url.indexOf("http:")==-1));if(typeof XMLHttpRequest=="undefined"){XMLHttpRequest=function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(f){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(g){}throw new Error("This browser does not support XMLHttpRequest.")}}var request=new XMLHttpRequest();try{request.open("GET",url)}catch(e){_V_.log("VideoJS XMLHttpRequest (open)",e);return false}request.onreadystatechange=_V_.proxy(this,function(){if(request.readyState==4){if(request.status==200||local&&request.status==0){onSuccess(request.responseText)}else{if(onError){onError()}}}});try{request.send()}catch(e){_V_.log("VideoJS XMLHttpRequest (send)",e);if(onError){onError(e)}}},setLocalStorage:function(key,value){var localStorage=window.localStorage||false;if(!localStorage){return}try{localStorage[key]=value}catch(e){if(e.code==22||e.code==1014){_V_.log("LocalStorage Full (VideoJS)",e)}else{_V_.log("LocalStorage Error (VideoJS)",e)}}},getAbsoluteURL:function(url){if(!url.match(/^https?:\/\//)){url=_V_.createElement("div",{innerHTML:'<a href="'+url+'">x</a>'}).firstChild.href}return url}});_V_.log=function(){_V_.log.history=_V_.log.history||[];_V_.log.history.push(arguments);if(window.console){arguments.callee=arguments.callee.caller;var newarr=[].slice.call(arguments);(typeof console.log==="object"?_V_.log.apply.call(console.log,console,newarr):console.log.apply(console,newarr))}};(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,timeStamp,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try{console.log();return window.console}catch(err){return window.console={}}})());if("getBoundingClientRect" in document.documentElement){_V_.findPosX=function(el){var box;try{box=el.getBoundingClientRect()}catch(e){}if(!box){return 0}var docEl=document.documentElement,body=document.body,clientLeft=docEl.clientLeft||body.clientLeft||0,scrollLeft=window.pageXOffset||body.scrollLeft,left=box.left+scrollLeft-clientLeft;return left}}else{_V_.findPosX=function(el){var curleft=el.offsetLeft;while(el=obj.offsetParent){if(el.className.indexOf("video-js")==-1){}else{}curleft+=el.offsetLeft}return curleft}}if(!Array.prototype.indexOf){Array.prototype.indexOf=function(searchElement){if(this===void 0||this===null){throw new TypeError()}var t=Object(this);var len=t.length>>>0;if(len===0){return -1}var n=0;if(arguments.length>0){n=Number(arguments[1]);if(n!==n){n=0}else{if(n!==0&&n!==(1/0)&&n!==-(1/0)){n=(n>0||-1)*Math.floor(Math.abs(n))}}}if(n>=len){return -1}var k=n>=0?n:Math.max(len-Math.abs(n),0);for(;k<len;k++){if(k in t&&t[k]===searchElement){return k}}return -1}}var JSON;if(!JSON){JSON={}}(function(){var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());_V_.extend({addEvent:function(elem,type,fn){var data=_V_.getData(elem),handlers;if(data&&!data.handler){data.handler=function(event){event=_V_.fixEvent(event);var handlers=_V_.getData(elem).events[event.type];if(handlers){var handlersCopy=[];_V_.each(handlers,function(handler,i){handlersCopy[i]=handler});for(var i=0,l=handlersCopy.length;i<l;i++){handlersCopy[i].call(elem,event)}}}}if(!data.events){data.events={}}handlers=data.events[type];if(!handlers){handlers=data.events[type]=[];if(document.addEventListener){elem.addEventListener(type,data.handler,false)}else{if(document.attachEvent){elem.attachEvent("on"+type,data.handler)}}}if(!fn.guid){fn.guid=_V_.guid++}handlers.push(fn)},removeEvent:function(elem,type,fn){var data=_V_.getData(elem),handlers;if(!data.events){return}if(!type){for(type in data.events){_V_.cleanUpEvents(elem,type)}return}handlers=data.events[type];if(!handlers){return}if(fn&&fn.guid){for(var i=0;i<handlers.length;i++){if(handlers[i].guid===fn.guid){handlers.splice(i--,1)}}}_V_.cleanUpEvents(elem,type)},cleanUpEvents:function(elem,type){var data=_V_.getData(elem);if(data.events[type].length===0){delete data.events[type];if(document.removeEventListener){elem.removeEventListener(type,data.handler,false)}else{if(document.detachEvent){elem.detachEvent("on"+type,data.handler)}}}if(_V_.isEmpty(data.events)){delete data.events;delete data.handler}if(_V_.isEmpty(data)){_V_.removeData(elem)}},fixEvent:function(event){if(event[_V_.expando]){return event}var originalEvent=event;event=new _V_.Event(originalEvent);for(var i=_V_.Event.props.length,prop;i;){prop=_V_.Event.props[--i];event[prop]=originalEvent[prop]}if(!event.target){event.target=event.srcElement||document}if(event.target.nodeType===3){event.target=event.target.parentNode}if(!event.relatedTarget&&event.fromElement){event.relatedTarget=event.fromElement===event.target?event.toElement:event.fromElement}if(event.pageX==null&&event.clientX!=null){var eventDocument=event.target.ownerDocument||document,doc=eventDocument.documentElement,body=eventDocument.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc&&doc.clientLeft||body&&body.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc&&doc.clientTop||body&&body.clientTop||0)}if(event.which==null&&(event.charCode!=null||event.keyCode!=null)){event.which=event.charCode!=null?event.charCode:event.keyCode}if(!event.metaKey&&event.ctrlKey){event.metaKey=event.ctrlKey}if(!event.which&&event.button!==undefined){event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)))}return event},triggerEvent:function(elem,event){var data=_V_.getData(elem),parent=elem.parentNode||elem.ownerDocument,type=event.type||event,handler;if(data){handler=data.handler}event=typeof event==="object"?event[_V_.expando]?event:new _V_.Event(type,event):new _V_.Event(type);event.type=type;if(handler){handler.call(elem,event)}event.result=undefined;event.target=elem},one:function(elem,type,fn){_V_.addEvent(elem,type,function(){_V_.removeEvent(elem,type,arguments.callee);fn.apply(this,arguments)})}});_V_.Event=function(src,props){if(src&&src.type){this.originalEvent=src;this.type=src.type;this.isDefaultPrevented=(src.defaultPrevented||src.returnValue===false||src.getPreventDefault&&src.getPreventDefault())?returnTrue:returnFalse}else{this.type=src}if(props){_V_.merge(this,props)}this.timeStamp=(new Date).getTime();this[_V_.expando]=true};_V_.Event.prototype={preventDefault:function(){this.isDefaultPrevented=returnTrue;var e=this.originalEvent;if(!e){return}if(e.preventDefault){e.preventDefault()}else{e.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=returnTrue;var e=this.originalEvent;if(!e){return}if(e.stopPropagation){e.stopPropagation()}e.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=returnTrue;this.stopPropagation()},isDefaultPrevented:returnFalse,isPropagationStopped:returnFalse,isImmediatePropagationStopped:returnFalse};_V_.Event.props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" ");function returnTrue(){return true}function returnFalse(){return false}(function(){var initializing=false,fnTest=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/;_V_.Class=function(){};_V_.Class.extend=function(prop){var _super=this.prototype;initializing=true;var prototype=new this();initializing=false;for(var name in prop){prototype[name]=typeof prop[name]=="function"&&typeof _super[name]=="function"&&fnTest.test(prop[name])?(function(name,fn){return function(){var tmp=this._super;this._super=_super[name];var ret=fn.apply(this,arguments);this._super=tmp;return ret}})(name,prop[name]):prop[name]}function Class(){if(!initializing&&this.init){return this.init.apply(this,arguments)}else{if(!initializing){return arguments.callee.prototype.init()}}}Class.prototype=prototype;Class.constructor=Class;Class.extend=arguments.callee;return Class}})();_V_.Component=_V_.Class.extend({init:function(player,options){this.player=player;options=this.options=_V_.merge(this.options||{},options);if(options.el){this.el=options.el}else{this.el=this.createElement()}this.initComponents()},destroy:function(){},createElement:function(type,attrs){return _V_.createElement(type||"div",attrs)},buildCSSClass:function(){return""},initComponents:function(){var options=this.options;if(options&&options.components){this.eachProp(options.components,function(name,opts){var tempAdd=this.proxy(function(){this[name]=this.addComponent(name,opts)});if(opts.loadEvent){this.one(opts.loadEvent,tempAdd)}else{tempAdd()}})}},addComponent:function(name,options){var component,componentClass;if(typeof name=="string"){options=options||{};componentClass=options.componentClass||_V_.uc(name);component=new _V_[componentClass](this.player||this,options)}else{component=name}this.el.appendChild(component.el);return component},removeComponent:function(component){this.el.removeChild(component.el)},show:function(){this.el.style.display="block"},hide:function(){this.el.style.display="none"},fadeIn:function(){this.removeClass("vjs-fade-out");this.addClass("vjs-fade-in")},fadeOut:function(){this.removeClass("vjs-fade-in");this.addClass("vjs-fade-out")},lockShowing:function(){var style=this.el.style;style.display="block";style.opacity=1;style.visiblity="visible"},unlockShowing:function(){var style=this.el.style;style.display="";style.opacity="";style.visiblity=""},addClass:function(classToAdd){_V_.addClass(this.el,classToAdd)},removeClass:function(classToRemove){_V_.removeClass(this.el,classToRemove)},addEvent:function(type,fn,uid){return _V_.addEvent(this.el,type,_V_.proxy(this,fn))},removeEvent:function(type,fn){return _V_.removeEvent(this.el,type,fn)},triggerEvent:function(type,e){return _V_.triggerEvent(this.el,type,e)},one:function(type,fn){_V_.one(this.el,type,_V_.proxy(this,fn))},ready:function(fn){if(!fn){return this}if(this.isReady){fn.call(this)}else{if(this.readyQueue===undefined){this.readyQueue=[]}this.readyQueue.push(fn)}return this},triggerReady:function(){this.isReady=true;if(this.readyQueue&&this.readyQueue.length>0){this.each(this.readyQueue,function(fn){fn.call(this)});this.readyQueue=[];this.triggerEvent("ready")}},each:function(arr,fn){_V_.each.call(this,arr,fn)},eachProp:function(obj,fn){_V_.eachProp.call(this,obj,fn)},extend:function(obj){_V_.merge(this,obj)},proxy:function(fn,uid){return _V_.proxy(this,fn,uid)}});_V_.Player=_V_.Component.extend({init:function(tag,addOptions,ready){this.tag=tag;var el=this.el=_V_.createElement("div"),options=this.options={};_V_.merge(options,_V_.options);_V_.merge(options,this.getVideoTagSettings());_V_.merge(options,addOptions);this.ready(ready);tag.removeAttribute("controls");tag.removeAttribute("poster");tag.player=el.player=this;tag.parentNode.insertBefore(el,tag);el.appendChild(tag);this.id=el.id=tag.id;el.className=tag.className;tag.id+="_html5_api";tag.className="vjs-tech";_V_.players[el.id]=this;el.setAttribute("width",options.width);el.setAttribute("height",options.height);el.style.width=options.width+"px";el.style.height=options.height+"px";tag.removeAttribute("width");tag.removeAttribute("height");if(tag.hasChildNodes()){for(var i=0,j=tag.childNodes;i<j.length;i++){if(j[i].nodeName=="SOURCE"||j[i].nodeName=="TRACK"){tag.removeChild(j[i])}}}this.values={};this.addClass("vjs-paused");this.addEvent("ended",this.onEnded);this.addEvent("play",this.onPlay);this.addEvent("pause",this.onPause);this.addEvent("progress",this.onProgress);this.addEvent("error",this.onError);if(options.controls){this.ready(function(){this.initComponents()})}this.textTracks=[];if(options.tracks&&options.tracks.length>0){this.addTextTracks(options.tracks)}if(!options.sources||options.sources.length==0){for(var i=0,j=options.techOrder;i<j.length;i++){var techName=j[i],tech=_V_[techName];if(tech.isSupported()){this.loadTech(techName);break}}}else{this.src(options.sources)}},values:{},destroy:function(){this.stopTrackingProgress();this.stopTrackingCurrentTime();_V_.players[this.id]=null;delete _V_.players[this.id];this.tech.destroy();this.el.parentNode.removeChild(this.el)},createElement:function(type,options){},getVideoTagSettings:function(){var options={sources:[],tracks:[]},tag=this.tag,getAttribute="getAttribute";options.src=tag[getAttribute]("src");options.controls=tag[getAttribute]("controls")!==null;options.poster=tag[getAttribute]("poster");options.preload=tag[getAttribute]("preload");options.autoplay=tag[getAttribute]("autoplay")!==null;options.loop=tag[getAttribute]("loop")!==null;options.muted=tag[getAttribute]("muted")!==null;if(this.tag.hasChildNodes()){for(var c,i=0,j=this.tag.childNodes;i<j.length;i++){c=j[i];if(c.nodeName=="SOURCE"){options.sources.push({src:c[getAttribute]("src"),type:c[getAttribute]("type"),media:c[getAttribute]("media"),title:c[getAttribute]("title")})}if(c.nodeName=="TRACK"){options.tracks.push({src:c[getAttribute]("src"),kind:c[getAttribute]("kind"),srclang:c[getAttribute]("srclang"),label:c[getAttribute]("label"),"default":c[getAttribute]("default")!==null,title:c[getAttribute]("title")})}}}return options},loadTech:function(techName,source){if(this.tech){this.unloadTech()}else{if(techName!="html5"&&this.tag){this.el.removeChild(this.tag);this.tag=false}}this.techName=techName;this.isReady=false;var techReady=function(){this.player.triggerReady();if(!this.support.progressEvent){this.player.manualProgressOn()}if(!this.support.timeupdateEvent){this.player.manualTimeUpdatesOn()}};var techOptions=_V_.merge({source:source,parentEl:this.el},this.options[techName]);if(source){if(source.src==this.values.src&&this.values.currentTime>0){techOptions.startTime=this.values.currentTime}this.values.src=source.src}this.tech=new _V_[techName](this,techOptions);this.tech.ready(techReady)},unloadTech:function(){this.tech.destroy();if(this.manualProgress){this.manualProgressOff()}if(this.manualTimeUpdates){this.manualTimeUpdatesOff()}this.tech=false},manualProgressOn:function(){this.manualProgress=true;this.trackProgress();this.tech.addEvent("progress",function(){this.removeEvent("progress",arguments.callee);this.support.progressEvent=true;this.player.manualProgressOff()})},manualProgressOff:function(){this.manualProgress=false;this.stopTrackingProgress()},trackProgress:function(){this.progressInterval=setInterval(_V_.proxy(this,function(){if(this.values.bufferEnd<this.buffered().end(0)){this.triggerEvent("progress")}else{if(this.bufferedPercent()==1){this.stopTrackingProgress();this.triggerEvent("progress")}}}),500)},stopTrackingProgress:function(){clearInterval(this.progressInterval)},manualTimeUpdatesOn:function(){this.manualTimeUpdates=true;this.addEvent("play",this.trackCurrentTime);this.addEvent("pause",this.stopTrackingCurrentTime);this.tech.addEvent("timeupdate",function(){this.removeEvent("timeupdate",arguments.callee);this.support.timeupdateEvent=true;this.player.manualTimeUpdatesOff()})},manualTimeUpdatesOff:function(){this.manualTimeUpdates=false;this.stopTrackingCurrentTime();this.removeEvent("play",this.trackCurrentTime);this.removeEvent("pause",this.stopTrackingCurrentTime)},trackCurrentTime:function(){if(this.currentTimeInterval){this.stopTrackingCurrentTime()}this.currentTimeInterval=setInterval(_V_.proxy(this,function(){this.triggerEvent("timeupdate")}),250)},stopTrackingCurrentTime:function(){clearInterval(this.currentTimeInterval)},onEnded:function(){if(this.options.loop){this.currentTime(0);this.play()}else{this.pause();this.currentTime(0);this.pause()}},onPlay:function(){_V_.removeClass(this.el,"vjs-paused");_V_.addClass(this.el,"vjs-playing")},onPause:function(){_V_.removeClass(this.el,"vjs-playing");_V_.addClass(this.el,"vjs-paused")},onProgress:function(){if(this.bufferedPercent()==1){this.triggerEvent("loadedalldata")}},onError:function(e){_V_.log("Video Error",e)},techCall:function(method,arg){if(!this.tech.isReady){this.tech.ready(function(){this[method](arg)})}else{try{this.tech[method](arg)}catch(e){_V_.log(e)}}},techGet:function(method){if(this.tech.isReady){try{return this.tech[method]()}catch(e){if(this.tech[method]===undefined){_V_.log("Video.js: "+method+" method not defined for "+this.techName+" playback technology.",e)}else{if(e.name=="TypeError"){_V_.log("Video.js: "+method+" unavailable on "+this.techName+" playback technology element.",e);this.tech.isReady=false}else{_V_.log(e)}}}}return},play:function(){this.techCall("play");return this},pause:function(){this.techCall("pause");return this},paused:function(){return(this.techGet("paused")===false)?false:true},currentTime:function(seconds){if(seconds!==undefined){this.values.lastSetCurrentTime=seconds;this.techCall("setCurrentTime",seconds);if(this.manualTimeUpdates){this.triggerEvent("timeupdate")}return this}return this.values.currentTime=(this.techGet("currentTime")||0)},duration:function(){return parseFloat(this.techGet("duration"))},remainingTime:function(){return this.duration()-this.currentTime()},buffered:function(){var buffered=this.techGet("buffered"),start=0,end=this.values.bufferEnd=this.values.bufferEnd||0,timeRange;if(buffered&&buffered.length>0&&buffered.end(0)!==end){end=buffered.end(0);this.values.bufferEnd=end}return _V_.createTimeRange(start,end)},bufferedPercent:function(){return(this.duration())?this.buffered().end(0)/this.duration():0},volume:function(percentAsDecimal){var vol;if(percentAsDecimal!==undefined){vol=Math.max(0,Math.min(1,parseFloat(percentAsDecimal)));this.values.volume=vol;this.techCall("setVolume",vol);_V_.setLocalStorage("volume",vol);return this}vol=parseFloat(this.techGet("volume"));return(isNaN(vol))?1:vol},muted:function(muted){if(muted!==undefined){this.techCall("setMuted",muted);return this}return this.techGet("muted")||false},width:function(width,skipListeners){if(width!==undefined){this.el.width=width;this.el.style.width=width+"px";if(!skipListeners){this.triggerEvent("resize")}return this}return parseInt(this.el.getAttribute("width"))},height:function(height){if(height!==undefined){this.el.height=height;this.el.style.height=height+"px";this.triggerEvent("resize");return this}return parseInt(this.el.getAttribute("height"))},size:function(width,height){return this.width(width,true).height(height)},supportsFullScreen:function(){return this.techGet("supportsFullScreen")||false},requestFullScreen:function(){var requestFullScreen=_V_.support.requestFullScreen;this.isFullScreen=true;if(requestFullScreen){_V_.addEvent(document,requestFullScreen.eventName,this.proxy(function(){this.isFullScreen=document[requestFullScreen.isFullScreen];if(this.isFullScreen==false){_V_.removeEvent(document,requestFullScreen.eventName,arguments.callee)}this.triggerEvent("fullscreenchange")}));if(this.tech.support.fullscreenResize===false&&this.options.flash.iFrameMode!=true){this.pause();this.unloadTech();_V_.addEvent(document,requestFullScreen.eventName,this.proxy(function(){_V_.removeEvent(document,requestFullScreen.eventName,arguments.callee);this.loadTech(this.techName,{src:this.values.src})}));this.el[requestFullScreen.requestFn]()}else{this.el[requestFullScreen.requestFn]()}}else{if(this.tech.supportsFullScreen()){this.triggerEvent("fullscreenchange");this.techCall("enterFullScreen")}else{this.triggerEvent("fullscreenchange");this.enterFullWindow()}}return this},cancelFullScreen:function(){var requestFullScreen=_V_.support.requestFullScreen;this.isFullScreen=false;if(requestFullScreen){if(this.tech.support.fullscreenResize===false&&this.options.flash.iFrameMode!=true){this.pause();this.unloadTech();_V_.addEvent(document,requestFullScreen.eventName,this.proxy(function(){_V_.removeEvent(document,requestFullScreen.eventName,arguments.callee);this.loadTech(this.techName,{src:this.values.src})}));document[requestFullScreen.cancelFn]()}else{document[requestFullScreen.cancelFn]()}}else{if(this.tech.supportsFullScreen()){this.techCall("exitFullScreen");this.triggerEvent("fullscreenchange")}else{this.exitFullWindow();this.triggerEvent("fullscreenchange")}}return this},enterFullWindow:function(){this.isFullWindow=true;this.docOrigOverflow=document.documentElement.style.overflow;_V_.addEvent(document,"keydown",_V_.proxy(this,this.fullWindowOnEscKey));document.documentElement.style.overflow="hidden";_V_.addClass(document.body,"vjs-full-window");_V_.addClass(this.el,"vjs-fullscreen");this.triggerEvent("enterFullWindow")},fullWindowOnEscKey:function(event){if(event.keyCode==27){if(this.isFullScreen==true){this.cancelFullScreen()}else{this.exitFullWindow()}}},exitFullWindow:function(){this.isFullWindow=false;_V_.removeEvent(document,"keydown",this.fullWindowOnEscKey);document.documentElement.style.overflow=this.docOrigOverflow;_V_.removeClass(document.body,"vjs-full-window");_V_.removeClass(this.el,"vjs-fullscreen");this.triggerEvent("exitFullWindow")},selectSource:function(sources){for(var i=0,j=this.options.techOrder;i<j.length;i++){var techName=j[i],tech=_V_[techName];if(tech.isSupported()){for(var a=0,b=sources;a<b.length;a++){var source=b[a];if(tech.canPlaySource.call(this,source)){return{source:source,tech:techName}}}}}return false},src:function(source){if(source instanceof Array){var sourceTech=this.selectSource(source),source,techName;if(sourceTech){source=sourceTech.source;techName=sourceTech.tech;if(techName==this.techName){this.src(source)}else{this.loadTech(techName,source)}}else{_V_.log("No compatible source and playback technology were found.")}}else{if(source instanceof Object){if(_V_[this.techName].canPlaySource(source)){this.src(source.src)}else{this.src([source])}}else{this.values.src=source;if(!this.isReady){this.ready(function(){this.src(source)})}else{this.techCall("src",source);if(this.options.preload=="auto"){this.load()}if(this.options.autoplay){this.play()}}}}return this},load:function(){this.techCall("load");return this},currentSrc:function(){return this.techGet("currentSrc")||this.values.src||""},preload:function(value){if(value!==undefined){this.techCall("setPreload",value);this.options.preload=value;return this}return this.techGet("preload")},autoplay:function(value){if(value!==undefined){this.techCall("setAutoplay",value);this.options.autoplay=value;return this}return this.techGet("autoplay",value)},loop:function(value){if(value!==undefined){this.techCall("setLoop",value);this.options.loop=value;return this}return this.techGet("loop")},controls:function(){return this.options.controls},poster:function(){return this.techGet("poster")},error:function(){return this.techGet("error")},ended:function(){return this.techGet("ended")}});(function(){var requestFn,cancelFn,eventName,isFullScreen,playerProto=_V_.Player.prototype;if(document.cancelFullscreen!==undefined){requestFn="requestFullscreen";cancelFn="exitFullscreen";eventName="fullscreenchange";isFullScreen="fullScreen"}else{_V_.each(["moz","webkit"],function(prefix){if((prefix!="moz"||document.mozFullScreenEnabled)&&document[prefix+"CancelFullScreen"]!==undefined){requestFn=prefix+"RequestFullScreen";cancelFn=prefix+"CancelFullScreen";eventName=prefix+"fullscreenchange";if(prefix=="webkit"){isFullScreen=prefix+"IsFullScreen"}else{isFullScreen=prefix+"FullScreen"}}})}if(requestFn){_V_.support.requestFullScreen={requestFn:requestFn,cancelFn:cancelFn,eventName:eventName,isFullScreen:isFullScreen}}})();_V_.PlaybackTech=_V_.Component.extend({init:function(player,options){},onClick:function(){if(this.player.options.controls){_V_.PlayToggle.prototype.onClick.call(this)}}});_V_.apiMethods="play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(",");_V_.each(_V_.apiMethods,function(methodName){_V_.PlaybackTech.prototype[methodName]=function(){throw new Error("The '"+methodName+"' method is not available on the playback technology's API")}});_V_.html5=_V_.PlaybackTech.extend({init:function(player,options,ready){this.player=player;this.el=this.createElement();this.ready(ready);this.addEvent("click",this.proxy(this.onClick));var source=options.source;if(source&&this.el.currentSrc==source.src){player.triggerEvent("loadstart")}else{if(source){this.el.src=source.src}}player.ready(function(){if(this.options.autoplay&&this.paused()){this.tag.poster=null;this.play()}});this.setupTriggers();this.triggerReady()},destroy:function(){this.player.tag=false;this.removeTriggers();this.el.parentNode.removeChild(this.el)},createElement:function(){var html5=_V_.html5,player=this.player,el=player.tag,newEl;if(!el||this.support.movingElementInDOM===false){if(el){player.el.removeChild(el)}newEl=_V_.createElement("video",{id:el.id||player.el.id+"_html5_api",className:el.className||"vjs-tech"});el=newEl;_V_.insertFirst(el,player.el)}_V_.each(["autoplay","preload","loop","muted"],function(attr){if(player.options[attr]!==null){el[attr]=player.options[attr]}},this);return el},setupTriggers:function(){_V_.each.call(this,_V_.html5.events,function(type){_V_.addEvent(this.el,type,_V_.proxy(this.player,this.eventHandler))})},removeTriggers:function(){_V_.each.call(this,_V_.html5.events,function(type){_V_.removeEvent(this.el,type,_V_.proxy(this.player,this.eventHandler))})},eventHandler:function(e){e.stopPropagation();this.triggerEvent(e)},play:function(){this.el.play()},pause:function(){this.el.pause()},paused:function(){return this.el.paused},currentTime:function(){return this.el.currentTime},setCurrentTime:function(seconds){try{this.el.currentTime=seconds}catch(e){_V_.log(e,"Video isn't ready. (VideoJS)")}},duration:function(){return this.el.duration||0},buffered:function(){return this.el.buffered},volume:function(){return this.el.volume},setVolume:function(percentAsDecimal){this.el.volume=percentAsDecimal},muted:function(){return this.el.muted},setMuted:function(muted){this.el.muted=muted},width:function(){return this.el.offsetWidth},height:function(){return this.el.offsetHeight},supportsFullScreen:function(){if(typeof this.el.webkitEnterFullScreen=="function"){if(!navigator.userAgent.match("Chrome")&&!navigator.userAgent.match("Mac OS X 10.5")){return true}}return false},enterFullScreen:function(){try{this.el.webkitEnterFullScreen()}catch(e){if(e.code==11){_V_.log("VideoJS: Video not ready.")}}},src:function(src){this.el.src=src},load:function(){this.el.load()},currentSrc:function(){return this.el.currentSrc},preload:function(){return this.el.preload},setPreload:function(val){this.el.preload=val},autoplay:function(){return this.el.autoplay},setAutoplay:function(val){this.el.autoplay=val},loop:function(){return this.el.loop},setLoop:function(val){this.el.loop=val},error:function(){return this.el.error},seeking:function(){return this.el.seeking},ended:function(){return this.el.ended},controls:function(){return this.player.options.controls},defaultMuted:function(){return this.el.defaultMuted}});_V_.html5.isSupported=function(){return !!document.createElement("video").canPlayType};_V_.html5.canPlaySource=function(srcObj){return !!document.createElement("video").canPlayType(srcObj.type)};_V_.html5.events="loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange".split(",");_V_.html5.prototype.support={fullscreen:(typeof _V_.testVid.webkitEnterFullScreen!==undefined)?(!_V_.ua.match("Chrome")&&!_V_.ua.match("Mac OS X 10.5")?true:false):false,movingElementInDOM:!_V_.isIOS()};if(_V_.isAndroid()){if(_V_.androidVersion()<3){document.createElement("video").constructor.prototype.canPlayType=function(type){return(type&&type.toLowerCase().indexOf("video/mp4")!=-1)?"maybe":""}}}_V_.flash=_V_.PlaybackTech.extend({init:function(player,options){this.player=player;var source=options.source,parentEl=options.parentEl,placeHolder=this.el=_V_.createElement("div",{id:parentEl.id+"_temp_flash"}),objId=player.el.id+"_flash_api",playerOptions=player.options,flashVars=_V_.merge({readyFunction:"_V_.flash.onReady",eventProxyFunction:"_V_.flash.onEvent",errorEventProxyFunction:"_V_.flash.onError",autoplay:playerOptions.autoplay,preload:playerOptions.preload,loop:playerOptions.loop,muted:playerOptions.muted},options.flashVars),params=_V_.merge({wmode:"opaque",bgcolor:"#000000"},options.params),attributes=_V_.merge({id:objId,name:objId,"class":"vjs-tech"},options.attributes);if(source){flashVars.src=encodeURIComponent(_V_.getAbsoluteURL(source.src))}_V_.insertFirst(placeHolder,parentEl);if(options.startTime){this.ready(function(){this.load();this.play();this.currentTime(options.startTime)})}if(options.iFrameMode==true&&!_V_.isFF){var iFrm=_V_.createElement("iframe",{id:objId+"_iframe",name:objId+"_iframe",className:"vjs-tech",scrolling:"no",marginWidth:0,marginHeight:0,frameBorder:0});flashVars.readyFunction="ready";flashVars.eventProxyFunction="events";flashVars.errorEventProxyFunction="errors";_V_.addEvent(iFrm,"load",_V_.proxy(this,function(){var iDoc,objTag,swfLoc,iWin=iFrm.contentWindow,varString="";iDoc=iFrm.contentDocument?iFrm.contentDocument:iFrm.contentWindow.document;iDoc.write(_V_.flash.getEmbedCode(options.swf,flashVars,params,attributes));iWin.player=this.player;iWin.ready=_V_.proxy(this.player,function(currSwf){var el=iDoc.getElementById(currSwf),player=this,tech=player.tech;tech.el=el;_V_.addEvent(el,"click",tech.proxy(tech.onClick));_V_.flash.checkReady(tech)});iWin.events=_V_.proxy(this.player,function(swfID,eventName,other){var player=this;if(player&&player.techName=="flash"){player.triggerEvent(eventName)}});iWin.errors=_V_.proxy(this.player,function(swfID,eventName){_V_.log("Flash Error",eventName)})}));placeHolder.parentNode.replaceChild(iFrm,placeHolder)}else{_V_.flash.embed(options.swf,placeHolder,flashVars,params,attributes)}},destroy:function(){this.el.parentNode.removeChild(this.el)},play:function(){this.el.vjs_play()},pause:function(){this.el.vjs_pause()},src:function(src){src=_V_.getAbsoluteURL(src);this.el.vjs_src(src);if(this.player.autoplay()){var tech=this;setTimeout(function(){tech.play()},0)}},load:function(){this.el.vjs_load()},poster:function(){this.el.vjs_getProperty("poster")},buffered:function(){return _V_.createTimeRange(0,this.el.vjs_getProperty("buffered"))},supportsFullScreen:function(){return false},enterFullScreen:function(){return false}});(function(){var api=_V_.flash.prototype,readWrite="preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted".split(","),readOnly="error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks".split(","),callOnly="load,play,pause".split(",");createSetter=function(attr){var attrUpper=attr.charAt(0).toUpperCase()+attr.slice(1);api["set"+attrUpper]=function(val){return this.el.vjs_setProperty(attr,val)}},createGetter=function(attr){api[attr]=function(){return this.el.vjs_getProperty(attr)}};_V_.each(readWrite,function(attr){createGetter(attr);createSetter(attr)});_V_.each(readOnly,function(attr){createGetter(attr)})})();_V_.flash.isSupported=function(){return _V_.flash.version()[0]>=10};_V_.flash.canPlaySource=function(srcObj){if(srcObj.type in _V_.flash.prototype.support.formats){return"maybe"}};_V_.flash.prototype.support={formats:{"video/flv":"FLV","video/x-flv":"FLV","video/mp4":"MP4","video/m4v":"MP4"},progressEvent:false,timeupdateEvent:false,fullscreenResize:false,parentResize:!(_V_.ua.match("Firefox"))};_V_.flash.onReady=function(currSwf){var el=_V_.el(currSwf);var player=el.player||el.parentNode.player,tech=player.tech;el.player=player;tech.el=el;tech.addEvent("click",tech.onClick);_V_.flash.checkReady(tech)};_V_.flash.checkReady=function(tech){if(tech.el.vjs_getProperty){tech.triggerReady()}else{setTimeout(function(){_V_.flash.checkReady(tech)},50)}};_V_.flash.onEvent=function(swfID,eventName){var player=_V_.el(swfID).player;player.triggerEvent(eventName)};_V_.flash.onError=function(swfID,err){var player=_V_.el(swfID).player;player.triggerEvent("error");_V_.log("Flash Error",err,swfID)};_V_.flash.version=function(){var version="0,0,0";try{version=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version").replace(/\D+/g,",").match(/^,?(.+),?$/)[1]}catch(e){try{if(navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){version=(navigator.plugins["Shockwave Flash 2.0"]||navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g,",").match(/^,?(.+),?$/)[1]}}catch(e){}}return version.split(",")};_V_.flash.embed=function(swf,placeHolder,flashVars,params,attributes){var code=_V_.flash.getEmbedCode(swf,flashVars,params,attributes),obj=_V_.createElement("div",{innerHTML:code}).childNodes[0],par=placeHolder.parentNode;placeHolder.parentNode.replaceChild(obj,placeHolder);if(_V_.isIE()){var newObj=par.childNodes[0];setTimeout(function(){newObj.style.display="block"},1000)}return obj};_V_.flash.getEmbedCode=function(swf,flashVars,params,attributes){var objTag='<object type="application/x-shockwave-flash"',flashVarsString="",paramsString="";attrsString="";if(flashVars){_V_.eachProp(flashVars,function(key,val){flashVarsString+=(key+"="+val+"&")})}params=_V_.merge({movie:swf,flashvars:flashVarsString,allowScriptAccess:"always",allowNetworking:"all"},params);_V_.eachProp(params,function(key,val){paramsString+='<param name="'+key+'" value="'+val+'" />'});attributes=_V_.merge({data:swf,width:"100%",height:"100%"},attributes);_V_.eachProp(attributes,function(key,val){attrsString+=(key+'="'+val+'" ')});return objTag+attrsString+">"+paramsString+"</object>"};_V_.Control=_V_.Component.extend({buildCSSClass:function(){return"vjs-control "+this._super()}});_V_.ControlBar=_V_.Component.extend({options:{loadEvent:"play",components:{playToggle:{},fullscreenToggle:{},currentTimeDisplay:{},timeDivider:{},durationDisplay:{},remainingTimeDisplay:{},progressControl:{},volumeControl:{},muteToggle:{}}},init:function(player,options){this._super(player,options);player.one("play",this.proxy(function(){this.fadeIn();this.player.addEvent("mouseover",this.proxy(this.fadeIn));this.player.addEvent("mouseout",this.proxy(this.fadeOut))}))},createElement:function(){return _V_.createElement("div",{className:"vjs-controls"})},fadeIn:function(){this._super();this.player.triggerEvent("controlsvisible")},fadeOut:function(){this._super();this.player.triggerEvent("controlshidden")},lockShowing:function(){this.el.style.opacity="1"}});_V_.Button=_V_.Control.extend({init:function(player,options){this._super(player,options);this.addEvent("click",this.onClick);this.addEvent("focus",this.onFocus);this.addEvent("blur",this.onBlur)},createElement:function(type,attrs){attrs=_V_.merge({className:this.buildCSSClass(),innerHTML:'<div><span class="vjs-control-text">'+(this.buttonText||"Need Text")+"</span></div>",role:"button",tabIndex:0},attrs);return this._super(type,attrs)},onClick:function(){},onFocus:function(){_V_.addEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))},onKeyPress:function(event){if(event.which==32||event.which==13){event.preventDefault();this.onClick()}},onBlur:function(){_V_.removeEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))}});_V_.PlayButton=_V_.Button.extend({buttonText:"Play",buildCSSClass:function(){return"vjs-play-button "+this._super()},onClick:function(){this.player.play()}});_V_.PauseButton=_V_.Button.extend({buttonText:"Pause",buildCSSClass:function(){return"vjs-pause-button "+this._super()},onClick:function(){this.player.pause()}});_V_.PlayToggle=_V_.Button.extend({buttonText:"Play",init:function(player,options){this._super(player,options);player.addEvent("play",_V_.proxy(this,this.onPlay));player.addEvent("pause",_V_.proxy(this,this.onPause))},buildCSSClass:function(){return"vjs-play-control "+this._super()},onClick:function(){if(this.player.paused()){this.player.play()}else{this.player.pause()}},onPlay:function(){_V_.removeClass(this.el,"vjs-paused");_V_.addClass(this.el,"vjs-playing")},onPause:function(){_V_.removeClass(this.el,"vjs-playing");_V_.addClass(this.el,"vjs-paused")}});_V_.FullscreenToggle=_V_.Button.extend({buttonText:"Fullscreen",buildCSSClass:function(){return"vjs-fullscreen-control "+this._super()},onClick:function(){if(!this.player.isFullScreen){this.player.requestFullScreen()}else{this.player.cancelFullScreen()}}});_V_.BigPlayButton=_V_.Button.extend({init:function(player,options){this._super(player,options);player.addEvent("play",_V_.proxy(this,this.hide));player.addEvent("ended",_V_.proxy(this,this.show))},createElement:function(){return this._super("div",{className:"vjs-big-play-button",innerHTML:"<span></span>"})},onClick:function(){if(this.player.currentTime()){this.player.currentTime(0)}this.player.play()}});_V_.LoadingSpinner=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("canplay",_V_.proxy(this,this.hide));player.addEvent("canplaythrough",_V_.proxy(this,this.hide));player.addEvent("playing",_V_.proxy(this,this.hide));player.addEvent("seeking",_V_.proxy(this,this.show));player.addEvent("seeked",_V_.proxy(this,this.hide));player.addEvent("error",_V_.proxy(this,this.show));player.addEvent("waiting",_V_.proxy(this,this.show))},createElement:function(){var classNameSpinner,innerHtmlSpinner;if(typeof this.player.el.style.WebkitBorderRadius=="string"||typeof this.player.el.style.MozBorderRadius=="string"||typeof this.player.el.style.KhtmlBorderRadius=="string"||typeof this.player.el.style.borderRadius=="string"){classNameSpinner="vjs-loading-spinner";innerHtmlSpinner="<div class='ball1'></div><div class='ball2'></div><div class='ball3'></div><div class='ball4'></div><div class='ball5'></div><div class='ball6'></div><div class='ball7'></div><div class='ball8'></div>"}else{classNameSpinner="vjs-loading-spinner-fallback";innerHtmlSpinner=""}return this._super("div",{className:classNameSpinner,innerHTML:innerHtmlSpinner})}});_V_.CurrentTimeDisplay=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("timeupdate",_V_.proxy(this,this.updateContent))},createElement:function(){var el=this._super("div",{className:"vjs-current-time vjs-time-controls vjs-control"});this.content=_V_.createElement("div",{className:"vjs-current-time-display",innerHTML:"0:00"});el.appendChild(_V_.createElement("div").appendChild(this.content));return el},updateContent:function(){var time=(this.player.scrubbing)?this.player.values.currentTime:this.player.currentTime();this.content.innerHTML=_V_.formatTime(time,this.player.duration())}});_V_.DurationDisplay=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("timeupdate",_V_.proxy(this,this.updateContent))},createElement:function(){var el=this._super("div",{className:"vjs-duration vjs-time-controls vjs-control"});this.content=_V_.createElement("div",{className:"vjs-duration-display",innerHTML:"0:00"});el.appendChild(_V_.createElement("div").appendChild(this.content));return el},updateContent:function(){if(this.player.duration()){this.content.innerHTML=_V_.formatTime(this.player.duration())}}});_V_.TimeDivider=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-time-divider",innerHTML:"<div><span>/</span></div>"})}});_V_.RemainingTimeDisplay=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("timeupdate",_V_.proxy(this,this.updateContent))},createElement:function(){var el=this._super("div",{className:"vjs-remaining-time vjs-time-controls vjs-control"});this.content=_V_.createElement("div",{className:"vjs-remaining-time-display",innerHTML:"-0:00"});el.appendChild(_V_.createElement("div").appendChild(this.content));return el},updateContent:function(){if(this.player.duration()){this.content.innerHTML="-"+_V_.formatTime(this.player.remainingTime())}}});_V_.Slider=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent(this.playerEvent,_V_.proxy(this,this.update));this.addEvent("mousedown",this.onMouseDown);this.addEvent("focus",this.onFocus);this.addEvent("blur",this.onBlur);this.player.addEvent("controlsvisible",this.proxy(this.update));this.update()},createElement:function(type,attrs){attrs=_V_.merge({role:"slider","aria-valuenow":0,"aria-valuemin":0,"aria-valuemax":100,tabIndex:0},attrs);return this._super(type,attrs)},onMouseDown:function(event){event.preventDefault();_V_.blockTextSelection();_V_.addEvent(document,"mousemove",_V_.proxy(this,this.onMouseMove));_V_.addEvent(document,"mouseup",_V_.proxy(this,this.onMouseUp));this.onMouseMove(event)},onMouseUp:function(event){_V_.unblockTextSelection();_V_.removeEvent(document,"mousemove",this.onMouseMove,false);_V_.removeEvent(document,"mouseup",this.onMouseUp,false);this.update()},update:function(){var barProgress,progress=this.getPercent();handle=this.handle,bar=this.bar;if(isNaN(progress)){progress=0}barProgress=progress;if(handle){var box=this.el,boxWidth=box.offsetWidth,handleWidth=handle.el.offsetWidth,handlePercent=(handleWidth)?handleWidth/boxWidth:0,boxAdjustedPercent=1-handlePercent;adjustedProgress=progress*boxAdjustedPercent,barProgress=adjustedProgress+(handlePercent/2);handle.el.style.left=_V_.round(adjustedProgress*100,2)+"%"}bar.el.style.width=_V_.round(barProgress*100,2)+"%"},calculateDistance:function(event){var box=this.el,boxX=_V_.findPosX(box),boxW=box.offsetWidth,handle=this.handle;if(handle){var handleW=handle.el.offsetWidth;boxX=boxX+(handleW/2);boxW=boxW-handleW}return Math.max(0,Math.min(1,(event.pageX-boxX)/boxW))},onFocus:function(event){_V_.addEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))},onKeyPress:function(event){if(event.which==37){event.preventDefault();this.stepBack()}else{if(event.which==39){event.preventDefault();this.stepForward()}}},onBlur:function(event){_V_.removeEvent(document,"keyup",_V_.proxy(this,this.onKeyPress))}});_V_.ProgressControl=_V_.Component.extend({options:{components:{seekBar:{}}},createElement:function(){return this._super("div",{className:"vjs-progress-control vjs-control"})}});_V_.SeekBar=_V_.Slider.extend({options:{components:{loadProgressBar:{},bar:{componentClass:"PlayProgressBar"},handle:{componentClass:"SeekHandle"}}},playerEvent:"timeupdate",init:function(player,options){this._super(player,options)},createElement:function(){return this._super("div",{className:"vjs-progress-holder"})},getPercent:function(){return this.player.currentTime()/this.player.duration()},onMouseDown:function(event){this._super(event);this.player.scrubbing=true;this.videoWasPlaying=!this.player.paused();this.player.pause()},onMouseMove:function(event){var newTime=this.calculateDistance(event)*this.player.duration();if(newTime==this.player.duration()){newTime=newTime-0.1}this.player.currentTime(newTime)},onMouseUp:function(event){this._super(event);this.player.scrubbing=false;if(this.videoWasPlaying){this.player.play()}},stepForward:function(){this.player.currentTime(this.player.currentTime()+1)},stepBack:function(){this.player.currentTime(this.player.currentTime()-1)}});_V_.LoadProgressBar=_V_.Component.extend({init:function(player,options){this._super(player,options);player.addEvent("progress",_V_.proxy(this,this.update))},createElement:function(){return this._super("div",{className:"vjs-load-progress",innerHTML:'<span class="vjs-control-text">Loaded: 0%</span>'})},update:function(){if(this.el.style){this.el.style.width=_V_.round(this.player.bufferedPercent()*100,2)+"%"}}});_V_.PlayProgressBar=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-play-progress",innerHTML:'<span class="vjs-control-text">Progress: 0%</span>'})}});_V_.SeekHandle=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-seek-handle",innerHTML:'<span class="vjs-control-text">00:00</span>'})}});_V_.VolumeControl=_V_.Component.extend({options:{components:{volumeBar:{}}},createElement:function(){return this._super("div",{className:"vjs-volume-control vjs-control"})}});_V_.VolumeBar=_V_.Slider.extend({options:{components:{bar:{componentClass:"VolumeLevel"},handle:{componentClass:"VolumeHandle"}}},playerEvent:"volumechange",createElement:function(){return this._super("div",{className:"vjs-volume-bar"})},onMouseMove:function(event){this.player.volume(this.calculateDistance(event))},getPercent:function(){return this.player.volume()},stepForward:function(){this.player.volume(this.player.volume()+0.1)},stepBack:function(){this.player.volume(this.player.volume()-0.1)}});_V_.VolumeLevel=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-volume-level",innerHTML:'<span class="vjs-control-text"></span>'})}});_V_.VolumeHandle=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-volume-handle",innerHTML:'<span class="vjs-control-text"></span>'})}});_V_.MuteToggle=_V_.Button.extend({init:function(player,options){this._super(player,options);player.addEvent("volumechange",_V_.proxy(this,this.update))},createElement:function(){return this._super("div",{className:"vjs-mute-control vjs-control",innerHTML:'<div><span class="vjs-control-text">Mute</span></div>'})},onClick:function(event){this.player.muted(this.player.muted()?false:true)},update:function(event){var vol=this.player.volume(),level=3;if(vol==0||this.player.muted()){level=0}else{if(vol<0.33){level=1}else{if(vol<0.67){level=2}}}_V_.each.call(this,[0,1,2,3],function(i){_V_.removeClass(this.el,"vjs-vol-"+i)});_V_.addClass(this.el,"vjs-vol-"+level)}});_V_.PosterImage=_V_.Button.extend({init:function(player,options){this._super(player,options);if(!this.player.options.poster){this.hide()}player.addEvent("play",_V_.proxy(this,this.hide))},createElement:function(){return _V_.createElement("img",{className:"vjs-poster",src:this.player.options.poster,tabIndex:-1})},onClick:function(){this.player.play()}});_V_.Menu=_V_.Component.extend({init:function(player,options){this._super(player,options)},addItem:function(component){this.addComponent(component);component.addEvent("click",this.proxy(function(){this.unlockShowing()}))},createElement:function(){return this._super("ul",{className:"vjs-menu"})}});_V_.MenuItem=_V_.Button.extend({init:function(player,options){this._super(player,options);if(options.selected){this.addClass("vjs-selected")}},createElement:function(type,attrs){return this._super("li",_V_.merge({className:"vjs-menu-item",innerHTML:this.options.label},attrs))},onClick:function(){this.selected(true)},selected:function(selected){if(selected){this.addClass("vjs-selected")}else{this.removeClass("vjs-selected")}}});_V_.merge(_V_.Player.prototype,{addTextTracks:function(trackObjects){var tracks=this.textTracks=(this.textTracks)?this.textTracks:[],i=0,j=trackObjects.length,track,Kind;for(;i<j;i++){Kind=_V_.uc(trackObjects[i].kind||"subtitles");track=new _V_[Kind+"Track"](this,trackObjects[i]);tracks.push(track);if(track["default"]){this.ready(_V_.proxy(track,track.show))}}return this},showTextTrack:function(id,disableSameKind){var tracks=this.textTracks,i=0,j=tracks.length,track,showTrack,kind;for(;i<j;i++){track=tracks[i];if(track.id===id){track.show();showTrack=track}else{if(disableSameKind&&track.kind==disableSameKind&&track.mode>0){track.disable()}}}kind=(showTrack)?showTrack.kind:((disableSameKind)?disableSameKind:false);if(kind){this.triggerEvent(kind+"trackchange")}return this}});_V_.Track=_V_.Component.extend({init:function(player,options){this._super(player,options);_V_.merge(this,{id:options.id||("vjs_"+options.kind+"_"+options.language+"_"+_V_.guid++),src:options.src,"default":options["default"],title:options.title,language:options.srclang,label:options.label,cues:[],activeCues:[],readyState:0,mode:0})},createElement:function(){return this._super("div",{className:"vjs-"+this.kind+" vjs-text-track"})},show:function(){this.activate();this.mode=2;this._super()},hide:function(){this.activate();this.mode=1;this._super()},disable:function(){if(this.mode==2){this.hide()}this.deactivate();this.mode=0},activate:function(){if(this.readyState==0){this.load()}if(this.mode==0){this.player.addEvent("timeupdate",this.proxy(this.update,this.id));this.player.addEvent("ended",this.proxy(this.reset,this.id));if(this.kind=="captions"||this.kind=="subtitles"){this.player.textTrackDisplay.addComponent(this)}}},deactivate:function(){this.player.removeEvent("timeupdate",this.proxy(this.update,this.id));this.player.removeEvent("ended",this.proxy(this.reset,this.id));this.reset();this.player.textTrackDisplay.removeComponent(this)},load:function(){if(this.readyState==0){this.readyState=1;_V_.get(this.src,this.proxy(this.parseCues),this.proxy(this.onError))}},onError:function(err){this.error=err;this.readyState=3;this.triggerEvent("error")},parseCues:function(srcContent){var cue,time,text,lines=srcContent.split("\n"),line="",id;for(var i=1,j=lines.length;i<j;i++){line=_V_.trim(lines[i]);if(line){if(line.indexOf("-->")==-1){id=line;line=_V_.trim(lines[++i])}else{id=this.cues.length}cue={id:id,index:this.cues.length};time=line.split(" --> ");cue.startTime=this.parseCueTime(time[0]);cue.endTime=this.parseCueTime(time[1]);text=[];while(lines[++i]&&(line=_V_.trim(lines[i]))){text.push(line)}cue.text=text.join("<br/>");this.cues.push(cue)}}this.readyState=2;this.triggerEvent("loaded")},parseCueTime:function(timeText){var parts=timeText.split(":"),time=0,hours,minutes,other,seconds,ms,flags;if(parts.length==3){hours=parts[0];minutes=parts[1];other=parts[2]}else{hours=0;minutes=parts[0];other=parts[1]}other=other.split(/\s+/);seconds=other.splice(0,1)[0];seconds=seconds.split(/\.|,/);ms=parseFloat(seconds[1]);seconds=seconds[0];time+=parseFloat(hours)*3600;time+=parseFloat(minutes)*60;time+=parseFloat(seconds);if(ms){time+=ms/1000}return time},update:function(){if(this.cues.length>0){var time=this.player.currentTime();if(this.prevChange===undefined||time<this.prevChange||this.nextChange<=time){var cues=this.cues,newNextChange=this.player.duration(),newPrevChange=0,reverse=false,newCues=[],firstActiveIndex,lastActiveIndex,html="",cue,i,j;if(time>=this.nextChange||this.nextChange===undefined){i=(this.firstActiveIndex!==undefined)?this.firstActiveIndex:0}else{reverse=true;i=(this.lastActiveIndex!==undefined)?this.lastActiveIndex:cues.length-1}while(true){cue=cues[i];if(cue.endTime<=time){newPrevChange=Math.max(newPrevChange,cue.endTime);if(cue.active){cue.active=false}}else{if(time<cue.startTime){newNextChange=Math.min(newNextChange,cue.startTime);if(cue.active){cue.active=false}if(!reverse){break}}else{if(reverse){newCues.splice(0,0,cue);if(lastActiveIndex===undefined){lastActiveIndex=i}firstActiveIndex=i}else{newCues.push(cue);if(firstActiveIndex===undefined){firstActiveIndex=i}lastActiveIndex=i}newNextChange=Math.min(newNextChange,cue.endTime);newPrevChange=Math.max(newPrevChange,cue.startTime);cue.active=true}}if(reverse){if(i===0){break}else{i--}}else{if(i===cues.length-1){break}else{i++}}}this.activeCues=newCues;this.nextChange=newNextChange;this.prevChange=newPrevChange;this.firstActiveIndex=firstActiveIndex;this.lastActiveIndex=lastActiveIndex;this.updateDisplay();this.triggerEvent("cuechange")}}},updateDisplay:function(){var cues=this.activeCues,html="",i=0,j=cues.length;for(;i<j;i++){html+="<span class='vjs-tt-cue'>"+cues[i].text+"</span>"}this.el.innerHTML=html},reset:function(){this.nextChange=0;this.prevChange=this.player.duration();this.firstActiveIndex=0;this.lastActiveIndex=0}});_V_.CaptionsTrack=_V_.Track.extend({kind:"captions"});_V_.SubtitlesTrack=_V_.Track.extend({kind:"subtitles"});_V_.ChaptersTrack=_V_.Track.extend({kind:"chapters"});_V_.TextTrackDisplay=_V_.Component.extend({createElement:function(){return this._super("div",{className:"vjs-text-track-display"})}});_V_.TextTrackMenuItem=_V_.MenuItem.extend({init:function(player,options){var track=this.track=options.track;options.label=track.label;options.selected=track["default"];this._super(player,options);this.player.addEvent(track.kind+"trackchange",_V_.proxy(this,this.update))},onClick:function(){this._super();this.player.showTextTrack(this.track.id,this.track.kind)},update:function(){if(this.track.mode==2){this.selected(true)}else{this.selected(false)}}});_V_.OffTextTrackMenuItem=_V_.TextTrackMenuItem.extend({init:function(player,options){options.track={kind:options.kind,player:player,label:"Off"};this._super(player,options)},onClick:function(){this._super();this.player.showTextTrack(this.track.id,this.track.kind)},update:function(){var tracks=this.player.textTracks,i=0,j=tracks.length,track,off=true;for(;i<j;i++){track=tracks[i];if(track.kind==this.track.kind&&track.mode==2){off=false}}if(off){this.selected(true)}else{this.selected(false)}}});_V_.TextTrackButton=_V_.Button.extend({init:function(player,options){this._super(player,options);this.menu=this.createMenu();if(this.items.length===0){this.hide()}},createMenu:function(){var menu=new _V_.Menu(this.player);menu.el.appendChild(_V_.createElement("li",{className:"vjs-menu-title",innerHTML:_V_.uc(this.kind)}));menu.addItem(new _V_.OffTextTrackMenuItem(this.player,{kind:this.kind}));this.items=this.createItems();this.each(this.items,function(item){menu.addItem(item)});this.addComponent(menu);return menu},createItems:function(){var items=[];this.each(this.player.textTracks,function(track){if(track.kind===this.kind){items.push(new _V_.TextTrackMenuItem(this.player,{track:track}))}});return items},buildCSSClass:function(){return this.className+" vjs-menu-button "+this._super()},onFocus:function(){this.menu.lockShowing();_V_.one(this.menu.el.childNodes[this.menu.el.childNodes.length-1],"blur",this.proxy(function(){this.menu.unlockShowing()}))},onBlur:function(){},onClick:function(){this.one("mouseout",this.proxy(function(){this.menu.unlockShowing();this.el.blur()}))}});_V_.CaptionsButton=_V_.TextTrackButton.extend({kind:"captions",buttonText:"Captions",className:"vjs-captions-button"});_V_.SubtitlesButton=_V_.TextTrackButton.extend({kind:"subtitles",buttonText:"Subtitles",className:"vjs-subtitles-button"});_V_.ChaptersButton=_V_.TextTrackButton.extend({kind:"chapters",buttonText:"Chapters",className:"vjs-chapters-button",createItems:function(chaptersTrack){var items=[];this.each(this.player.textTracks,function(track){if(track.kind===this.kind){items.push(new _V_.TextTrackMenuItem(this.player,{track:track}))}});return items},createMenu:function(){var tracks=this.player.textTracks,i=0,j=tracks.length,track,chaptersTrack,items=this.items=[];for(;i<j;i++){track=tracks[i];if(track.kind==this.kind&&track["default"]){if(track.readyState<2){this.chaptersTrack=track;track.addEvent("loaded",this.proxy(this.createMenu));return}else{chaptersTrack=track;break}}}var menu=this.menu=new _V_.Menu(this.player);menu.el.appendChild(_V_.createElement("li",{className:"vjs-menu-title",innerHTML:_V_.uc(this.kind)}));if(chaptersTrack){var cues=chaptersTrack.cues,i=0,j=cues.length,cue,mi;for(;i<j;i++){cue=cues[i];mi=new _V_.ChaptersTrackMenuItem(this.player,{track:chaptersTrack,cue:cue});items.push(mi);menu.addComponent(mi)}}this.addComponent(menu);if(this.items.length>0){this.show()}return menu}});_V_.ChaptersTrackMenuItem=_V_.MenuItem.extend({init:function(player,options){var track=this.track=options.track,cue=this.cue=options.cue,currentTime=player.currentTime();options.label=cue.text;options.selected=(cue.startTime<=currentTime&¤tTime<cue.endTime);this._super(player,options);track.addEvent("cuechange",_V_.proxy(this,this.update))},onClick:function(){this._super();this.player.currentTime(this.cue.startTime);this.update(this.cue.startTime)},update:function(time){var cue=this.cue,currentTime=this.player.currentTime();if(cue.startTime<=currentTime&¤tTime<cue.endTime){this.selected(true)}else{this.selected(false)}}});_V_.merge(_V_.ControlBar.prototype.options.components,{subtitlesButton:{},captionsButton:{},chaptersButton:{}});_V_.autoSetup=function(){var options,vid,player,vids=document.getElementsByTagName("video");if(vids&&vids.length>0){for(var i=0,j=vids.length;i<j;i++){vid=vids[i];if(vid&&vid.getAttribute){if(vid.player===undefined){options=vid.getAttribute("data-setup");if(options!==null){options=JSON.parse(options||"{}");player=_V_(vid,options)}}}else{_V_.autoSetupTimeout(1);break}}}else{if(!_V_.windowLoaded){_V_.autoSetupTimeout(1)}}};_V_.autoSetupTimeout=function(wait){setTimeout(_V_.autoSetup,wait)};_V_.addEvent(window,"load",function(){_V_.windowLoaded=true});_V_.autoSetup();window.VideoJS=window._V_=VideoJS})(window);
\ No newline at end of file diff --git a/mediagoblin.ini b/mediagoblin.ini index f10a452f..4906546a 100644 --- a/mediagoblin.ini +++ b/mediagoblin.ini @@ -1,5 +1,9 @@ # If you want to make changes to this file, first copy it to # mediagoblin_local.ini, then make the changes there. +# +# If you don't see what you need here, have a look at mediagoblin/config_spec.ini +# It defines types and defaults so it’s a good place to look for documentation +# or to find hidden options that we didn’t tell you about. :) [mediagoblin] direct_remote_path = /mgoblin_static/ @@ -7,7 +11,7 @@ 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 +# sql_engine = postgresql:///mediagoblin # set to false to enable sending notices email_debug_mode = true @@ -16,6 +20,8 @@ email_debug_mode = true allow_registration = true ## Uncomment this to turn on video or enable other media types +## You may have to install dependencies, and will have to run ./bin/dbupdate +## See http://docs.mediagoblin.org/siteadmin/media-types.html for details. # media_types = mediagoblin.media_types.image, mediagoblin.media_types.video ## Uncomment this to put some user-overriding templates here @@ -40,3 +46,4 @@ base_url = /mgoblin_media/ # place plugins here---each in their own subsection of [plugins]. see # documentation for details. [plugins] +[[mediagoblin.plugins.geolocation]] diff --git a/mediagoblin/_version.py b/mediagoblin/_version.py index 94ab9f1a..8437be8b 100644 --- a/mediagoblin/_version.py +++ b/mediagoblin/_version.py @@ -23,4 +23,4 @@ # see http://www.python.org/dev/peps/pep-0386/ -__version__ = "0.3.2.dev" +__version__ = "0.4.0.dev" diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py index e6a3eac3..22ca74a3 100644 --- a/mediagoblin/admin/views.py +++ b/mediagoblin/admin/views.py @@ -14,28 +14,30 @@ # You 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, render_404 -from mediagoblin.db.util import DESCENDING -from mediagoblin.decorators import require_active_login +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: - return render_404(request) + raise Forbidden() - processing_entries = request.db.MediaEntry.find( - {'state': u'processing'}).sort('created', DESCENDING) + processing_entries = MediaEntry.query.filter_by(state = u'processing').\ + order_by(MediaEntry.created.desc()) # Get media entries which have failed to process - failed_entries = request.db.MediaEntry.find( - {'state': u'failed'}).sort('created', DESCENDING) + failed_entries = MediaEntry.query.filter_by(state = u'failed').\ + order_by(MediaEntry.created.desc()) - processed_entries = request.db.MediaEntry.find( - {'state': u'processed'}).sort('created', DESCENDING).limit(10) + processed_entries = MediaEntry.query.filter_by(state = u'processed').\ + order_by(MediaEntry.created.desc()).limit(10) # Render to response return render_to_response( diff --git a/mediagoblin/app.py b/mediagoblin/app.py index 3a2d00f0..1984ce77 100644 --- a/mediagoblin/app.py +++ b/mediagoblin/app.py @@ -17,23 +17,26 @@ import os import logging -from mediagoblin.routing import url_map, view_functions, add_route +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, NotFound +from werkzeug.exceptions import HTTPException +from werkzeug.routing import RequestRedirect from mediagoblin import meddleware, __version__ -from mediagoblin.tools import common, translate, template -from mediagoblin.tools.response import render_404 +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_workbench, setup_database, - setup_storage, setup_beaker_cache) -from mediagoblin.tools.pluginapi import PluginManager + 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__) @@ -64,17 +67,25 @@ class MediaGoblinApp(object): # 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.connection, self.db = setup_database() + self.db = setup_database() # Register themes self.theme_registry, self.current_theme = register_themes(app_config) @@ -90,18 +101,11 @@ class MediaGoblinApp(object): self.public_store, self.queue_store = setup_storage() # set up routing - self.url_map = url_map - - for route in PluginManager().get_routes(): - _log.debug('adding plugin route: {0}'.format(route)) - add_route(*route) + self.url_map = get_url_map() # set up staticdirector tool self.staticdirector = get_staticdirector(app_config) - # set up caching - self.cache = setup_beaker_cache() - # 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': @@ -132,10 +136,8 @@ class MediaGoblinApp(object): def call_backend(self, environ, start_response): request = Request(environ) - ## Compatibility webob -> werkzeug + # Compatibility with django, use request.args preferrably request.GET = request.args - request.accept_language = request.accept_languages - request.accept = request.accept_mimetypes ## Routing / controller loading stuff map_adapter = self.url_map.bind_to_environ(request.environ) @@ -158,7 +160,8 @@ class MediaGoblinApp(object): ## Attach utilities to the request object # Do we really want to load this via middleware? Maybe? - request.session = request.environ['beaker.session'] + 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 @@ -185,42 +188,54 @@ class MediaGoblinApp(object): mg_request.setup_user_in_request(request) + request.controller_name = None try: - endpoint, url_values = map_adapter.match() + found_rule, url_values = map_adapter.match(return_rule=True) request.matchdict = url_values - except NotFound as exc: - return render_404(request)(environ, start_response) + except RequestRedirect as response: + # Deal with 301 responses eg due to missing final slash + return response(environ, start_response) except HTTPException as exc: - # Support legacy webob.exc responses - return exc(environ, start_response) - - view_func = view_functions[endpoint] + # Stop and render exception + return render_http_exception( + request, exc, + exc.get_description(environ))(environ, start_response) - _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, unicode) \ - or isinstance(view_func, str): - controller = common.import_component(view_func) - else: - controller = view_func + 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 - for m in self.meddleware: - response = m.process_request(request, controller) - if response is not None: - return response(environ, start_response) + 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 response from the controller - response = controller(request) + # 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)) - # pass the response through the meddleware - for m in self.meddleware[::-1]: - m.process_response(request, response) + session_manager.save_session_to_cookie(request.session, + request, response) return response(environ, start_response) @@ -248,5 +263,6 @@ def paste_app_factory(global_config, **app_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/forms.py b/mediagoblin/auth/forms.py index 0b2bf959..33e1f45c 100644 --- a/mediagoblin/auth/forms.py +++ b/mediagoblin/auth/forms.py @@ -15,54 +15,76 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import wtforms -import re -from mediagoblin.tools.translate import fake_ugettext_passthrough as _ +from mediagoblin.tools.mail import normalize_email +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +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 class RegistrationForm(wtforms.Form): username = wtforms.TextField( _('Username'), [wtforms.validators.Required(), - wtforms.validators.Length(min=3, max=30), - wtforms.validators.Regexp(r'^\w+$')]) + normalize_user_or_email_field(allow_email=False)]) password = wtforms.PasswordField( _('Password'), [wtforms.validators.Required(), - wtforms.validators.Length(min=6, max=30)]) + wtforms.validators.Length(min=5, max=1024)]) email = wtforms.TextField( _('Email address'), [wtforms.validators.Required(), - wtforms.validators.Email()]) + normalize_user_or_email_field(allow_user=False)]) class LoginForm(wtforms.Form): username = wtforms.TextField( - _('Username'), + _('Username or Email'), [wtforms.validators.Required(), - wtforms.validators.Regexp(r'^\w+$')]) + normalize_user_or_email_field()]) password = wtforms.PasswordField( _('Password'), - [wtforms.validators.Required()]) + [wtforms.validators.Required(), + wtforms.validators.Length(min=5, max=1024)]) class ForgotPassForm(wtforms.Form): username = wtforms.TextField( _('Username or email'), - [wtforms.validators.Required()]) - - def validate_username(form, field): - if not (re.match(r'^\w+$', field.data) or - re.match(r'^.+@[^.].*\.[a-z]{2,10}$', field.data, - re.IGNORECASE)): - raise wtforms.ValidationError(_(u'Incorrect input')) + [wtforms.validators.Required(), + normalize_user_or_email_field()]) class ChangePassForm(wtforms.Form): password = wtforms.PasswordField( 'Password', [wtforms.validators.Required(), - wtforms.validators.Length(min=6, max=30)]) + wtforms.validators.Length(min=5, max=1024)]) userid = wtforms.HiddenField( '', [wtforms.validators.Required()]) diff --git a/mediagoblin/auth/lib.py b/mediagoblin/auth/lib.py index c5b046d2..8829995a 100644 --- a/mediagoblin/auth/lib.py +++ b/mediagoblin/auth/lib.py @@ -109,7 +109,7 @@ def send_verification_email(user, request): 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format( host=request.host, uri=request.urlgen('mediagoblin.auth.verify_email'), - userid=unicode(user._id), + userid=unicode(user.id), verification_key=user.verification_key)}) # TODO: There is no error handling in place @@ -144,7 +144,7 @@ def send_fp_verification_email(user, request): 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format( host=request.host, uri=request.urlgen('mediagoblin.auth.verify_forgot_password'), - userid=unicode(user._id), + userid=unicode(user.id), fp_verification_key=user.fp_verification_key)}) # TODO: There is no error handling in place diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py index 5b77c122..dc408911 100644 --- a/mediagoblin/auth/views.py +++ b/mediagoblin/auth/views.py @@ -17,18 +17,15 @@ import uuid import datetime -from webob import exc - -from mediagoblin import messages -from mediagoblin import mg_globals +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.db.util import ObjectId, InvalidId from mediagoblin.auth import lib as auth_lib from mediagoblin.auth import forms as auth_forms from mediagoblin.auth.lib import send_verification_email, \ send_fp_verification_email - +from sqlalchemy import or_ def email_debug_message(request): """ @@ -44,8 +41,10 @@ def email_debug_message(request): def register(request): - """ - Your classic registration view! + """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"]: @@ -59,14 +58,8 @@ def register(request): if request.method == 'POST' and register_form.validate(): # TODO: Make sure the user doesn't exist already - username = unicode(request.form['username'].lower()) - em_user, em_dom = unicode(request.form['email']).split("@", 1) - em_dom = em_dom.lower() - email = em_user + "@" + em_dom - users_with_username = request.db.User.find( - {'username': username}).count() - users_with_email = request.db.User.find( - {'email': email}).count() + 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 @@ -81,16 +74,16 @@ def register(request): if extra_validation_passes: # Create the user - user = request.db.User() - user.username = username - user.email = email + user = User() + user.username = register_form.data['username'] + user.email = register_form.data['email'] user.pw_hash = auth_lib.bcrypt_gen_password_hash( - request.form['password']) + register_form.password.data) user.verification_key = unicode(uuid.uuid4()) - user.save(validate=True) + user.save() # log the user in - request.session['user_id'] = unicode(user._id) + request.session['user_id'] = unicode(user.id) request.session.save() # send verification email @@ -119,21 +112,29 @@ def login(request): login_failed = False - if request.method == 'POST' and login_form.validate(): - user = request.db.User.find_one( - {'username': request.form['username'].lower()}) + if request.method == 'POST': + + username = login_form.data['username'] - if user and user.check_login(request.form['password']): - # set up login in session - request.session['user_id'] = unicode(user._id) - request.session.save() + if login_form.validate(): + user = User.query.filter( + or_( + User.username == username, + User.email == username, - if request.form.get('next'): - return exc.HTTPFound(location=request.form['next']) - else: - return redirect(request, "index") + )).first() - else: + if user and user.check_login(login_form.password.data): + # 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") + + # Some failure during login occured if we are here! # Prevent detecting who's on this system by testing login # attempt timings auth_lib.fake_login_attempt() @@ -166,8 +167,7 @@ def verify_email(request): if not 'userid' in request.GET or not 'token' in request.GET: return render_404(request) - user = request.db.User.find_one( - {'_id': ObjectId(unicode(request.GET['userid']))}) + user = User.query.filter_by(id=request.args['userid']).first() if user and user.verification_key == unicode(request.GET['token']): user.status = u'active' @@ -204,7 +204,7 @@ def resend_activation(request): 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: @@ -212,12 +212,12 @@ def resend_activation(request): 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) @@ -234,61 +234,66 @@ def forgot_password(request): """ Forgot password view - Sends an email with an url to renew forgotten password + 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.GET.get('username')) - - if request.method == 'POST' and fp_form.validate(): - - # '$or' not available till mongodb 1.5.3 - user = request.db.User.find_one( - {'username': request.form['username']}) - if not user: - user = request.db.User.find_one( - {'email': request.form['username']}) - - if user: - if user.email_verified and user.status == 'active': - user.fp_verification_key = unicode(uuid.uuid4()) - user.fp_token_expire = datetime.datetime.now() + \ - datetime.timedelta(days=10) - user.save() - - send_fp_verification_email(user, request) - - messages.add_message( - request, - messages.INFO, - _("An email has been sent with instructions on how to " - "change your password.")) - email_debug_message(request) - - else: - # special case... we can't send the email because the - # username is inactive / hasn't verified their email - messages.add_message( - request, - messages.WARNING, - _("Could not send password recovery email as " - "your username is inactive or your account's " - "email address has not been verified.")) - - return redirect( - request, 'mediagoblin.user_pages.user_home', - user=user.username) - return redirect(request, 'mediagoblin.auth.login') - else: - messages.add_message( - request, - messages.WARNING, - _("Couldn't find someone with that username or email.")) + 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') - return render_to_response( - request, - 'mediagoblin/auth/forgot_password.html', - {'fp_form': fp_form}) + 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): @@ -305,11 +310,9 @@ def verify_forgot_password(request): formdata_userid = formdata['vars']['userid'] formdata_vars = formdata['vars'] - # check if it's a valid Id - try: - user = request.db.User.find_one( - {'_id': ObjectId(unicode(formdata_userid))}) - except InvalidId: + # 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 @@ -322,7 +325,7 @@ def verify_forgot_password(request): if request.method == 'POST' and cp_form.validate(): user.pw_hash = auth_lib.bcrypt_gen_password_hash( - request.form['password']) + cp_form.password.data) user.fp_verification_key = None user.fp_token_expire = None user.save() @@ -338,7 +341,7 @@ def verify_forgot_password(request): 'mediagoblin/auth/change_fp.html', {'cp_form': cp_form}) - # in case there is a valid id but no user whit that id in the db + # 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) diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini index 17df2819..2af4adb2 100644 --- a/mediagoblin/config_spec.ini +++ b/mediagoblin/config_spec.ini @@ -9,14 +9,14 @@ source_link = string(default="https://gitorious.org/mediagoblin/mediagoblin") media_types = string_list(default=list("mediagoblin.media_types.image")) # database stuff -db_host = string() -db_name = string(default="mediagoblin") -db_port = integer() 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/") @@ -32,7 +32,10 @@ email_smtp_pass = string(default=None) allow_registration = boolean(default=True) # tag parsing -tags_max_length = integer(default=50) +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) @@ -58,7 +61,7 @@ csrf_cookie_name = string(default='mediagoblin_csrftoken') push_urls = string_list(default=list()) exif_visible = boolean(default=False) -geolocation_map_visible = boolean(default=False) +original_date_visible = boolean(default=False) # Theming stuff theme_install_dir = string(default="%(here)s/user_dev/themes/") @@ -89,6 +92,12 @@ max_height = integer(default=640) 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) @@ -100,22 +109,28 @@ 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 qualiy +# 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) -[beaker.cache] -type = string(default="file") -data_dir = string(default="%(here)s/user_dev/beaker/cache/data") -lock_dir = string(default="%(here)s/user_dev/beaker/cache/lock") +[media_type:mediagoblin.media_types.pdf] +pdf_js = boolean(default=False) [celery] diff --git a/mediagoblin/db/__init__.py b/mediagoblin/db/__init__.py index d149f62a..27ca4b06 100644 --- a/mediagoblin/db/__init__.py +++ b/mediagoblin/db/__init__.py @@ -18,18 +18,6 @@ Database Abstraction/Wrapper Layer ================================== - **NOTE from Chris Webber:** I asked Elrond to explain why he put - ASCENDING and DESCENDING in db/util.py when we could just import from - pymongo. Read beow for why, but note that nobody is actually doing - this and there's no proof that we'll ever support more than - MongoDB... it would be a huge amount of work to do so. - - If you really want to prove that possible, jump on IRC and talk to - us about making such a branch. In the meanwhile, it doesn't hurt to - have things as they are... if it ever makes it hard for us to - actually do things, we might revisit or remove this. But for more - information, read below. - This submodule is for most of the db specific stuff. There are two main ideas here: diff --git a/mediagoblin/db/sql/base.py b/mediagoblin/db/base.py index 838080b0..699a503a 100644 --- a/mediagoblin/db/sql/base.py +++ b/mediagoblin/db/base.py @@ -17,47 +17,19 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker, object_session -from sqlalchemy.orm.query import Query -from sqlalchemy.sql.expression import desc -from mediagoblin.db.sql.fake import DESCENDING - -def _get_query_model(query): - cols = query.column_descriptions - assert len(cols) == 1, "These functions work only on simple queries" - return cols[0]["type"] - - -class GMGQuery(Query): - def sort(self, key, direction): - key_col = getattr(_get_query_model(self), key) - if direction is DESCENDING: - key_col = desc(key_col) - return self.order_by(key_col) - - def skip(self, amount): - return self.offset(amount) - - -Session = scoped_session(sessionmaker(query_cls=GMGQuery)) - - -def _fix_query_dict(query_dict): - if '_id' in query_dict: - query_dict['id'] = query_dict.pop('_id') +Session = scoped_session(sessionmaker()) class GMGTableBase(object): query = Session.query_property() @classmethod - def find(cls, query_dict={}): - _fix_query_dict(query_dict) + def find(cls, query_dict): return cls.query.filter_by(**query_dict) @classmethod - def find_one(cls, query_dict={}): - _fix_query_dict(query_dict) + def find_one(cls, query_dict): return cls.query.filter_by(**query_dict).first() @classmethod @@ -71,19 +43,20 @@ class GMGTableBase(object): # The key *has* to exist on sql. return getattr(self, key) - def save(self, validate=True): - assert validate + def save(self): sess = object_session(self) if sess is None: sess = Session() sess.add(self) sess.commit() - def delete(self): + 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) - sess.commit() + if commit: + sess.commit() Base = declarative_base(cls=GMGTableBase) diff --git a/mediagoblin/db/sql/extratypes.py b/mediagoblin/db/extratypes.py index f2304af0..f2304af0 100644 --- a/mediagoblin/db/sql/extratypes.py +++ b/mediagoblin/db/extratypes.py diff --git a/mediagoblin/db/sql/util.py b/mediagoblin/db/migration_tools.py index 74b5d73e..c0c7e998 100644 --- a/mediagoblin/db/sql/util.py +++ b/mediagoblin/db/migration_tools.py @@ -14,12 +14,11 @@ # You 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 -from mediagoblin.db.sql.base import Session -from mediagoblin.db.sql.models import MediaEntry, Tag, MediaTag, Collection - from mediagoblin.tools.common import simple_printer +from sqlalchemy import Table + +class TableAlreadyExists(Exception): + pass class MigrationManager(object): @@ -39,7 +38,7 @@ class MigrationManager(object): - migration_registry: where we should find all migrations to run """ - self.name = name + self.name = unicode(name) self.models = models self.session = session self.migration_registry = migration_registry @@ -47,7 +46,7 @@ class MigrationManager(object): self.printer = printer # For convenience - from mediagoblin.db.sql.models import MigrationData + from mediagoblin.db.models import MigrationData self.migration_model = MigrationData self.migration_table = MigrationData.__table__ @@ -132,7 +131,10 @@ class MigrationManager(object): # 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? - assert not model.__table__.exists(self.session.bind) + 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, @@ -217,9 +219,9 @@ class MigrationManager(object): 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') - self.set_current_migration() return u'migrated' # Otherwise return None. Well it would do this anyway, but @@ -261,67 +263,14 @@ def assure_migrations_table_setup(db): """ Make sure the migrations table is set up in the database. """ - from mediagoblin.db.sql.models import MigrationData + from mediagoblin.db.models import MigrationData if not MigrationData.__table__.exists(db.bind): MigrationData.metadata.create_all( db.bind, tables=[MigrationData.__table__]) -########################## -# 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(dummy_db, uploader_id, slug, ignore_m_id): - filt = (MediaEntry.uploader == uploader_id) \ - & (MediaEntry.slug == slug) - if ignore_m_id is not None: - filt = filt & (MediaEntry.id != ignore_m_id) - does_exist = Session.query(MediaEntry.id).filter(filt).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(): - 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) - - Session.commit() - - -def check_collection_slug_used(dummy_db, 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.sql.open import setup_connection_and_db_from_config - - conn,db = setup_connection_and_db_from_config({'sql_engine':'sqlite:///mediagoblin.db'}) - - clean_orphan_tags() +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 index f6a15a12..388bac89 100644 --- a/mediagoblin/db/mixin.py +++ b/mediagoblin/db/mixin.py @@ -27,8 +27,13 @@ These functions now live here and get "mixed in" into the real objects. """ +import uuid + +from werkzeug.utils import cached_property + from mediagoblin import mg_globals from mediagoblin.auth import lib as auth_lib +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 @@ -47,22 +52,83 @@ class UserMixin(object): return cleaned_markdown_conversion(self.bio) -class MediaEntryMixin(object): +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 - self.slug = slugify(self.title) - - duplicate = check_media_slug_used(mg_globals.database, - self.uploader, self.slug, self.id) - - if duplicate: - if self.id is not None: - self.slug = u"%s-%s" % (self.id, self.slug) - else: - self.slug = None + return check_media_slug_used(self.uploader, slug, self.id) @property def description_html(self): @@ -72,37 +138,44 @@ class MediaEntryMixin(object): """ return cleaned_markdown_conversion(self.description) - def get_display_media(self, media_map, - fetch_order=common.DISPLAY_IMAGE_FETCHING_ORDER): - """ - Find the best media for display. + def get_display_media(self): + """Find the best media for display. - Args: - - media_map: a dict like - {u'image_size': [u'dir1', u'dir2', u'image.jpg']} - - fetch_order: the order we should try fetching images in + We try checking self.media_manager.fetching_order if it exists to + pull down the order. Returns: - (media_size, media_path) + (media_size, media_path) + or, if not found, None. + """ - media_sizes = media_map.keys() + fetch_order = self.media_manager.media_fetch_order - for media_size in common.DISPLAY_IMAGE_FETCHING_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_map[media_size] + return media_size, self.media_files[media_size] def main_mediafile(self): pass @property def slug_or_id(self): - return (self.slug or self._id) + 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'. + Use a slug if we have one, else use our 'id'. """ uploader = self.get_uploader @@ -112,6 +185,38 @@ class MediaEntryMixin(object): 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 @@ -121,7 +226,7 @@ class MediaEntryMixin(object): def get_license_data(self): """Return license dict for requested license""" - return licenses.SUPPORTED_LICENSES[self.license or ""] + return licenses.get_license_by_url(self.license or "") def exif_display_iter(self): from mediagoblin.tools.exif import USEFUL_TAGS @@ -145,22 +250,13 @@ class MediaCommentMixin(object): return cleaned_markdown_conversion(self.content) -class CollectionMixin(object): - def generate_slug(self): +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 - self.slug = slugify(self.title) - - duplicate = check_collection_slug_used(mg_globals.database, - self.creator, self.slug, self.id) - - if duplicate: - if self.id is not None: - self.slug = u"%s-%s" % (self.id, self.slug) - else: - self.slug = None + return check_collection_slug_used(self.creator, slug, self.id) @property def description_html(self): @@ -172,13 +268,13 @@ class CollectionMixin(object): @property def slug_or_id(self): - return (self.slug or self._id) + 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'. + Use a slug if we have one, else use our 'id'. """ creator = self.get_creator diff --git a/mediagoblin/db/sql/models.py b/mediagoblin/db/models.py index b48c1fbe..2b925983 100644 --- a/mediagoblin/db/sql/models.py +++ b/mediagoblin/db/models.py @@ -18,9 +18,8 @@ TODO: indexes on foreignkeys, where useful. """ - +import logging import datetime -import sys from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \ Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \ @@ -31,10 +30,11 @@ from sqlalchemy.sql.expression import desc from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.util import memoized_property -from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded -from mediagoblin.db.sql.base import Base, DictReadAttrProxy +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.db.sql.base import Session +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 @@ -43,17 +43,7 @@ from mediagoblin.db.sql.base import Session # this import-based meddling... from migrate import changeset - -class SimpleFieldAlias(object): - """An alias for any field""" - def __init__(self, fieldname): - self.fieldname = fieldname - - def __get__(self, instance, cls): - return getattr(instance, self.fieldname) - - def __set__(self, instance, val): - setattr(instance, self.fieldname, val) +_log = logging.getLogger(__name__) class User(Base, UserMixin): @@ -65,6 +55,10 @@ class User(Base, UserMixin): 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) @@ -73,6 +67,7 @@ class User(Base, UserMixin): # 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) @@ -83,8 +78,6 @@ class User(Base, UserMixin): ## TODO # plugin data would be in a separate model - _id = SimpleFieldAlias("id") - def __repr__(self): return '<{0} #{1} {2} {3} "{4}">'.format( self.__class__.__name__, @@ -93,6 +86,25 @@ class User(Base, UserMixin): '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): """ @@ -146,7 +158,7 @@ class MediaEntry(Base, MediaEntryMixin): ) tags_helper = relationship("MediaTag", - cascade="all, delete-orphan" + 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"]) @@ -158,17 +170,13 @@ class MediaEntry(Base, MediaEntryMixin): collections = association_proxy("collections_helper", "in_collection") ## TODO - # media_data # fail_error - _id = SimpleFieldAlias("id") - def get_comments(self, ascending=False): order_col = MediaComment.created if not ascending: order_col = desc(order_col) - return MediaComment.query.filter_by( - media_entry=self.id).order_by(order_col) + return self.all_comments.order_by(order_col) def url_to_prev(self, urlgen): """get the next 'newer' entry by this user""" @@ -190,40 +198,31 @@ class MediaEntry(Base, MediaEntryMixin): if media is not None: return media.url_for_self(urlgen) - #@memoized_property @property def media_data(self): - session = Session() - - return session.query(self.media_data_table).filter_by( - media_entry=self.id).first() + 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 """ - session = Session() - - media_data = session.query(self.media_data_table).filter_by( - media_entry=self.id).first() + media_data = self.media_data - # 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 + # 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_table(self): - # TODO: memoize this - models_module = self.media_type + '.models' - __import__(models_module) - return sys.modules[models_module].DATA_MODEL + def media_data_ref(self): + return import_component(self.media_type + '.models:BACKREF_NAME') def __repr__(self): safe_title = self.title.encode('ascii', 'replace') @@ -233,6 +232,35 @@ class MediaEntry(Base, MediaEntryMixin): 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): """ @@ -357,34 +385,58 @@ class MediaComment(Base, MediaCommentMixin): created = Column(DateTime, nullable=False, default=datetime.datetime.now) content = Column(UnicodeText, nullable=False) - get_author = relationship(User) + # 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")) - _id = SimpleFieldAlias("id") + # 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) + 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) - get_creator = relationship(User) + # 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) - _id = SimpleFieldAlias("id") - class CollectionItem(Base, CollectionItemMixin): __tablename__ = "core__collection_items" @@ -396,11 +448,14 @@ class CollectionItem(Base, CollectionItemMixin): note = Column(UnicodeText, nullable=True) added = Column(DateTime, nullable=False, default=datetime.datetime.now) position = Column(Integer) - in_collection = relationship("Collection") - get_media_entry = relationship(MediaEntry) + # Cascade: CollectionItems are owned by their Collection. So do the full thing. + in_collection = relationship(Collection, + backref=backref( + "collection_items", + cascade="all, delete-orphan")) - _id = SimpleFieldAlias("id") + get_media_entry = relationship(MediaEntry) __table_args__ = ( UniqueConstraint('collection', 'media_entry'), diff --git a/mediagoblin/db/sql/models_v0.py b/mediagoblin/db/models_v0.py index 06f87d28..ec51a1f5 100644 --- a/mediagoblin/db/sql/models_v0.py +++ b/mediagoblin/db/models_v0.py @@ -31,9 +31,8 @@ from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.util import memoized_property -from mediagoblin.db.sql.extratypes import PathTupleWithSlashes, JSONEncoded -from mediagoblin.db.sql.base import GMGTableBase -from mediagoblin.db.sql.base import Session +from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded +from mediagoblin.db.base import GMGTableBase, Session Base_v0 = declarative_base(cls=GMGTableBase) diff --git a/mediagoblin/db/mongo/indexes.py b/mediagoblin/db/mongo/indexes.py deleted file mode 100644 index a63c24ae..00000000 --- a/mediagoblin/db/mongo/indexes.py +++ /dev/null @@ -1,146 +0,0 @@ -# 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/>. - -""" -Indexes for the local database. - -To add new indexes ------------------- - -Indexes are recorded in the following format: - -ACTIVE_INDEXES = { - 'collection_name': { - 'identifier': { # key identifier used for possibly deprecating later - 'index': [index_foo_goes_here]}} - -... and anything else being parameters to the create_index function -(including unique=True, etc) - -Current indexes must be registered in ACTIVE_INDEXES... deprecated -indexes should be marked in DEPRECATED_INDEXES. - -Remember, ordering of compound indexes MATTERS. Read below for more. - -REQUIRED READING: - - http://kylebanker.com/blog/2010/09/21/the-joy-of-mongodb-indexes/ - - - http://www.mongodb.org/display/DOCS/Indexes - - http://www.mongodb.org/display/DOCS/Indexing+Advice+and+FAQ - - -To remove deprecated indexes ----------------------------- - -Removing deprecated indexes is the same, just move the index into the -deprecated indexes mapping. - -DEPRECATED_INDEXES = { - 'collection_name': { - 'deprecated_index_identifier1': { - 'index': [index_foo_goes_here]}} - -... etc. - -If an index has been deprecated that identifier should NEVER BE USED -AGAIN. Eg, if you previously had 'awesomepants_unique', you shouldn't -use 'awesomepants_unique' again, you should create a totally new name -or at worst use 'awesomepants_unique2'. -""" - -from pymongo import ASCENDING, DESCENDING - - -################ -# Active indexes -################ -ACTIVE_INDEXES = {} - -# MediaEntry indexes -# ------------------ - -MEDIAENTRY_INDEXES = { - 'uploader_slug_unique': { - # Matching an object to an uploader + slug. - # MediaEntries are unique on these two combined, eg: - # /u/${myuser}/m/${myslugname}/ - 'index': [('uploader', ASCENDING), - ('slug', ASCENDING)], - 'unique': True}, - - 'created': { - # A global index for all media entries created, in descending - # order. This is used for the site's frontpage. - 'index': [('created', DESCENDING)]}, - - 'uploader_created': { - # Indexing on uploaders and when media entries are created. - # Used for showing a user gallery, etc. - 'index': [('uploader', ASCENDING), - ('created', DESCENDING)]}, - - 'state_uploader_tags_created': { - # Indexing on processed?, media uploader, associated tags, and - # timestamp Used for showing media items matching a tag - # search, most recent first. - 'index': [('state', ASCENDING), - ('uploader', ASCENDING), - ('tags.slug', DESCENDING), - ('created', DESCENDING)]}, - - 'state_tags_created': { - # Indexing on processed?, media tags, and timestamp (across all users) - # This is used for a front page tag search. - 'index': [('state', ASCENDING), - ('tags.slug', DESCENDING), - ('created', DESCENDING)]}} - - -ACTIVE_INDEXES['media_entries'] = MEDIAENTRY_INDEXES - - -# User indexes -# ------------ - -USER_INDEXES = { - 'username_unique': { - # Index usernames, and make sure they're unique. - # ... I guess we might need to adjust this once we're federated :) - 'index': 'username', - 'unique': True}, - 'created': { - # All most recently created users - 'index': 'created'}} - - -ACTIVE_INDEXES['users'] = USER_INDEXES - - -# MediaComment indexes - -MEDIA_COMMENT_INDEXES = { - 'mediaentry_created': { - 'index': [('media_entry', ASCENDING), - ('created', DESCENDING)]}} - -ACTIVE_INDEXES['media_comments'] = MEDIA_COMMENT_INDEXES - - -#################### -# Deprecated indexes -#################### - -DEPRECATED_INDEXES = {} diff --git a/mediagoblin/db/mongo/migrations.py b/mediagoblin/db/mongo/migrations.py deleted file mode 100644 index 569dec88..00000000 --- a/mediagoblin/db/mongo/migrations.py +++ /dev/null @@ -1,208 +0,0 @@ -# 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.mongo.util import RegisterMigration -from mediagoblin.tools.text import cleaned_markdown_conversion - - -def add_table_field(db, table_name, field_name, default_value): - """ - Add a new field to the table/collection named table_name. - The field will have the name field_name and the value default_value - """ - db[table_name].update( - {field_name: {'$exists': False}}, - {'$set': {field_name: default_value}}, - multi=True) - - -def drop_table_field(db, table_name, field_name): - """ - Drop an old field from a table/collection - """ - db[table_name].update( - {field_name: {'$exists': True}}, - {'$unset': {field_name: 1}}, - multi=True) - - -# Please see mediagoblin/tests/test_migrations.py for some examples of -# basic migrations. - - -@RegisterMigration(1) -def user_add_bio_html(database): - """ - Users now have richtext bios via Markdown, reflect appropriately. - """ - collection = database['users'] - - target = collection.find( - {'bio_html': {'$exists': False}}) - - for document in target: - document['bio_html'] = cleaned_markdown_conversion( - document['bio']) - collection.save(document) - - -@RegisterMigration(2) -def mediaentry_mediafiles_main_to_original(database): - """ - Rename "main" media file to "original". - """ - collection = database['media_entries'] - target = collection.find( - {'media_files.main': {'$exists': True}}) - - for document in target: - original = document['media_files'].pop('main') - document['media_files']['original'] = original - - collection.save(document) - - -@RegisterMigration(3) -def mediaentry_remove_thumbnail_file(database): - """ - Use media_files['thumb'] instead of media_entries['thumbnail_file'] - """ - database['media_entries'].update( - {'thumbnail_file': {'$exists': True}}, - {'$unset': {'thumbnail_file': 1}}, - multi=True) - - -@RegisterMigration(4) -def mediaentry_add_queued_task_id(database): - """ - Add the 'queued_task_id' field for entries that don't have it. - """ - add_table_field(database, 'media_entries', 'queued_task_id', None) - - -@RegisterMigration(5) -def mediaentry_add_fail_error_and_metadata(database): - """ - Add 'fail_error' and 'fail_metadata' fields to media entries - """ - add_table_field(database, 'media_entries', 'fail_error', None) - add_table_field(database, 'media_entries', 'fail_metadata', {}) - - -@RegisterMigration(6) -def user_add_forgot_password_token_and_expires(database): - """ - Add token and expiration fields to help recover forgotten passwords - """ - add_table_field(database, 'users', 'fp_verification_key', None) - add_table_field(database, 'users', 'fp_token_expire', None) - - -@RegisterMigration(7) -def media_type_image_to_multimedia_type_image(database): - database['media_entries'].update( - {'media_type': 'image'}, - {'$set': {'media_type': 'mediagoblin.media_types.image'}}, - multi=True) - - -@RegisterMigration(8) -def mediaentry_add_license(database): - """ - Add the 'license' field for entries that don't have it. - """ - add_table_field(database, 'media_entries', 'license', None) - - -@RegisterMigration(9) -def remove_calculated_html(database): - """ - Drop pre-rendered html again and calculate things - on the fly (and cache): - - User.bio_html - - MediaEntry.description_html - - MediaComment.content_html - """ - drop_table_field(database, 'users', 'bio_html') - drop_table_field(database, 'media_entries', 'description_html') - drop_table_field(database, 'media_comments', 'content_html') - - -@RegisterMigration(10) -def convert_video_media_data(database): - """ - Move media_data["video"] directly into media_data - """ - collection = database['media_entries'] - target = collection.find( - {'media_data.video': {'$exists': True}}) - - for document in target: - assert len(document['media_data']) == 1 - document['media_data'] = document['media_data']['video'] - collection.save(document) - - -@RegisterMigration(11) -def convert_gps_media_data(database): - """ - Move media_data["gps"]["*"] to media_data["gps_*"]. - In preparation for media_data.gps_* - """ - collection = database['media_entries'] - target = collection.find( - {'media_data.gps': {'$exists': True}}) - - for document in target: - for key, value in document['media_data']['gps'].iteritems(): - document['media_data']['gps_' + key] = value - del document['media_data']['gps'] - collection.save(document) - - -@RegisterMigration(12) -def convert_exif_media_data(database): - """ - Move media_data["exif"]["clean"] to media_data["exif_all"]. - Drop media_data["exif"]["useful"] - In preparation for media_data.exif_all - """ - collection = database['media_entries'] - target = collection.find( - {'media_data.exif.clean': {'$exists': True}}) - - for document in target: - media_data = document['media_data'] - - exif_all = media_data['exif'].pop('clean') - if len(exif_all): - media_data['exif_all'] = exif_all - - del media_data['exif']['useful'] - - assert len(media_data['exif']) == 0 - del media_data['exif'] - - collection.save(document) - - -@RegisterMigration(13) -def user_add_wants_comment_notification(database): - """ - Add wants_comment_notification to user model - """ - add_table_field(database, 'users', 'wants_comment_notification', True) diff --git a/mediagoblin/db/mongo/models.py b/mediagoblin/db/mongo/models.py deleted file mode 100644 index 3f1363d5..00000000 --- a/mediagoblin/db/mongo/models.py +++ /dev/null @@ -1,310 +0,0 @@ -# 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 mongokit import Document - -from mediagoblin.db.mongo import migrations -from mediagoblin.db.mongo.util import ASCENDING, DESCENDING, ObjectId -from mediagoblin.tools.pagination import Pagination -from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin - - -class MongoPK(object): - """An alias for the _id primary key""" - def __get__(self, instance, cls): - return instance['_id'] - def __set__(self, instance, val): - instance['_id'] = val - def __delete__(self, instance): - del instance['_id'] - - -################### -# Custom validators -################### - -######## -# Models -######## - - -class User(Document, UserMixin): - """ - A user of MediaGoblin. - - Structure: - - username: The username of this user, should be unique to this instance. - - email: Email address of this user - - created: When the user was created - - plugin_data: a mapping of extra plugin information for this User. - Nothing uses this yet as we don't have plugins, but someday we - might... :) - - pw_hash: Hashed version of user's password. - - email_verified: Whether or not the user has verified their email or not. - Most parts of the site are disabled for users who haven't yet. - - status: whether or not the user is active, etc. Currently only has two - values, 'needs_email_verification' or 'active'. (In the future, maybe - we'll change this to a boolean with a key of 'active' and have a - separate field for a reason the user's been disabled if that's - appropriate... email_verified is already separate, after all.) - - wants_comment_notification: The user has selected that they want to be - notified when comments are posted on their media. - - verification_key: If the user is awaiting email verification, the user - will have to provide this key (which will be encoded in the presented - URL) in order to confirm their email as active. - - is_admin: Whether or not this user is an administrator or not. - - url: this user's personal webpage/website, if appropriate. - - bio: biography of this user (plaintext, in markdown) - """ - __collection__ = 'users' - use_dot_notation = True - - structure = { - 'username': unicode, - 'email': unicode, - 'created': datetime.datetime, - 'plugin_data': dict, # plugins can dump stuff here. - 'pw_hash': unicode, - 'email_verified': bool, - 'status': unicode, - 'wants_comment_notification': bool, - 'verification_key': unicode, - 'is_admin': bool, - 'url': unicode, - 'bio': unicode, # May contain markdown - 'fp_verification_key': unicode, # forgotten password verification key - 'fp_token_expire': datetime.datetime, - } - - required_fields = ['username', 'created', 'pw_hash', 'email'] - - default_values = { - 'created': datetime.datetime.utcnow, - 'email_verified': False, - 'wants_comment_notification': True, - 'status': u'needs_email_verification', - 'is_admin': False} - - id = MongoPK() - - -class MediaEntry(Document, MediaEntryMixin): - """ - Record of a piece of media. - - Structure: - - uploader: A reference to a User who uploaded this. - - - title: Title of this work - - - slug: A normalized "slug" which can be used as part of a URL to retrieve - this work, such as 'my-works-name-in-slug-form' may be viewable by - 'http://mg.example.org/u/username/m/my-works-name-in-slug-form/' - Note that since URLs are constructed this way, slugs must be unique - per-uploader. (An index is provided to enforce that but code should be - written on the python side to ensure this as well.) - - - created: Date and time of when this piece of work was uploaded. - - - description: Uploader-set description of this work. This can be marked - up with MarkDown for slight fanciness (links, boldness, italics, - paragraphs...) - - - media_type: What type of media is this? Currently we only support - 'image' ;) - - - media_data: Extra information that's media-format-dependent. - For example, images might contain some EXIF data that's not appropriate - to other formats. You might store it like: - - mediaentry.media_data['exif'] = { - 'manufacturer': 'CASIO', - 'model': 'QV-4000', - 'exposure_time': .659} - - Alternately for video you might store: - - # play length in seconds - mediaentry.media_data['play_length'] = 340 - - ... so what's appropriate here really depends on the media type. - - - plugin_data: a mapping of extra plugin information for this User. - Nothing uses this yet as we don't have plugins, but someday we - might... :) - - - tags: A list of tags. Each tag is stored as a dictionary that has a key - for the actual name and the normalized name-as-slug, so ultimately this - looks like: - [{'name': 'Gully Gardens', - 'slug': 'gully-gardens'}, - {'name': 'Castle Adventure Time?!", - 'slug': 'castle-adventure-time'}] - - - state: What's the state of this file? Active, inactive, disabled, etc... - But really for now there are only two states: - "unprocessed": uploaded but needs to go through processing for display - "processed": processed and able to be displayed - - - license: URI for media's license. - - - queued_media_file: storage interface style filepath describing a file - queued for processing. This is stored in the mg_globals.queue_store - storage system. - - - queued_task_id: celery task id. Use this to fetch the task state. - - - media_files: Files relevant to this that have actually been processed - and are available for various types of display. Stored like: - {'thumb': ['dir1', 'dir2', 'pic.png'} - - - attachment_files: A list of "attachment" files, ones that aren't - critical to this piece of media but may be usefully relevant to people - viewing the work. (currently unused.) - - - fail_error: path to the exception raised - - fail_metadata: - """ - __collection__ = 'media_entries' - use_dot_notation = True - - structure = { - 'uploader': ObjectId, - 'title': unicode, - 'slug': unicode, - 'created': datetime.datetime, - 'description': unicode, # May contain markdown/up - 'media_type': unicode, - 'media_data': dict, # extra data relevant to this media_type - 'plugin_data': dict, # plugins can dump stuff here. - 'tags': [dict], - 'state': unicode, - 'license': unicode, - - # For now let's assume there can only be one main file queued - # at a time - 'queued_media_file': [unicode], - 'queued_task_id': unicode, - - # A dictionary of logical names to filepaths - 'media_files': dict, - - # The following should be lists of lists, in appropriate file - # record form - 'attachment_files': list, - - # If things go badly in processing things, we'll store that - # data here - 'fail_error': unicode, - 'fail_metadata': dict} - - required_fields = [ - 'uploader', 'created', 'media_type', 'slug'] - - default_values = { - 'created': datetime.datetime.utcnow, - 'state': u'unprocessed'} - - id = MongoPK() - - def media_data_init(self, **kwargs): - self.media_data.update(kwargs) - - def get_comments(self, ascending=False): - if ascending: - order = ASCENDING - else: - order = DESCENDING - - return self.db.MediaComment.find({ - 'media_entry': self._id}).sort('created', order) - - def url_to_prev(self, urlgen): - """ - Provide a url to the previous entry from this user, if there is one - """ - cursor = self.db.MediaEntry.find({'_id': {"$gt": self._id}, - 'uploader': self.uploader, - 'state': 'processed'}).sort( - '_id', ASCENDING).limit(1) - for media in cursor: - return media.url_for_self(urlgen) - - def url_to_next(self, urlgen): - """ - Provide a url to the next entry from this user, if there is one - """ - cursor = self.db.MediaEntry.find({'_id': {"$lt": self._id}, - 'uploader': self.uploader, - 'state': 'processed'}).sort( - '_id', DESCENDING).limit(1) - - for media in cursor: - return media.url_for_self(urlgen) - - @property - def get_uploader(self): - return self.db.User.find_one({'_id': self.uploader}) - - -class MediaComment(Document, MediaCommentMixin): - """ - A comment on a MediaEntry. - - Structure: - - media_entry: The media entry this comment is attached to - - author: user who posted this comment - - created: when the comment was created - - content: plaintext (but markdown'able) version of the comment's content. - """ - - __collection__ = 'media_comments' - use_dot_notation = True - - structure = { - 'media_entry': ObjectId, - 'author': ObjectId, - 'created': datetime.datetime, - 'content': unicode, - } - - required_fields = [ - 'media_entry', 'author', 'created', 'content'] - - default_values = { - 'created': datetime.datetime.utcnow} - - def media_entry(self): - return self.db.MediaEntry.find_one({'_id': self['media_entry']}) - - @property - def get_author(self): - return self.db.User.find_one({'_id': self['author']}) - - -REGISTER_MODELS = [ - MediaEntry, - User, - MediaComment] - - -def register_models(connection): - """ - Register all models in REGISTER_MODELS with this connection. - """ - connection.register(REGISTER_MODELS) diff --git a/mediagoblin/db/mongo/open.py b/mediagoblin/db/mongo/open.py deleted file mode 100644 index c4f37b42..00000000 --- a/mediagoblin/db/mongo/open.py +++ /dev/null @@ -1,82 +0,0 @@ -# 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 pymongo -import mongokit -from paste.deploy.converters import asint -from mediagoblin.db.mongo import models -from mediagoblin.db.mongo.util import MigrationManager - - -def load_models(app_config): - pass - - -def connect_database_from_config(app_config, use_pymongo=False): - """ - Connect to the main database, take config from app_config - - Optionally use pymongo instead of mongokit for the connection. - """ - port = app_config.get('db_port') - if port: - port = asint(port) - - if use_pymongo: - connection = pymongo.Connection( - app_config.get('db_host'), port) - else: - connection = mongokit.Connection( - app_config.get('db_host'), port) - return connection - - -def setup_connection_and_db_from_config(app_config, use_pymongo=False): - """ - Setup connection and database from config. - - Optionally use pymongo instead of mongokit. - """ - connection = connect_database_from_config(app_config, use_pymongo) - database_path = app_config['db_name'] - db = connection[database_path] - - if not use_pymongo: - models.register_models(connection) - - return (connection, db) - - -def check_db_migrations_current(db): - # This MUST be imported so as to set up the appropriate migrations! - from mediagoblin.db.mongo import migrations - - # Init the migration number if necessary - migration_manager = MigrationManager(db) - migration_manager.install_migration_version_if_missing() - - # Tiny hack to warn user if our migration is out of date - if not migration_manager.database_at_latest_migration(): - db_migration_num = migration_manager.database_current_migration() - latest_migration_num = migration_manager.latest_migration() - if db_migration_num < latest_migration_num: - print ( - "*WARNING:* Your migrations are out of date, " - "maybe run ./bin/gmg migrate?") - elif db_migration_num > latest_migration_num: - print ( - "*WARNING:* Your migrations are out of date... " - "in fact they appear to be from the future?!") diff --git a/mediagoblin/db/mongo/util.py b/mediagoblin/db/mongo/util.py deleted file mode 100644 index f61ae6be..00000000 --- a/mediagoblin/db/mongo/util.py +++ /dev/null @@ -1,318 +0,0 @@ -# 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/>. - -""" -Utilities for database operations. - -Some note on migration and indexing tools: - -We store information about what the state of the database is in the -'mediagoblin' document of the 'app_metadata' collection. Keys in that -document relevant to here: - - - 'migration_number': The integer representing the current state of - the migrations -""" - -import copy - -# Imports that other modules might use -from pymongo import ASCENDING, DESCENDING -from pymongo.errors import InvalidId -from mongokit import ObjectId - -from mediagoblin.db.mongo.indexes import ACTIVE_INDEXES, DEPRECATED_INDEXES - - -################ -# Indexing tools -################ - - -def add_new_indexes(database, active_indexes=ACTIVE_INDEXES): - """ - Add any new indexes to the database. - - Args: - - database: pymongo or mongokit database instance. - - active_indexes: indexes to possibly add in the pattern of: - {'collection_name': { - 'identifier': { - 'index': [index_foo_goes_here], - 'unique': True}} - where 'index' is the index to add and all other options are - arguments for collection.create_index. - - Returns: - A list of indexes added in form ('collection', 'index_name') - """ - indexes_added = [] - - for collection_name, indexes in active_indexes.iteritems(): - collection = database[collection_name] - collection_indexes = collection.index_information().keys() - - for index_name, index_data in indexes.iteritems(): - if not index_name in collection_indexes: - # Get a copy actually so we don't modify the actual - # structure - index_data = copy.copy(index_data) - index = index_data.pop('index') - collection.create_index( - index, name=index_name, **index_data) - - indexes_added.append((collection_name, index_name)) - - return indexes_added - - -def remove_deprecated_indexes(database, deprecated_indexes=DEPRECATED_INDEXES): - """ - Remove any deprecated indexes from the database. - - Args: - - database: pymongo or mongokit database instance. - - deprecated_indexes: the indexes to deprecate in the pattern of: - {'collection_name': { - 'identifier': { - 'index': [index_foo_goes_here], - 'unique': True}} - - (... although we really only need the 'identifier' here, as the - rest of the information isn't used in this case. But it's kept - around so we can remember what it was) - - Returns: - A list of indexes removed in form ('collection', 'index_name') - """ - indexes_removed = [] - - for collection_name, indexes in deprecated_indexes.iteritems(): - collection = database[collection_name] - collection_indexes = collection.index_information().keys() - - for index_name, index_data in indexes.iteritems(): - if index_name in collection_indexes: - collection.drop_index(index_name) - - indexes_removed.append((collection_name, index_name)) - - return indexes_removed - - -################# -# Migration tools -################# - -# The default migration registry... -# -# Don't set this yourself! RegisterMigration will automatically fill -# this with stuff via decorating methods in migrations.py - -class MissingCurrentMigration(Exception): - pass - - -MIGRATIONS = {} - - -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=MIGRATIONS): - 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 - - -class MigrationManager(object): - """ - Migration handling tool. - - Takes information about a database, lets you update the database - to the latest migrations, etc. - """ - def __init__(self, database, migration_registry=MIGRATIONS): - """ - Args: - - database: database we're going to migrate - - migration_registry: where we should find all migrations to - run - """ - self.database = database - self.migration_registry = migration_registry - self._sorted_migrations = None - - def _ensure_current_migration_record(self): - """ - If there isn't a database[u'app_metadata'] mediagoblin entry - with the 'current_migration', throw an error. - """ - if self.database_current_migration() is None: - raise MissingCurrentMigration( - "Tried to call function which requires " - "'current_migration' set in database") - - @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 - - 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 - - def set_current_migration(self, migration_number): - """ - Set the migration in the database to migration_number - """ - # Add the mediagoblin migration if necessary - self.database[u'app_metadata'].update( - {u'_id': u'mediagoblin'}, - {u'$set': {u'current_migration': migration_number}}, - upsert=True) - - def install_migration_version_if_missing(self): - """ - Sets the migration to the latest version if no migration - version at all is set. - """ - mgoblin_metadata = self.database[u'app_metadata'].find_one( - {u'_id': u'mediagoblin'}) - if not mgoblin_metadata: - latest_migration = self.latest_migration() - self.set_current_migration(latest_migration) - - def database_current_migration(self): - """ - Return the current migration in the database. - """ - mgoblin_metadata = self.database[u'app_metadata'].find_one( - {u'_id': u'mediagoblin'}) - if not mgoblin_metadata: - return None - else: - return mgoblin_metadata[u'current_migration'] - - def database_at_latest_migration(self): - """ - See if the database is at the latest migration. - Returns a boolean. - """ - current_migration = self.database_current_migration() - return current_migration == self.latest_migration() - - def migrations_to_run(self): - """ - Get a list of migrations to run still, if any. - - Note that calling this will set your migration version to the - latest version if it isn't installed to anything yet! - """ - self._ensure_current_migration_record() - - 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 migrate_new(self, pre_callback=None, post_callback=None): - """ - Run all migrations. - - Includes two optional args: - - pre_callback: if called, this is a callback on something to - run pre-migration. Takes (migration_number, migration_func) - as arguments - - pre_callback: if called, this is a callback on something to - run post-migration. Takes (migration_number, migration_func) - as arguments - """ - # If we aren't set to any version number, presume we're at the - # latest (which means we'll do nothing here...) - self.install_migration_version_if_missing() - - for migration_number, migration_func in self.migrations_to_run(): - if pre_callback: - pre_callback(migration_number, migration_func) - migration_func(self.database) - self.set_current_migration(migration_number) - if post_callback: - post_callback(migration_number, migration_func) - - -########################## -# Random utility functions -########################## - - -def atomic_update(table, query_dict, update_values): - table.collection.update( - query_dict, - {"$set": update_values}) - - -def check_media_slug_used(db, uploader_id, slug, ignore_m_id): - query_dict = {'uploader': uploader_id, 'slug': slug} - if ignore_m_id is not None: - query_dict['_id'] = {'$ne': ignore_m_id} - existing_user_slug_entries = db.MediaEntry.find( - query_dict).count() - return existing_user_slug_entries - - -def media_entries_for_tag_slug(db, tag_slug): - return db.MediaEntry.find( - {u'state': u'processed', - u'tags.slug': tag_slug}) diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py index f4c38511..0b1679fb 100644 --- a/mediagoblin/db/open.py +++ b/mediagoblin/db/open.py @@ -14,16 +14,88 @@ # You 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 mediagoblin.db.sql_switch import use_sql -except ImportError: - use_sql = False - -if use_sql: - from mediagoblin.db.sql.open import \ - setup_connection_and_db_from_config, check_db_migrations_current, \ - load_models -else: - from mediagoblin.db.mongo.open import \ - setup_connection_and_db_from_config, check_db_migrations_current, \ - load_models + +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/sql/convert.py b/mediagoblin/db/sql/convert.py deleted file mode 100644 index ac64cf8d..00000000 --- a/mediagoblin/db/sql/convert.py +++ /dev/null @@ -1,282 +0,0 @@ -# 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 copy import copy -from itertools import chain, imap - -from mediagoblin.init import setup_global_and_app_config - -from mediagoblin.db.sql.base import Session -from mediagoblin.db.sql.models_v0 import Base_v0 -from mediagoblin.db.sql.models_v0 import (User, MediaEntry, MediaComment, - Tag, MediaTag, MediaFile, MediaAttachmentFile, MigrationData, - ImageData, VideoData, AsciiData, AudioData) -from mediagoblin.db.sql.open import setup_connection_and_db_from_config as \ - sql_connect -from mediagoblin.db.mongo.open import setup_connection_and_db_from_config as \ - mongo_connect - - -obj_id_table = dict() - - -def add_obj_ids(entry, new_entry): - global obj_id_table - print "\t%r -> SQL id %r" % (entry._id, new_entry.id) - obj_id_table[entry._id] = new_entry.id - - -def copy_attrs(entry, new_entry, attr_list): - for a in attr_list: - val = entry[a] - setattr(new_entry, a, val) - - -def copy_reference_attr(entry, new_entry, ref_attr): - val = entry[ref_attr] - val = obj_id_table[val] - setattr(new_entry, ref_attr, val) - - -def convert_users(mk_db): - session = Session() - - for entry in mk_db.User.find().sort('created'): - print entry.username - - new_entry = User() - copy_attrs(entry, new_entry, - ('username', 'email', 'created', 'pw_hash', 'email_verified', - 'status', 'verification_key', 'is_admin', 'url', - 'bio', - 'fp_verification_key', 'fp_token_expire',)) - # new_entry.fp_verification_expire = entry.fp_token_expire - - session.add(new_entry) - session.flush() - add_obj_ids(entry, new_entry) - - session.commit() - session.close() - - -def convert_media_entries(mk_db): - session = Session() - - for entry in mk_db.MediaEntry.find().sort('created'): - print repr(entry.title) - - new_entry = MediaEntry() - copy_attrs(entry, new_entry, - ('title', 'slug', 'created', - 'description', - 'media_type', 'state', 'license', - 'fail_error', 'fail_metadata', - 'queued_task_id',)) - copy_reference_attr(entry, new_entry, "uploader") - - session.add(new_entry) - session.flush() - add_obj_ids(entry, new_entry) - - for key, value in entry.media_files.iteritems(): - new_file = MediaFile(name=key, file_path=value) - new_file.media_entry = new_entry.id - Session.add(new_file) - - for attachment in entry.attachment_files: - new_attach = MediaAttachmentFile( - name=attachment["name"], - filepath=attachment["filepath"], - created=attachment["created"] - ) - new_attach.media_entry = new_entry.id - Session.add(new_attach) - - session.commit() - session.close() - - -def convert_image(mk_db): - session = Session() - - for media in mk_db.MediaEntry.find( - {'media_type': 'mediagoblin.media_types.image'}).sort('created'): - media_data = copy(media.media_data) - - if len(media_data): - media_data_row = ImageData(**media_data) - media_data_row.media_entry = obj_id_table[media['_id']] - session.add(media_data_row) - - session.commit() - session.close() - - -def convert_video(mk_db): - session = Session() - - for media in mk_db.MediaEntry.find( - {'media_type': 'mediagoblin.media_types.video'}).sort('created'): - media_data_row = VideoData(**media.media_data) - media_data_row.media_entry = obj_id_table[media['_id']] - session.add(media_data_row) - - session.commit() - session.close() - - -def convert_media_tags(mk_db): - session = Session() - session.autoflush = False - - for media in mk_db.MediaEntry.find().sort('created'): - print repr(media.title) - - for otag in media.tags: - print " ", repr((otag["slug"], otag["name"])) - - nslug = session.query(Tag).filter_by(slug=otag["slug"]).first() - print " ", repr(nslug) - if nslug is None: - nslug = Tag(slug=otag["slug"]) - session.add(nslug) - session.flush() - print " ", repr(nslug), nslug.id - - ntag = MediaTag() - ntag.tag = nslug.id - ntag.name = otag["name"] - ntag.media_entry = obj_id_table[media._id] - session.add(ntag) - - session.commit() - session.close() - - -def convert_media_comments(mk_db): - session = Session() - - for entry in mk_db.MediaComment.find().sort('created'): - print repr(entry.content) - - new_entry = MediaComment() - copy_attrs(entry, new_entry, - ('created', - 'content',)) - - try: - copy_reference_attr(entry, new_entry, "media_entry") - copy_reference_attr(entry, new_entry, "author") - except KeyError as e: - print('KeyError in convert_media_comments(): {0}'.format(e)) - else: - session.add(new_entry) - session.flush() - add_obj_ids(entry, new_entry) - - session.commit() - session.close() - - -media_types_tables = ( - ("mediagoblin.media_types.image", (ImageData,)), - ("mediagoblin.media_types.video", (VideoData,)), - ("mediagoblin.media_types.ascii", (AsciiData,)), - ("mediagoblin.media_types.audio", (AudioData,)), - ) - - -def convert_add_migration_versions(dummy_sql_db): - session = Session() - - for name in chain(("__main__",), - imap(lambda e: e[0], media_types_tables)): - print "\tAdding %s" % (name,) - m = MigrationData(name=unicode(name), version=0) - session.add(m) - - session.commit() - session.close() - - -def cleanup_sql_tables(sql_db): - for mt, table_list in media_types_tables: - session = Session() - - count = session.query(MediaEntry.media_type). \ - filter_by(media_type=unicode(mt)).count() - print " %s: %d entries" % (mt, count) - - if count == 0: - print "\tAnalyzing tables" - for tab in table_list: - cnt2 = session.query(tab).count() - print "\t %s: %d entries" % (tab.__tablename__, cnt2) - assert cnt2 == 0 - - print "\tRemoving migration info" - mi = session.query(MigrationData).filter_by(name=unicode(mt)).one() - session.delete(mi) - session.commit() - session.close() - - print "\tDropping tables" - tables = [model.__table__ for model in table_list] - Base_v0.metadata.drop_all(sql_db.engine, tables=tables) - - session.close() - - -def print_header(title): - print "\n=== %s ===" % (title,) - - -convert_call_list = ( - ("Converting Users", convert_users), - ("Converting Media Entries", convert_media_entries), - ("Converting Media Data for Images", convert_image), - ("Cnnverting Media Data for Videos", convert_video), - ("Converting Tags for Media", convert_media_tags), - ("Converting Media Comments", convert_media_comments), - ) - -sql_call_list = ( - ("Filling Migration Tables", convert_add_migration_versions), - ("Analyzing/Cleaning SQL Data", cleanup_sql_tables), - ) - -def run_conversion(config_name): - global_config, app_config = setup_global_and_app_config(config_name) - - sql_conn, sql_db = sql_connect(app_config) - mk_conn, mk_db = mongo_connect(app_config) - - Base_v0.metadata.create_all(sql_db.engine) - - for title, func in convert_call_list: - print_header(title) - func(mk_db) - Session.remove() - - for title, func in sql_call_list: - print_header(title) - func(sql_db) - Session.remove() - - -if __name__ == '__main__': - run_conversion("mediagoblin.ini") diff --git a/mediagoblin/db/sql/migrations.py b/mediagoblin/db/sql/migrations.py deleted file mode 100644 index 1d822cd9..00000000 --- a/mediagoblin/db/sql/migrations.py +++ /dev/null @@ -1,118 +0,0 @@ -# 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 sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger, - Integer, Unicode, UnicodeText, DateTime, ForeignKey) - -from mediagoblin.db.sql.util import RegisterMigration -from mediagoblin.db.sql.models import MediaEntry, Collection, User, \ - ProcessingMetaData - -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 = Table('core__media_entries', metadata, autoload=True, - autoload_with=db_conn.bind) - - col = Column('transcoding_progress', SmallInteger) - col.create(media_entry) - db_conn.commit() - - -@RegisterMigration(4, MIGRATIONS) -def add_collection_tables(db_conn): - metadata = MetaData(bind=db_conn.bind) - - collection = Table('core__collections', metadata, - Column('id', Integer, primary_key=True), - Column('title', Unicode, nullable=False), - Column('slug', Unicode), - Column('created', DateTime, nullable=False, default=datetime.datetime.now, index=True), - Column('description', UnicodeText), - Column('creator', Integer, ForeignKey(User.id), nullable=False), - Column('items', Integer, default=0)) - - collection_item = Table('core__collection_items', metadata, - Column('id', Integer, primary_key=True), - Column('media_entry', Integer, ForeignKey(MediaEntry.id), nullable=False, index=True), - Column('collection', Integer, ForeignKey(Collection.id), nullable=False), - Column('note', UnicodeText, nullable=True), - Column('added', DateTime, nullable=False, default=datetime.datetime.now), - Column('position', Integer)) - - collection.create() - collection_item.create() - - db_conn.commit() - - -@RegisterMigration(5, MIGRATIONS) -def add_mediaentry_collected(db_conn): - metadata = MetaData(bind=db_conn.bind) - - media_entry = Table('core__media_entries', metadata, autoload=True, - autoload_with=db_conn.bind) - - col = Column('collected', Integer, default=0) - col.create(media_entry) - db_conn.commit() - - -@RegisterMigration(6, MIGRATIONS) -def create_processing_metadata_table(db): - metadata = MetaData(bind=db.bind) - - metadata_table = Table('core__processing_metadata', metadata, - Column('id', Integer, primary_key=True), - Column('media_entry_id', Integer, ForeignKey(MediaEntry.id), - nullable=False, index=True), - Column('callback_url', Unicode)) - - metadata_table.create() - db.commit() diff --git a/mediagoblin/db/sql/open.py b/mediagoblin/db/sql/open.py deleted file mode 100644 index 9db21c56..00000000 --- a/mediagoblin/db/sql/open.py +++ /dev/null @@ -1,78 +0,0 @@ -# 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 -import logging - -from mediagoblin.db.sql.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.sql.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 setup_connection_and_db_from_config(app_config): - engine = create_engine(app_config['sql_engine']) - # logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) - Session.configure(bind=engine) - - return "dummy conn", DatabaseMaster(engine) - - -def check_db_migrations_current(db): - pass diff --git a/mediagoblin/db/sql_switch.py b/mediagoblin/db/sql_switch.py deleted file mode 100644 index 571adbdb..00000000 --- a/mediagoblin/db/sql_switch.py +++ /dev/null @@ -1 +0,0 @@ -use_sql = True diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py index a8c8c92b..6ffec44d 100644 --- a/mediagoblin/db/util.py +++ b/mediagoblin/db/util.py @@ -14,16 +14,63 @@ # You 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 mediagoblin.db.sql_switch import use_sql -except ImportError: - use_sql = False - -if use_sql: - from mediagoblin.db.sql.fake import ObjectId, InvalidId, DESCENDING - from mediagoblin.db.sql.util import atomic_update, check_media_slug_used, \ - media_entries_for_tag_slug, check_collection_slug_used -else: - from mediagoblin.db.mongo.util import \ - ObjectId, InvalidId, DESCENDING, atomic_update, \ - check_media_slug_used, media_entries_for_tag_slug +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 index 9be9d4cc..f3535fcf 100644 --- a/mediagoblin/decorators.py +++ b/mediagoblin/decorators.py @@ -17,11 +17,11 @@ from functools import wraps from urlparse import urljoin -from urllib import urlencode +from werkzeug.exceptions import Forbidden, NotFound +from werkzeug.urls import url_quote -from webob import exc - -from mediagoblin.db.util import ObjectId, InvalidId +from mediagoblin import mg_globals as mgg +from mediagoblin.db.models import MediaEntry, User from mediagoblin.tools.response import redirect, render_404 @@ -32,26 +32,37 @@ def require_active_login(controller): @wraps(controller) def new_controller_func(request, *args, **kwargs): if request.user and \ - request.user.get('status') == u'needs_email_verification': + 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.get('status') != u'active': + elif not request.user or request.user.status != u'active': next_url = urljoin( request.urlgen('mediagoblin.auth.login', qualified=True), request.url) - return exc.HTTPFound( - location='?'.join([ - request.urlgen('mediagoblin.auth.login'), - urlencode({ - 'next': next_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): """ @@ -59,11 +70,10 @@ def user_may_delete_media(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - uploader_id = request.db.MediaEntry.find_one( - {'_id': ObjectId(request.matchdict['media'])}).uploader + uploader_id = kwargs['media'].uploader if not (request.user.is_admin or - request.user._id == uploader_id): - return exc.HTTPForbidden() + request.user.id == uploader_id): + raise Forbidden() return controller(request, *args, **kwargs) @@ -79,8 +89,8 @@ def user_may_alter_collection(controller): creator_id = request.db.User.find_one( {'username': request.matchdict['user']}).id if not (request.user.is_admin or - request.user._id == creator_id): - return exc.HTTPForbidden() + request.user.id == creator_id): + raise Forbidden() return controller(request, *args, **kwargs) @@ -111,29 +121,34 @@ def get_user_media_entry(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - user = request.db.User.find_one( - {'username': request.matchdict['user']}) - + user = User.query.filter_by(username=request.matchdict['user']).first() if not user: - return render_404(request) - media = request.db.MediaEntry.find_one( - {'slug': request.matchdict['media'], - 'state': u'processed', - 'uploader': user._id}) + raise NotFound() - # no media via slug? Grab it via ObjectId - if not media: + 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 = request.db.MediaEntry.find_one( - {'_id': ObjectId(request.matchdict['media']), - 'state': u'processed', - 'uploader': user._id}) - except InvalidId: - return render_404(request) + 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() - # Still no media? Okay, 404. - if not media: - return render_404(request) + if not media: + # Didn't find anything? Okay, 404. + raise NotFound() return controller(request, media=media, *args, **kwargs) @@ -154,7 +169,7 @@ def get_user_collection(controller): collection = request.db.Collection.find_one( {'slug': request.matchdict['collection'], - 'creator': user._id}) + 'creator': user.id}) # Still no collection? Okay, 404. if not collection: @@ -177,12 +192,8 @@ def get_user_collection_item(controller): if not user: return render_404(request) - collection = request.db.Collection.find_one( - {'slug': request.matchdict['collection'], - 'creator': user._id}) - collection_item = request.db.CollectionItem.find_one( - {'_id': request.matchdict['collection_item'] }) + {'id': request.matchdict['collection_item'] }) # Still no collection item? Okay, 404. if not collection_item: @@ -199,17 +210,28 @@ def get_media_entry_by_id(controller): """ @wraps(controller) def wrapper(request, *args, **kwargs): - try: - media = request.db.MediaEntry.find_one( - {'_id': ObjectId(request.matchdict['media']), - 'state': u'processed'}) - except InvalidId: - return render_404(request) - + 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/forms.py b/mediagoblin/edit/forms.py index 856852b6..ef270237 100644 --- a/mediagoblin/edit/forms.py +++ b/mediagoblin/edit/forms.py @@ -17,7 +17,7 @@ import wtforms from mediagoblin.tools.text import tag_length_validator, TOO_LONG_TAG_WARNING -from mediagoblin.tools.translate import fake_ugettext_passthrough as _ +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ from mediagoblin.tools.licenses import licenses_as_choices class EditForm(wtforms.Form): @@ -65,11 +65,21 @@ class EditAccountForm(wtforms.Form): "Enter your old password to prove you own this account.")) new_password = wtforms.PasswordField( _('New password'), - [wtforms.validators.Length(min=6, max=30)], + [ + wtforms.validators.Optional(), + wtforms.validators.Length(min=6, max=30) + ], id="password") + 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( - _(''), - description=_("Email me when others comment on my media")) + label=_("Email me when others comment on my media")) class EditAttachmentsForm(wtforms.Form): diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py index b4715134..aab537a0 100644 --- a/mediagoblin/edit/lib.py +++ b/mediagoblin/edit/lib.py @@ -17,7 +17,7 @@ def may_edit_media(request, media): """Check, if the request's user may edit the media details""" - if media.uploader == request.user._id: + if media.uploader == request.user.id: return True if request.user.is_admin: return True diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py index 28b73d1e..035a766f 100644 --- a/mediagoblin/edit/routing.py +++ b/mediagoblin/edit/routing.py @@ -14,9 +14,13 @@ # You 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.routing import add_route +from mediagoblin.tools.routing import add_route -add_route('mediagoblin.edit.profile', '/edit/profile/', +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') diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py index 2d42ff0b..bfcf65b5 100644 --- a/mediagoblin/edit/views.py +++ b/mediagoblin/edit/views.py @@ -14,10 +14,9 @@ # You 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 webob import exc -from cgi import FieldStorage from datetime import datetime +from werkzeug.exceptions import Forbidden from werkzeug.utils import secure_filename from mediagoblin import messages @@ -26,22 +25,25 @@ 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, get_user_media_entry, \ - user_may_alter_collection, get_user_collection -from mediagoblin.tools.response import render_to_response, redirect +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_user_media_entry +@get_media_entry_by_id @require_active_login def edit_media(request, media): if not may_edit_media(request, media): - return exc.HTTPForbidden() + raise Forbidden("User may not edit this media") defaults = dict( title=media.title, @@ -57,29 +59,26 @@ def edit_media(request, media): if request.method == 'POST' and form.validate(): # Make sure there isn't already a MediaEntry with such a slug # and userid. - slug_used = check_media_slug_used(request.db, media.uploader, - request.form['slug'], media.id) + 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 = unicode(request.form['title']) - media.description = unicode(request.form.get('description')) + media.title = form.title.data + media.description = form.description.data media.tags = convert_to_tag_list_of_dicts( - request.form.get('tags')) - - media.license = unicode(request.form.get('license', '')) or None - - media.slug = unicode(request.form['slug']) + form.tags.data) + media.license = unicode(form.license.data) or None + media.slug = slug media.save() - return exc.HTTPFound( - location=media.url_for_self(request.urlgen)) + return redirect_obj(request, media) if request.user.is_admin \ - and media.uploader != request.user._id \ + and media.uploader != request.user.id \ and request.method != 'POST': messages.add_message( request, messages.WARNING, @@ -99,7 +98,7 @@ UNSAFE_MIMETYPES = [ 'text/svg+xml'] -@get_user_media_entry +@get_media_entry_by_id @require_active_login def edit_attachments(request, media): if mg_globals.app_config['allow_attachments']: @@ -130,7 +129,7 @@ def edit_attachments(request, media): attachment_public_filepath \ = mg_globals.public_store.get_unique_filepath( - ['media_entries', unicode(media._id), 'attachment', + ['media_entries', unicode(media.id), 'attachment', public_filename]) attachment_public_file = mg_globals.public_store.get_file( @@ -143,7 +142,7 @@ def edit_attachments(request, media): request.files['attachment_file'].stream.close() media.attachment_files.append(dict( - name=request.form['attachment_name'] \ + name=form.attachment_name.data \ or request.files['attachment_file'].filename, filepath=attachment_public_filepath, created=datetime.utcnow(), @@ -153,42 +152,50 @@ def edit_attachments(request, media): messages.add_message( request, messages.SUCCESS, - "You added the attachment %s!" \ - % (request.form['attachment_name'] + _("You added the attachment %s!") \ + % (form.attachment_name.data or request.files['attachment_file'].filename)) - return exc.HTTPFound( - location=media.url_for_self(request.urlgen)) + return redirect(request, + location=media.url_for_self(request.urlgen)) return render_to_response( request, 'mediagoblin/edit/attachments.html', {'media': media, 'form': form}) else: - return exc.HTTPForbidden() + 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 -def edit_profile(request): - # admins may edit any user profile given a username in the querystring - edit_username = request.GET.get('username') - if request.user.is_admin and request.user.username != edit_username: - user = request.db.User.find_one({'username': edit_username}) +@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.")) - else: - user = request.user + + user = url_user form = forms.EditProfileForm(request.form, - url=user.get('url'), - bio=user.get('bio')) + url=user.url, + bio=user.bio) if request.method == 'POST' and form.validate(): - user.url = unicode(request.form['url']) - user.bio = unicode(request.form['bio']) + user.url = unicode(form.url.data) + user.bio = unicode(form.bio.data) user.save() @@ -210,45 +217,42 @@ def edit_profile(request): def edit_account(request): user = request.user form = forms.EditAccountForm(request.form, - wants_comment_notification=user.get('wants_comment_notification')) + wants_comment_notification=user.wants_comment_notification, + license_preference=user.license_preference) if request.method == 'POST': form_validated = form.validate() - #if the user has not filled in the new or old password fields - if not form.new_password.data and not form.old_password.data: - if form.wants_comment_notification.validate(form): - user.wants_comment_notification = \ - form.wants_comment_notification.data - user.save() - messages.add_message(request, - messages.SUCCESS, - _("Account settings saved")) - return redirect(request, - 'mediagoblin.user_pages.user_home', - user=user.username) - - #so the user has filled in one or both of the password fields - else: - if form_validated: - password_matches = auth_lib.bcrypt_check_password( - form.old_password.data, - user.pw_hash) - if password_matches: - #the entire form validates and the password matches - user.pw_hash = auth_lib.bcrypt_gen_password_hash( - form.new_password.data) - user.wants_comment_notification = \ - form.wants_comment_notification.data - user.save() - messages.add_message(request, - messages.SUCCESS, - _("Account settings saved")) - return redirect(request, - 'mediagoblin.user_pages.user_home', - user=user.username) - else: - form.old_password.errors.append(_('Wrong password')) + if form_validated and \ + form.wants_comment_notification.validate(form): + user.wants_comment_notification = \ + form.wants_comment_notification.data + + if form_validated and \ + form.new_password.data or form.old_password.data: + password_matches = auth_lib.bcrypt_check_password( + form.old_password.data, + user.pw_hash) + if password_matches: + #the entire form validates and the password matches + user.pw_hash = auth_lib.bcrypt_gen_password_hash( + form.new_password.data) + else: + form.old_password.errors.append(_('Wrong password')) + + 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, @@ -258,6 +262,37 @@ def edit_account(request): @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): @@ -273,35 +308,33 @@ def edit_collection(request, collection): 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(request.db, collection.creator, - request.form['slug'], collection.id) + 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':request.form['title']}) + '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"!') % \ - request.form['title']) + 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(request.form['title']) - collection.description = unicode(request.form.get('description')) - collection.slug = unicode(request.form['slug']) + collection.title = unicode(form.title.data) + collection.description = unicode(form.description.data) + collection.slug = unicode(form.slug.data) collection.save() - return redirect(request, "mediagoblin.user_pages.user_collection", - user=collection.get_creator.username, - collection=collection.slug) + return redirect_obj(request, collection) if request.user.is_admin \ - and collection.creator != request.user._id \ + and collection.creator != request.user.id \ and request.method != 'POST': messages.add_message( request, messages.WARNING, diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py index e965dd57..6aed4f6c 100644 --- a/mediagoblin/gmg_commands/__init__.py +++ b/mediagoblin/gmg_commands/__init__.py @@ -25,11 +25,6 @@ SUBCOMMAND_MAP = { 'setup': 'mediagoblin.gmg_commands.shell:shell_parser_setup', 'func': 'mediagoblin.gmg_commands.shell:shell', 'help': 'Run a shell with some tools pre-setup'}, - 'migrate': { - 'setup': 'mediagoblin.gmg_commands.migrate:migrate_parser_setup', - 'func': 'mediagoblin.gmg_commands.migrate:migrate', - 'help': ('Migrate your Mongo database. ' - '[DEPRECATED!] use convert_mongo_to_sql and dbupdate.')}, 'adduser': { 'setup': 'mediagoblin.gmg_commands.users:adduser_parser_setup', 'func': 'mediagoblin.gmg_commands.users:adduser', @@ -37,19 +32,15 @@ SUBCOMMAND_MAP = { 'makeadmin': { 'setup': 'mediagoblin.gmg_commands.users:makeadmin_parser_setup', 'func': 'mediagoblin.gmg_commands.users:makeadmin', - 'help': 'Changes a user\'s password'}, + 'help': 'Makes user an admin'}, 'changepw': { 'setup': 'mediagoblin.gmg_commands.users:changepw_parser_setup', 'func': 'mediagoblin.gmg_commands.users:changepw', - 'help': 'Makes admin an user'}, + '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'}, - 'convert_mongo_to_sql': { - 'setup': 'mediagoblin.gmg_commands.mongosql:mongosql_parser_setup', - 'func': 'mediagoblin.gmg_commands.mongosql:mongosql', - 'help': 'Convert Mongo DB data to SQL DB data'}, 'theme': { 'setup': 'mediagoblin.gmg_commands.theme:theme_parser_setup', 'func': 'mediagoblin.gmg_commands.theme:theme', diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py index 67fdd69c..fa25ecb2 100644 --- a/mediagoblin/gmg_commands/dbupdate.py +++ b/mediagoblin/gmg_commands/dbupdate.py @@ -18,8 +18,8 @@ import logging from sqlalchemy.orm import sessionmaker -from mediagoblin.db.sql.open import setup_connection_and_db_from_config -from mediagoblin.db.sql.util import MigrationManager +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 @@ -52,8 +52,8 @@ def gather_database_data(media_types, plugins): managed_dbdata = [] # Add main first - from mediagoblin.db.sql.models import MODELS as MAIN_MODELS - from mediagoblin.db.sql.migrations import MIGRATIONS as MAIN_MIGRATIONS + from mediagoblin.db.models import MODELS as MAIN_MODELS + from mediagoblin.db.migrations import MIGRATIONS as MAIN_MIGRATIONS managed_dbdata.append( DatabaseData( @@ -78,6 +78,7 @@ def gather_database_data(media_types, plugins): 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( @@ -91,6 +92,7 @@ forgotten to add it? ({1})'.format(plugin, exc)) 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( @@ -114,7 +116,7 @@ def run_dbupdate(app_config, global_config): global_config.get('plugins', {}).keys()) # Set up the database - connection, db = setup_connection_and_db_from_config(app_config) + db = setup_connection_and_db_from_config(app_config, migrations=True) Session = sessionmaker(bind=db.engine) diff --git a/mediagoblin/gmg_commands/import_export.py b/mediagoblin/gmg_commands/import_export.py index 72ebd8a8..d51a1e3e 100644 --- a/mediagoblin/gmg_commands/import_export.py +++ b/mediagoblin/gmg_commands/import_export.py @@ -105,7 +105,7 @@ def env_import(args): setup_storage() global_config, app_config = setup_global_and_app_config(args.conf_file) - connection, db = setup_connection_and_db_from_config( + db = setup_connection_and_db_from_config( app_config) tf = tarfile.open( @@ -243,8 +243,7 @@ def env_export(args): setup_storage() - connection, db = setup_connection_and_db_from_config( - app_config) + db = setup_connection_and_db_from_config(app_config) _export_database(db, args) diff --git a/mediagoblin/gmg_commands/migrate.py b/mediagoblin/gmg_commands/migrate.py deleted file mode 100644 index b915a528..00000000 --- a/mediagoblin/gmg_commands/migrate.py +++ /dev/null @@ -1,75 +0,0 @@ -# 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 - -from mediagoblin.init import setup_global_and_app_config - - -def migrate_parser_setup(subparser): - pass - - -def _print_started_migration(migration_number, migration_func): - sys.stdout.write( - "Running migration %s, '%s'... " % ( - migration_number, migration_func.func_name)) - sys.stdout.flush() - - -def _print_finished_migration(migration_number, migration_func): - sys.stdout.write("done.\n") - sys.stdout.flush() - - -def migrate(args): - run_migrate(args.conf_file) - - -def run_migrate(conf_file): - # This MUST be imported so as to set up the appropriate migrations! - from mediagoblin.db.mongo import migrations - - from mediagoblin.db.mongo import util as db_util - from mediagoblin.db.mongo.open import setup_connection_and_db_from_config - - global_config, app_config = setup_global_and_app_config(conf_file) - - connection, db = setup_connection_and_db_from_config( - app_config, use_pymongo=True) - migration_manager = db_util.MigrationManager(db) - - # Clear old indexes - print "== Clearing old indexes... ==" - removed_indexes = db_util.remove_deprecated_indexes(db) - - for collection, index_name in removed_indexes: - print "Removed index '%s' in collection '%s'" % ( - index_name, collection) - - # Migrate - print "\n== Applying migrations... ==" - migration_manager.migrate_new( - pre_callback=_print_started_migration, - post_callback=_print_finished_migration) - - # Add new indexes - print "\n== Adding new indexes... ==" - new_indexes = db_util.add_new_indexes(db) - - for collection, index_name in new_indexes: - print "Added index '%s' to collection '%s'" % ( - index_name, collection) diff --git a/mediagoblin/gmg_commands/shell.py b/mediagoblin/gmg_commands/shell.py index ec1ab535..4998acd7 100644 --- a/mediagoblin/gmg_commands/shell.py +++ b/mediagoblin/gmg_commands/shell.py @@ -47,24 +47,21 @@ def py_shell(**user_namespace): def ipython_shell(**user_namespace): """ - Run a shell for the user using ipython. + Run a shell for the user using ipython. Return False if there is no IPython """ try: from IPython import embed except: - print "IPython not available... exiting!" - return - + 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 + Setup a shell for the user either a normal Python shell or an IPython one """ user_namespace = { 'mg_globals': mg_globals, @@ -74,4 +71,6 @@ def shell(args): if args.ipython: ipython_shell(**user_namespace) else: - py_shell(**user_namespace) + # 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 index 70e591c9..024c8498 100644 --- a/mediagoblin/gmg_commands/users.py +++ b/mediagoblin/gmg_commands/users.py @@ -55,7 +55,7 @@ def adduser(args): entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password) entry.status = u'active' entry.email_verified = True - entry.save(validate=True) + entry.save() print "User created (and email marked as verified)" diff --git a/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo Binary files differindex eaec0daa..5e69858e 100644 --- a/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po index cf090994..51c71c3a 100644 --- a/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po @@ -1,17 +1,18 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # Majid Al-Dharrab <majid@aldharrab.com>, 2011. +# Mena Rezk Eid <minaeid90@gmail.com>, 2013. # <Omar.w.kh@gmail.com>, 2011. # <osamak@gnu.org>, 2011. msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -21,82 +22,96 @@ msgstr "" "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:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "اسم المستخدم" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "كلمة السر" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "عنوان البريد الإلكتروني" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "عÙوًا، التسجيل غير Ù…ØªØ§Ø Ù‡Ù†Ø§." -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "عذرًا، لقد اختار مستخدم آخر هذا الاسم." -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "تم التØÙ‚Ù‚ من بريدك الإلكتروني. يمكنك الآن الولوج، ÙˆØªØØ±ÙŠØ± ملÙÙƒ الشخصي، ونشر الصور!" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "Ù…ÙØªØ§Ø التØÙ‚Ù‚ أو معر٠المستخدم خاطئ" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" -msgstr "" +msgstr "يجب عليك تسجيل الدخول لإرسال بريد الكترونى لك!" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" -msgstr "" +msgstr "لقد قمت Ø¨Ø§Ù„ÙØ¹Ù„ بالتØÙ‚Ù‚ من عنوان البريد الإلكتروني الخاص بك!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "أعدنا إرسال رسالة التØÙ‚Ù‚." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "العنوان" @@ -105,8 +120,8 @@ msgid "Description of this work" msgstr "وص٠هذا العمل." #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -121,11 +136,11 @@ msgstr "الوسوم" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "المسار" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "لا يمكن ترك المسار ÙØ§Ø±ØºÙ‹Ø§" @@ -164,60 +179,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "يوجد مل٠آخر بهذا المسار لدى هذى المستخدم." -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "كلمة سر خاطئة" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -233,54 +269,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -290,17 +334,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -308,7 +352,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "" @@ -316,75 +375,74 @@ msgstr "" msgid "File" msgstr "الملÙ" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "يجب أن تضع ملÙًا." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "يا سلام! Ù†ÙØ´Ø±ÙŽØª!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "صورة قزم مرتبك" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "ويØÙŠ!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "يبدو أنه لا توجد ØµÙØØ© ÙÙŠ العنوان. عذرًا!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -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 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" -msgstr "" +msgstr "تأكد من بريدك الإلكترونى!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: 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:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "أض٠وسائط" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -392,31 +450,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 -msgid "Explore" +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:31 +msgid "Explore" +msgstr "استكشÙ" + +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -424,17 +486,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Ø£ØØ¯Ø« الوسائط" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -537,47 +592,85 @@ msgid "" "%(verification_url)s" msgstr "أهلًا يا %(username)sØŒ\n\nØ§ÙØªØ الرابط التالي\nÙÙŠ Ù…ØªØµÙØÙƒ Ù„ØªÙØ¹ÙŠÙ„ ØØ³Ø§Ø¨Ùƒ ÙÙŠ غنو ميدياغوبلن:\n\n%(verification_url)s" +#: 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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +msgid "Delete my account" +msgstr "" + #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "ØªØØ±ÙŠØ± %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "ØªØØ±ÙŠØ± مل٠%(username)s الشخصي" @@ -592,13 +685,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -617,7 +709,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -625,33 +717,77 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: 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/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" +msgstr "Ø¥Ø¶Ø§ÙØ© مجموعة" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 @@ -669,43 +805,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -718,67 +851,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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: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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -840,74 +959,58 @@ msgstr "إن كنت أنت ذلك الشخص لكنك Ùقدت رسالة الت 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "أض٠وسائط" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -938,23 +1041,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -962,74 +1106,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "أنت على وشك ØØ°Ù وسائط مستخدم آخر. كن ØØ°Ø±Ù‹Ø§ أثناء العملية." -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 89fde97f..495ef726 100644 --- a/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po index ee4a0aca..28bdca82 100644 --- a/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po @@ -1,16 +1,17 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # Al fred <devaleitzer@aim.com>, 2011. # <devaleitzer@aim.com>, 2011. +# <skarbat@gmail.com>, 2012. msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -20,97 +21,111 @@ msgstr "" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Nom d'usuari" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Contrasenya" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Adreça electrònica" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" -msgstr "" - -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" +msgstr "Nom d'usuari o correu" -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "Lamentablement aquest usuari ja existeix." -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." -msgstr "" +msgstr "Perdó, ja existeix un usuari amb aquesta adreça de correu." -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 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 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" -msgstr "" +msgstr "Has d'estar conectat per saber a qui hem d'enviar el correu!" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" -msgstr "" +msgstr "Ja has verificat la teva adreça de correu!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Torna'm a enviar el correu de verificació" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 msgid "" -"An email has been sent with instructions on how to change your password." +"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:273 +#: mediagoblin/auth/views.py:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 +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:271 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 "" +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:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." -msgstr "" +msgstr "Ara et pots conectar amb la teva nova contrasenya." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: 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 "" +msgstr "Descripció d'aquest treball." #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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 "" +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" @@ -118,26 +133,26 @@ msgstr "Etiquetes" #: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 msgid "Separate tags by commas." -msgstr "" +msgstr "Separa els tags amb comes." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" -msgstr "" +msgstr "Llimac" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" -msgstr "" +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 "" +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 "" +msgstr "Llicència" #: mediagoblin/edit/forms.py:50 msgid "Bio" @@ -149,80 +164,101 @@ msgstr "Lloc web" #: mediagoblin/edit/forms.py:58 msgid "This address contains errors" -msgstr "" +msgstr "Aquesta adreça conté errors" #: mediagoblin/edit/forms.py:63 msgid "Old password" -msgstr "" +msgstr "Contrasenya antiga" #: mediagoblin/edit/forms.py:64 msgid "Enter your old password to prove you own this account." -msgstr "" +msgstr "Introdueix la teva contrasenya antiga per comprovar que aquest compte és teu." #: mediagoblin/edit/forms.py:67 msgid "New password" +msgstr "Nova contrasenya" + +#: mediagoblin/edit/forms.py:74 +msgid "License preference" msgstr "" -#: mediagoblin/edit/forms.py:72 -msgid "Email me when others comment on my media" +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:82 +msgid "Email me when others comment on my media" +msgstr "Envia'm correu quan d'altres comentin al meu mitjà " + +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "El tÃtol no pot ser buit" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Descripció d'aquesta col.lecció" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "La part del tÃtol de l'adreça d'aquesta col.lecció. Normalment no cal que canviis això." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." -msgstr "" +msgstr "Ja existeix una entrada amb aquest llimac per aquest usuari" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" -msgstr "" +msgstr "Els canvis al perfil s'han guardat" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" -msgstr "" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Contrasenya errònia" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" +msgstr "Els detalls del compte s'han guardat" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Ja tens una col.lecció anomenada \"%s\"!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Estas editant la col.lecció d'un altre usuari. Prossegueix amb cautela." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "" +msgstr "No es pot enllaçar el tema... no hi ha tema establert\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" @@ -230,56 +266,64 @@ msgstr "" #: mediagoblin/gmg_commands/theme.py:74 msgid "However, old link directory symlink found; removed.\n" +msgstr "Tot i aixÃ, l'enllaç antic al directori s'ha trobat; eliminat.\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 "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" -msgstr "" +msgstr "Ho sento, no puc manegar aquest tipus d'arxiu :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" -msgstr "" +msgstr "La transformació del vÃdeo ha fallat" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Ubicació" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "Permetre" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "Denegar" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Nom" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "El nom del client OAuth" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Descripció" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +msgstr "Això serà visiable a usuaris que permetin que la teva aplicació\n s'autentifiqui com a ells." -#: mediagoblin/plugins/oauth/forms.py:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Tipus" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -287,27 +331,42 @@ msgid "" " <strong>Public</strong> - The client can't make confidential\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n" " JavaScript client)." -msgstr "" +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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "Redireccionar URI " -#: mediagoblin/plugins/oauth/forms.py:55 +#: mediagoblin/plugins/oauth/forms.py:54 msgid "" "The redirect URI for the applications, this field\n" " is <strong>required</strong> for public clients." -msgstr "" +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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Aquest camp és requeriment per a clients públics" #: mediagoblin/plugins/oauth/views.py:59 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/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Aquest tipus de fitxer no és và lid." @@ -315,129 +374,125 @@ msgstr "Aquest tipus de fitxer no és và lid." msgid "File" msgstr "Fitxer" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Heu d'escollir un fitxer." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Visca! S'ha enviat!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Imatge de la pantalla 404, el goblin no sap què fer..." - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Ups!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Sembla que no hi ha cap pà gina en aquesta adreça. Ho sentim." - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Si esteu convençut que l'adreça és correcta, pot ser que la pà gina que cerqueu s'hagi canviat d'ubicació o s'hagi eliminat." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Logo de mediagoblin" +msgstr "S'ha afegit la col.leccio \"%s\"!" -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" -msgstr "" +msgstr "Verifica el teu correu electrònic" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" +msgstr "Modificar els ajustaments del compte" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Tots els fitxers" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, 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/error.html:24 +msgid "Image of goblin stressing out" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" -msgstr "" +msgstr "Explorar" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" -msgstr "" +msgstr "Hola, una benvinguda al MediaGoblin!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 msgid "" "This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " "extraordinarily great piece of media hosting software." -msgstr "" +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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "" +msgstr "Per afegir el teu propi mitjà , col.locar comentaris, i més, pots conectar-te amb el teu compte MediaGoblin." -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" -msgstr "" +msgstr "No en tens una encara? Es fà cil!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, 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 "" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Crear un compte a aquest lloc</a> \no\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Preparar MediaGoblin al teu propi servidor</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" -msgstr "" - -#: 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" +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 "" +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 @@ -447,7 +502,7 @@ 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 "" +msgstr "No s'està processant cap mitjà " #: mediagoblin/templates/mediagoblin/admin/panel.html:61 #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 @@ -457,34 +512,34 @@ 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 "" +msgstr "Sense entrades fallades!" #: mediagoblin/templates/mediagoblin/admin/panel.html:92 msgid "Last 10 successful uploads" -msgstr "" +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 "" +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 "" +msgstr "Estableix la teva nova contrasenya" #: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 msgid "Set password" -msgstr "" +msgstr "Establir contrasenya" #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 msgid "Recover password" -msgstr "" +msgstr "Recuperar contrasenya" #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 msgid "Send instructions" -msgstr "" +msgstr "Enviar instruccions" #: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 #, python-format @@ -498,7 +553,7 @@ msgid "" "\n" "If you think this is an error, just ignore this email and continue being\n" "a happy goblin!" -msgstr "" +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!" @@ -514,7 +569,7 @@ msgstr "Creeu-ne un aquÃ!" #: mediagoblin/templates/mediagoblin/auth/login.html:51 msgid "Forgot your password?" -msgstr "" +msgstr "Has oblidat la teva contrasenya?" #: mediagoblin/templates/mediagoblin/auth/register.html:28 #: mediagoblin/templates/mediagoblin/auth/register.html:36 @@ -536,50 +591,88 @@ msgid "" "%(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/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:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" msgstr "" -#: 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/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +msgid "Add attachment" +msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: 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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Desa els canvis" +#: 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:47 +#: 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:41 +#: 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:59 +msgid "Delete my account" msgstr "" #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "Editant %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" -msgstr "" +msgstr "Editant perfil de %(username)s" #: mediagoblin/templates/mediagoblin/listings/collection.html:30 #: mediagoblin/templates/mediagoblin/listings/collection.html:35 @@ -587,122 +680,162 @@ msgstr "" #: mediagoblin/templates/mediagoblin/listings/tag.html:35 #, python-format msgid "Media tagged with: %(tag_name)s" -msgstr "" +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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 msgid "Download" -msgstr "" +msgstr "Descarregar" #: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" -msgstr "" +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 "" +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 "" +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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" -msgstr "" +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/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/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/video.html:40 +#: 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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" -msgstr "" +msgstr "Arxiu WebM (640p; VP8/Vorbis)" #: mediagoblin/templates/mediagoblin/submit/collection.html:26 msgid "Add a collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" +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 "" +msgstr "Afegeix el teu mitjà " #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 msgid "Edit" -msgstr "" +msgstr "Editar" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -msgstr "" +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 "" - -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "" +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 "" +msgstr "Relment eliminar %(media_title)s de %(collection_title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 @@ -710,91 +843,77 @@ msgstr "" msgid "" "Hi %(username)s,\n" "%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" -msgstr "" +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:37 +#: 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:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "" +msgstr "â– Navegant mitjà per a <a href=\"%(user_url)s\">%(username)s</a>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" -msgstr "" +msgstr "Afegeix un comentari" -#: 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" -msgstr "" +msgstr "Afegir aquest comentari" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" -msgstr "" +msgstr "a" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" +msgstr "<h3>Afegit el</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +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 "" +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 "" +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 "" +msgstr "Perfil de %(username)s" #: mediagoblin/templates/mediagoblin/user_pages/user.html:43 msgid "Sorry, no such user found." @@ -837,198 +956,218 @@ msgstr "Si siu aqeust usuari però heu perdut el correu de verificació, podeu < #: mediagoblin/templates/mediagoblin/user_pages/user.html:96 msgid "Here's a spot to tell others about yourself." -msgstr "" +msgstr "Aqui hi ha un espai per explicar de tu als demés" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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 "" +msgstr "Aqui és on apareixerà el teu mitjà , però sembla que encara no hi has afegit res." -#: mediagoblin/templates/mediagoblin/user_pages/user.html:157 -msgid "Add media" -msgstr "Tots els fitxers" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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 "" +msgstr "Sembla que no hi ha cap mitjà aqui encara..." -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" +msgstr "Tots els drets reservats" #: mediagoblin/templates/mediagoblin/utils/pagination.html:39 msgid "↠Newer" -msgstr "" +msgstr "↠Més nou" #: mediagoblin/templates/mediagoblin/utils/pagination.html:45 msgid "Older →" -msgstr "" +msgstr "Més antic →" #: mediagoblin/templates/mediagoblin/utils/pagination.html:48 msgid "Go to page:" -msgstr "" +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 "" +msgstr "més nou" #: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 #: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 msgid "older" -msgstr "" +msgstr "més antic" #: mediagoblin/templates/mediagoblin/utils/tags.html:20 msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 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/user_pages/forms.py:28 -msgid "I am sure I want to delete this" +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" msgstr "" -#: mediagoblin/user_pages/forms.py:32 -msgid "I am sure I want to remove this item from the collection" +#: 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/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 "-- Select --" +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:37 +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Sel.leccionar --" + +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "Incluir una nota" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" -msgstr "" +msgstr "comentat al teu post" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." -msgstr "" +msgstr "Uups, el teu comentari era buit." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" -msgstr "" +msgstr "El teu comentari s'ha publicat!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +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:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "Has de sel.leccionar o afegir una col.lecció" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "\"%s\" ja és a la col.lecció \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" +msgstr "\"%s\" afegir a la col.lecció \"%s\"" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." -msgstr "" +msgstr "Has esborrat el mitjà " -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "El mitjà no s'ha esborrat perque no has marcat que n'estiguessis segur." -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." -msgstr "" +msgstr "Ets a punt d'esborrar el mitjà d'un altre usuari. Prossegueix amb cautela." -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "Has esborrat l'element de la col.lecció" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "L'element no s'ha eliminat perque no has marcat que n'estiguessis segur." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +msgstr "Ets a punt d'esborrar un element de la col.lecció d'un altre usuari. Prossegueix amb cautela." -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "Has esborrat la col.lecció \"%s\"" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "La col.lecció no s'ha esborrat perquè no has marcat que n'estiguessis segur." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 Binary files differindex 23685f41..6b6827f0 100644 --- a/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po index 0426e9ed..8494aa60 100644 --- a/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po @@ -1,16 +1,17 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# 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. +# Tanja Trudslev <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: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -20,111 +21,125 @@ msgstr "" "Language: da\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Brugernavn" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Kodeord" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Email adresse" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" -msgstr "" - -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" +msgstr "Brugernavn eller email" -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." -msgstr "" +msgstr "Desværre, registrering er ikke muligt pÃ¥ denne instans" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." -msgstr "" +msgstr "Desværre, det brugernavn er allerede brugt" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." -msgstr "" +msgstr "Desværre, en bruger er allerede oprettet for den email" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" -msgstr "" +msgstr "Din email adresse er blevet bekræftet. Du kan nu logge pÃ¥, ændre din profil, og indsende billeder!" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" -msgstr "" +msgstr "Bekræftelsesnøglen eller brugerid er forkert" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" -msgstr "" +msgstr "Du er nødt til at være logget ind, sÃ¥ vi ved hvem vi skal emaile!" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" -msgstr "" +msgstr "Du har allerede bekræftet din email adresse!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Email til godkendelse sendt igen." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 msgid "" -"An email has been sent with instructions on how to change your password." +"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:261 +msgid "Couldn't find someone with that username." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:264 +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:271 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 "" +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:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." -msgstr "" +msgstr "Du kan nu logge ind med dit nye kodeord." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: 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 "" +msgstr "Beskrivelse af arbejdet" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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 "" +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 "" +msgstr "Tags" #: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 msgid "Separate tags by commas." -msgstr "" +msgstr "Separer tags med kommaer." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -132,16 +147,16 @@ msgstr "" msgid "" "The title part of this media's address. You usually don't need to change " "this." -msgstr "" +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 "" +msgstr "Licens" #: mediagoblin/edit/forms.py:50 msgid "Bio" -msgstr "" +msgstr "Bio" #: mediagoblin/edit/forms.py:56 msgid "Website" @@ -149,80 +164,101 @@ msgstr "Websted" #: mediagoblin/edit/forms.py:58 msgid "This address contains errors" -msgstr "" +msgstr "Denne adresse indeholder fejl" #: mediagoblin/edit/forms.py:63 msgid "Old password" -msgstr "" +msgstr "Gammelt kodeord" #: mediagoblin/edit/forms.py:64 msgid "Enter your old password to prove you own this account." -msgstr "" +msgstr "Skriv dit gamle kodeord for at bevise det er din konto." #: mediagoblin/edit/forms.py:67 msgid "New password" +msgstr "Ny kodeord" + +#: mediagoblin/edit/forms.py:74 +msgid "License preference" msgstr "" -#: mediagoblin/edit/forms.py:72 -msgid "Email me when others comment on my media" +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:82 +msgid "Email me when others comment on my media" +msgstr "Email mig nÃ¥r andre kommenterer pÃ¥ mine medier" + +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "Titlen kan ikke være tom" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Beskrivelse af denne samling" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "Titeldelen af denne samlings's adresse. Du behøver normalt ikke ændre dette." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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 are editing a user's profile. Proceed with caution." +msgid "You can only edit your own profile." msgstr "" -#: mediagoblin/edit/views.py:198 +#: 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 "" +msgstr "Profilændringer gemt" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" -msgstr "" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Forkert kodeord" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" +msgstr "Kontoindstillinger gemt" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Du har allerede en samling ved navn \"%s\"!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Du er ved at ændre en anden bruger's samling. Pas pÃ¥." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "" +msgstr "Kan ikke linke til tema... intet tema sat\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" @@ -232,54 +268,62 @@ msgstr "" 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 :(" +#: 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/video/processing.py:35 +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 +msgid "Sorry, I don't support that file type :(" +msgstr "Desværre, jeg understøtter ikke den filtype :(" + +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "Tillad" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "Forbyd" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Navn" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "Navnet af OAuth klienten" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Beskrivelse" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Type" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -289,101 +333,115 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Dette felt er nødvendigt for offentlige klienter" #: mediagoblin/plugins/oauth/views.py:59 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/processing/__init__.py:138 -msgid "Invalid file given for media type." +#: 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:172 +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:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." -msgstr "" +msgstr "Du mÃ¥ give mig en fil" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" -msgstr "" +msgstr "Juhuu! Delt!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Billede af stresset 404 goblin" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Hovsa!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" +msgstr "Bekræft din email!" -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "MediaGoblin logo" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: 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:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -391,31 +449,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 -msgid "Explore" +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:31 +msgid "Explore" +msgstr "Udforsk" + +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" -msgstr "" +msgstr "Hey, velkommen til denne MediaGoblin side!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "" +msgstr "For at tilføje dine egne medier, skrive kommentarer, og mere, du kan logge ind med din MediaGoblin konto." -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" -msgstr "" +msgstr "Har du ikke en endnu? Det er let!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -423,17 +485,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -536,47 +591,85 @@ msgid "" "%(verification_url)s" 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/edit.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit.html:35 -#, python-format -msgid "Editing %(media_title)s" +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Gem ændringer" +#: 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Redigerer %(username)s profil" @@ -591,13 +684,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -616,7 +708,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -624,21 +716,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -646,12 +788,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -668,43 +804,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -717,67 +850,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" +msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -839,74 +958,58 @@ msgstr "" 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -937,23 +1040,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -961,74 +1105,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 015a480c..5ae794fa 100644 --- a/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po index 6a1605c3..b3d82ee9 100644 --- a/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po @@ -1,26 +1,29 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # <benjamin@lebsanft.org>, 2011. # <cwebber@dustycloud.org>, 2011. -# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011, 2012. +# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011-2012. +# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2013. # <jakob.kramer@gmx.de>, 2011, 2012. -# Jakob Kramer <jakob.kramer@gmx.de>, 2012. +# Jakob Kramer <jakob.kramer@gmx.de>, 2012-2013. # Jan-Christoph Borchardt <JanCBorchardt@fsfe.org>, 2011. # Jan-Christoph Borchardt <jan@unhosted.org>, 2011, 2012. # <kyoo@kyoo.ch>, 2011. # <mediagoblin.org@samba-tng.org>, 2011. # Rafael Maguiña <rafael.maguina@gmail.com>, 2011. +# <sebastian@sspaeth.de>, 2012. +# Vinzenz Vietzke <vietzke@b1-systems.de>, 2012. # Vinzenz Vietzke <vinz@fedoraproject.org>, 2011. msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" -"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-07 13:16+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" @@ -29,82 +32,96 @@ msgstr "" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Ungültiger Benutzername oder E-Mail-Adresse." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Dieses Feld akzeptiert keine E-Mail-Adressen." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Dieses Feld benötigt eine E-Mail-Adresse." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Benutzername" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Passwort" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "E-Mail-Adresse" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Benutzername oder E-Mail-Adresse" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Fehlerhafte Eingabe" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." -msgstr "Das Registrieren ist auf dieser Instanz leider deaktiviert." +msgstr "Benutzerregistrierung ist auf diesem Server leider deaktiviert." -#: mediagoblin/auth/views.py:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" -msgstr "Deine E-Mail-Adresse wurde bestätigt. Du kannst dich nun anmelden, dein Profil bearbeiten und Bilder hochladen!" +msgstr "Dein GNU MediaGoblin Konto wurde hiermit aktiviert. Du kannst dich jetzt anmelden, dein Profil bearbeiten und Medien hochladen." -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" -msgstr "Der Bestätigungsschlüssel oder die Nutzernummer ist falsch." +msgstr "Der Aktivierungsschlüssel oder die Nutzerkennung ist falsch." -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" -msgstr "Deine E-Mail-Adresse wurde bereits bestätigt." +msgstr "Deine E-Mail-Adresse wurde bereits aktiviert." -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." -msgstr "Bestätigungs-E-Mail wurde erneut versandt." +msgstr "Aktivierungsmail wurde erneut versandt." + +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "Es konnte niemand mit diesem Benutzernamen gefunden werden." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." -msgstr "Es wurde eine Email mit Anweisungen für die Änderung des Passwortes an dich gesendet." +msgstr "Es wurde eine E-Mail mit der Anleitung zur Änderung des Passwortes an Dich gesendet." -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 bestätigt wurde." +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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Es konnte niemand mit diesem Nutzernamen oder Email gefunden werden." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 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:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titel" @@ -113,8 +130,8 @@ msgid "Description of this work" msgstr "Beschreibung des Werkes" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -129,11 +146,11 @@ msgstr "Schlagwörter" msgid "Separate tags by commas." msgstr "Kommaseparierte Schlagwörter" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Kurztitel" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Bitte gib einen Kurztitel ein" @@ -172,62 +189,83 @@ msgstr "Gib dein altes Passwort ein, um zu bestätigen, dass du dieses Konto bes msgid "New password" msgstr "Neues Passwort" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "Bevorzugte Lizenz" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "Dies wird Deine Standardlizenz in den Upload-Forumularen sein." + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "Mir eine E-Mail schicken, wenn andere meine Medien kommentieren" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "Der Titel kann nicht leer sein" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Beschreibung dieser Sammlung" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "Der Titelteil dieser Sammlungsadresse. Du musst ihn normalerweise nicht ändern." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "Diesen Kurztitel hast du bereits vergeben." -#: mediagoblin/edit/views.py:86 +#: mediagoblin/edit/views.py:85 msgid "You are editing another user's media. Proceed with caution." -msgstr "Du bearbeitest die Medien eines Anderen. Sei bitte vorsichtig." +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. Sei bitte vorsichtig." +msgstr "Du bearbeitest das Profil eines anderen Nutzers. Sei bitte vorsichtig." -#: mediagoblin/edit/views.py:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Das Profil wurde aktualisiert" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Falsches Passwort" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Kontoeinstellungen gespeichert" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Falsches Passwort" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Du musst die Löschung deines Kontos bestätigen." -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Du hast bereits eine Sammlung mit Namen »%s«!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." -msgstr "" +msgstr "Eine Sammlung mit diesem Kurztitel existiert bereits für diesen Benutzer." -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Du bearbeitest die Sammlung eines anderen Benutzers. Sei vorsichtig." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" @@ -241,54 +279,62 @@ msgstr "Für dieses Theme gibt es kein asset-Verzeichnis\n" msgid "However, old link directory symlink found; removed.\n" msgstr "Trotzdem wurde eine alte Verknüpfung gefunden; sie wurde entfernt\n" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Entschuldigung, dieser Dateityp wird nicht unterstützt." -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Videokonvertierung fehlgeschlagen" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Aufnahmeort" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "Erlauben" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "Verweigern" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Name" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "Der Name des OAuth-Clients" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Beschreibung" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +msgstr "Dies wird für Benutzer sichtbar sein, die deiner\nAnwendung erlauben, sich als sie zu authentifizieren.." -#: mediagoblin/plugins/oauth/forms.py:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Typ" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -296,27 +342,42 @@ msgid "" " <strong>Public</strong> - The client can't make confidential\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n" " JavaScript client)." -msgstr "" +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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "Weiterleitungs-URI" -#: mediagoblin/plugins/oauth/forms.py:55 +#: mediagoblin/plugins/oauth/forms.py:54 msgid "" "The redirect URI for the applications, this field\n" " is <strong>required</strong> for public clients." -msgstr "" +msgstr "Die Weiterleitungs-URI für die Anwendung, dieses Feld\n ist <strong>Pflicht</strong> für öffentliche Clients." -#: mediagoblin/plugins/oauth/forms.py:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Dieses Feld ist Pflicht für öffentliche Clients" #: mediagoblin/plugins/oauth/views.py:59 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 "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Die Datei stimmt nicht mit dem gewählten Medientyp überein." @@ -324,75 +385,74 @@ msgstr "Die Datei stimmt nicht mit dem gewählten Medientyp überein." msgid "File" msgstr "Datei" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Du musst eine Datei angeben." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" -msgstr "Yeeeaaah! Geschafft!" +msgstr "JAAA! Geschafft!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Bild eines angespannten Goblins" +msgstr "Sammlung »%s« hinzugefügt!" -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Hoppla!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Tut uns Leid, aber unter der angegebenen Adresse gibt es keine Seite!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Wenn du sicher bist, dass die Adresse stimmt, wurde die Seite eventuell verschoben oder gelöscht." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "MediaGoblin-Logo" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" -msgstr "Bitte bestätige deine E-Mail-Adresse!" - -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Medien hinzufügen" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" +msgstr "Bitte bestätige Deine E-Mail-Adresse!" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Dein Profil ansehen" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "abmelden" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Abmelden" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Kontoeinstellungen ändern" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "Abmelden" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Medien hinzufügen" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Neues Album erstellen" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Diese Seite setzt das <a href=\"http://gnu.org/\">GNU</a>-Projekt <a href=\"http://mediagoblin.org/\">MediaGoblin</a> ein." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -400,31 +460,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Bild eines gestressten Goblins" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Entdecken" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Hallo du, willkommen auf dieser MediaGoblin-Seite!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 msgid "" "This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " "extraordinarily great piece of media hosting software." -msgstr "Diese Seite setzt <a href=\"http://mediagoblin.org\">MediaGoblin</a> ein, eine großartige Software für Medienhosting." +msgstr "Diese Webseite setzt <a href=\"http://mediagoblin.org\">MediaGoblin</a> ein, eine großartige Software für Medienhosting." -#: mediagoblin/templates/mediagoblin/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 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, zu kommentieren und mehr." +msgstr "Melde Dich mit Deinem MediaGoblin-Konto an, um eigene Medien hinzuzufügen, andere zu kommentieren und vieles mehr." -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Hast du noch keinen? Das geht ganz einfach!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -432,17 +496,10 @@ msgid "" " <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/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Neuste Medien" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -515,11 +572,11 @@ msgstr "Anmeldevorgang fehlgeschlagen!" #: mediagoblin/templates/mediagoblin/auth/login.html:44 msgid "Don't have an account yet?" -msgstr "Hast du noch keines? Es geht ganz einfach!" +msgstr "Hast du noch keines?" #: mediagoblin/templates/mediagoblin/auth/login.html:45 msgid "Create one here!" -msgstr "Registriere dich hier!" +msgstr "Registriere dich einfach hier!" #: mediagoblin/templates/mediagoblin/auth/login.html:51 msgid "Forgot your password?" @@ -528,7 +585,7 @@ msgstr "Passwort vergessen?" #: mediagoblin/templates/mediagoblin/auth/register.html:28 #: mediagoblin/templates/mediagoblin/auth/register.html:36 msgid "Create an account!" -msgstr "Neues Konto registrieren!" +msgstr "Neues Nutzerkonto registrieren!" #: mediagoblin/templates/mediagoblin/auth/register.html:40 msgid "Create" @@ -543,49 +600,87 @@ msgid "" "your web browser:\n" "\n" "%(verification_url)s" -msgstr "Hallo %(username)s,\n\num dein Konto bei GNU MediaGoblin zu aktivieren, musst du folgende Adresse in deinem Webbrowser öffnen:\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/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 "" +msgstr "Bearbeite Anhänge von %(media_title)s" -#: 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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Anhänge" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Änderungen speichern" +#: 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:47 +#: 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:41 +#: 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:59 +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 "" +msgstr "Bearbeite %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "%(username)ss Profil bearbeiten" @@ -600,13 +695,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Original" @@ -625,7 +719,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "Originaldatei" @@ -633,33 +727,77 @@ msgstr "Originaldatei" msgid "WebM file (Vorbis codec)" msgstr "WebM-Datei (Vorbis-Codec)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" 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:59 +#: 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 "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Hinzufügen" +msgstr "Eine Sammlung hinzufügen" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 @@ -669,50 +807,47 @@ msgstr "Deine Medien" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Löschen" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Möchtest du %(title)s wirklich löschen?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Dauerhaft 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 "" +msgstr "Wirklich »%(media_title)s« aus »%(collection_title)s« entfernen?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 msgid "Remove" -msgstr "" +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 @@ -726,74 +861,60 @@ msgstr "Hallo %(username)s,\n%(comment_author)s hat dein Medium (%(comment_url)s msgid "%(username)s's media" msgstr "%(username)ss Medien" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Bild für %(media_title)s" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Einen Kommentar schreiben" -#: 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 "Die Texte lassen sich durch <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> formatieren." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Kommentar absenden" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "um" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Veröffentlicht am</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "Anhänge" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "Anhang hinzufügen" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "" +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:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +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 den Status der Medien, die sich gerade in Bearbeitung befinden, hier betrachten." +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" @@ -812,7 +933,7 @@ 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-Bestätigung benötigt" +msgstr "E-Mail Aktivierung benötigt" #: mediagoblin/templates/mediagoblin/user_pages/user.html:53 msgid "Almost done! Your account still needs to be activated." @@ -821,15 +942,15 @@ 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 dir erklärt, was du noch machen musst." +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 "Wenn sie nicht ankommt:" +msgstr "Falls sie nicht ankommt:" #: mediagoblin/templates/mediagoblin/user_pages/user.html:65 msgid "Resend verification email" -msgstr "Bestätigungs-E-Mail erneut senden" +msgstr "Aktivierungsmail erneut senden" #: mediagoblin/templates/mediagoblin/user_pages/user.html:73 msgid "" @@ -842,80 +963,64 @@ msgstr "Jemand hat bereits ein Konto mit diesem Benutzernamen registriert, aber 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 Bestätigungsmail verloren gegangen ist, kannst du dich <a href=\"%(login_url)s\">anmelden</a> und sie erneut senden." +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 Anderen etwas über dich erzählen." +msgstr "Hier kannst Du Dich selbst beschreiben." -#: mediagoblin/templates/mediagoblin/user_pages/user.html:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Kontoeinstellungen ändern" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Sammlungen durchstöbern" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Medien hinzufügen" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(entfernen)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "In den Sammlungen" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "" +#: 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/geolocation_map.html:25 -msgid "Location" -msgstr "Aufnahmeort" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "In <a href=\"%(osm_url)s\">OpenStreetMap</a> öffnen" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Alle Rechte vorbehalten" @@ -946,98 +1051,134 @@ msgstr "älter" msgid "Tagged with" msgstr "Schlagwörter" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Die Bilddatei konnte nicht gelesen werden." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: mediagoblin/user_pages/forms.py:35 msgid "I am sure I want to remove this item from the collection" -msgstr "" +msgstr "Ich bin sicher, dass ich dieses Objekt aus der Sammlung entfernen möchte" -#: mediagoblin/user_pages/forms.py:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Album" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" -msgstr "" +msgstr "-- Auswählen --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "Notiz anfügen" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" msgstr "hat dein Medium kommentiert" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." -msgstr "Ohh, der Kommentar war leer." +msgstr "Hoppla, der Kommentartext fehlte." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" -msgstr "Dein Kommentar wurde gesendet!" +msgstr "Dein Kommentar wurde angenommen!" + +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Bitte prüfe deinen Einträge und versuche erneut." -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "Du musst eine Sammlung auswählen oder hinzufügen" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "»%s« ist bereits in der Sammlung »%s«" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "Manche Dateien dieses Eintrags scheinen zu fehlen. Es wird trotzdem gelöscht." +msgstr "»%s« zur Sammlung »%s« hinzugefügt" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Du hast das Medium gelöscht." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "Du hast das Objekt aus der Sammlung gelöscht." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +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:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +msgstr "Du bist dabei ein Objekt aus der Sammlung eines anderen Nutzers zu entfernen. Sei vorsichtig." -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "Du hast die Sammlung »%s« gelöscht" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "Die Sammlung wurde nicht gelöscht, weil du nicht bestätigt hast, dass du dir sicher bist." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 index 3e589f71..6950f515 100644 --- a/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po @@ -1,14 +1,14 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR <EMAIL@ADDRESS>, 2012. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2013. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" +"POT-Creation-Date: 2013-03-11 17:21-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" @@ -17,81 +17,95 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your " "profile, and submit images!" msgstr "" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "" @@ -100,8 +114,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 msgid "" "You can use\n" " <a " @@ -117,11 +131,11 @@ msgstr "" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -160,60 +174,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -229,54 +264,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:37 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: mediagoblin/plugins/oauth/forms.py:45 msgid "" "<strong>Confidential</strong> - The client can\n" " make requests to the GNU MediaGoblin instance that can " @@ -290,17 +333,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -308,7 +351,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "" @@ -316,75 +374,75 @@ msgstr "" msgid "File" msgstr "" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for " -"has been moved or deleted." +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: mediagoblin/templates/mediagoblin/auth/login.html:28 -#: mediagoblin/templates/mediagoblin/auth/login.html:36 -#: mediagoblin/templates/mediagoblin/auth/login.html:54 -msgid "Log in" +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -392,31 +450,35 @@ msgid "" " href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an " @@ -427,17 +489,10 @@ msgid "" "your own server</a>" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "Here you can track the state of media being processed on this instance." msgstr "" @@ -539,47 +594,85 @@ msgid "" "%(verification_url)s" 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/edit.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit.html:35 -#, python-format -msgid "Editing %(media_title)s" +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "" @@ -594,13 +687,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -619,7 +711,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -627,21 +719,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -649,12 +791,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -671,43 +807,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -721,67 +854,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" +msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -841,74 +960,58 @@ msgstr "" 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -939,23 +1042,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -963,74 +1107,70 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed " "with caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were " "sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex dbd2bc4d..ac74a68b 100644 --- a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po index 2bfb4319..e7785d73 100644 --- a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po @@ -1,8 +1,9 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: +# <deletesoftware@yandex.ru>, 2013. # <deletesoftware@yandex.ru>, 2011-2012. # Fernando Inocencio <faigos@gmail.com>, 2011. # <john_w1954@fastmail.fm>, 2011. @@ -10,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 11:41-0500\n" -"PO-Revision-Date: 2012-09-24 18:27+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-10 16:50+0000\n" "Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -21,82 +22,96 @@ msgstr "" "Language: eo\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Nevalida ensalutnomo aÅ retpoÅtadreso." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Ĉi tiu kampo ne akceptas retpoÅtadresojn." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Ĉi tiu kampo postulas retpoÅtadreson." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Uzantnomo" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Pasvorto" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "RetpoÅtadreso" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Salutnomo aÅ retpoÅtadreso" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "La enigitaĵo malÄustas" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "La kontrol-kodo aÅ la uzantonomo ne estas korekta" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Vi jam konfirmis vian retpoÅtadreson!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Resendi vian kontrol-mesaÄon." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "Trovitas neniu kun tiu ensalutnomo." + +#: mediagoblin/auth/views.py:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Mi trovis neniun kun tiu salutnomo aÅ retpoÅtadreso." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 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:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titolo" @@ -105,8 +120,8 @@ 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:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -121,11 +136,11 @@ msgstr "Etikedoj" msgid "Separate tags by commas." msgstr "Dividu la etikedojn per komoj." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "La distingiga adresparto" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "La distingiga adresparto ne povas esti malplena" @@ -164,61 +179,83 @@ msgstr "Enigu vian malnovan pasvorton por pruvi, ke ĉi tiu konto estas via." msgid "New password" msgstr "La nova pasvorto" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "Permesila prefero" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 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:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "La titolo ne povas malpleni." -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "Priskribo de ĉi tiu kolekto" +msgstr "Priskribo de la kolekto" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 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/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "ProfilÅanÄoj estis konservitaj" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "MalÄusta pasvorto" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Kontagordoj estis konservitaj" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "MalÄusta pasvorto" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Vi bezonas konfirmi la forigon de via konto." -#: mediagoblin/edit/views.py:287 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format -msgid "You already have a collection called \"%s\"!title" -msgstr "Vi jam havas kolekton kun la nomo «%s»!title" +msgid "You already have a collection called \"%s\"!" +msgstr "Vi jam havas kolekton kun la nomo «%s»!" -#: mediagoblin/edit/views.py:290 +#: mediagoblin/edit/views.py:326 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:307 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Vi redaktas kolekton de alia uzanto. Agu singardeme." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" @@ -232,54 +269,62 @@ msgstr "Mankas dosierujo kun aspektiloj por la etoso\n" msgid "However, old link directory symlink found; removed.\n" msgstr "Tamen trovitas — kaj forigitas — malnova simbola ligilo al dosierujo.\n" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Mi pardonpetas, mi ne subtenas tiun dosiertipon :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Malsukcesis transkodado de filmo" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Loko" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Nomo" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "La nomo de la OAuth-kliento" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Priskribo" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Tipo" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -289,17 +334,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -307,7 +352,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "La provizita dosiero ne konformas al la informtipo." @@ -315,80 +375,74 @@ msgstr "La provizita dosiero ne konformas al la informtipo." msgid "File" msgstr "Dosiero" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Vi devas provizi dosieron." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Hura! AlÅutitas!" -#: mediagoblin/submit/views.py:211 mediagoblin/user_pages/views.py:215 -#, python-format -msgid "You already have a collection called \"%s\"!" -msgstr "" - -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Bildo de 404-koboldo penÅvitanta." - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Oj!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "VerÅajne ĉe ĉi tiu adreso ne estas paÄo. Ni bedaÅras!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Se vi estas certa, ke la adreso estas Äusta, eble la serĉata de vi paÄo estis movita aÅ forigita." +msgstr "Kolekto «%s» aldonitas!" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Emblemo de MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Konfirmu viecon de la retpoÅtadreso!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Aldoni dosieron" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Vidi vian profilon" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "elsaluti" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Elsaluti" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "ÅœanÄi kontagordojn" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "Elsaluti" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Aldoni dosieron" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Krei novan kolekton" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Funkcias per <a href=\"http://mediagoblin.org\">MediaGoblin</a>, unu el la <a href=\"http://gnu.org/\">projektoj de GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -396,31 +450,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Bildo de zorgigita koboldo" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "ĈirkaÅrigardi" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Saluton, kaj bonvenon al ĉi tiu MediaGoblina retpaÄaro!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 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/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Ĉu vi ankoraÅ ne havas tian? Ne malÄoju!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -428,17 +486,10 @@ msgid "" " <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\">Kreu konton en ĉi tiu retejo</a>\n aÅ\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">ekfunkciigu MediaGoblin’on en via propra servilo</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Laste aldonitaj dosieroj" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -541,47 +592,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Kundosieroj" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Konservi ÅanÄojn" +#: 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:47 +#: 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:41 +#: 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:59 +msgid "Delete my account" +msgstr "Forigi mian konton." + #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "Redaktado de %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Redaktado de l’profilo de %(username)s'" @@ -596,13 +685,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Originalo" @@ -621,7 +709,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "originalan dosieron" @@ -629,33 +717,77 @@ msgstr "originalan dosieron" msgid "WebM file (Vorbis codec)" msgstr "WebMan dosieron (kun Vorbisa kodaĵo)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "BedaÅrinde ĉi tiu filmo ne spekteblas, ĉar\n<span class=\"whitespace other\" title=\"Tab\">»</span> via TTT-legilo ne subtenas montradon\n<span class=\"whitespace other\" title=\"Tab\">»</span> de filmoj laÅ HTML5." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Vi povas akiri modernan TTT-legilon,\n<span class=\"whitespace other\" title=\"Tab\">»</span> kapablan montri ĉi tiun filmon, ĉe <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!" +" 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:59 +#: 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 "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Aldoni" +msgstr "Aldonado de kolekto" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 @@ -665,50 +797,47 @@ msgstr "Aldono de via dosiero" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Forigi" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Ĉu vere forigi %(title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Forigi senrevene" - #: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 #, python-format msgid "Really remove %(media_title)s from %(collection_title)s?" -msgstr "" +msgstr "Ĉu vere forigi %(media_title)s el %(collection_title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 msgid "Remove" -msgstr "" +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 @@ -722,69 +851,55 @@ msgstr "Saluton, %(username)s.\n%(comment_author)s komentis ĉe via alÅutaĵo ( msgid "%(username)s's media" msgstr "Dosieroj de %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Bildo de «%(media_title)s»" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Aldoni komenton" -#: 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 "Vi povas uzi por markado la lingvon «<a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>»." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Aldoni ĉi tiun komenton" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "je" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Aldonita je</h3>\n <p>la %(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "Kundosieroj" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "Aldoni kundosieron" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "" +msgid "Add “%(media_title)s†to a collection" +msgstr "Aldoni «%(media_title)s» al kolekto" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +msgstr "Aldoni novan kolekton" #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 msgid "" @@ -844,74 +959,58 @@ msgstr "Se vi estas tiu sed vi perdis vian kontrolmesaÄon, vi povas <a href=\"% 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "ÅœanÄi kontagordojn" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Vidi kolektojn" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Aldoni dosieron" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(forigi)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "En kolektoj:" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "" +#: 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/geolocation_map.html:25 -msgid "Location" -msgstr "Loko" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "Vidi sur <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Ĉiuj rajtoj estas rezervitaj" @@ -942,98 +1041,134 @@ msgstr "malpli nova" msgid "Tagged with" msgstr "Markita per" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Malsukcesis lego de la bildodosiero" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: mediagoblin/user_pages/forms.py:35 msgid "I am sure I want to remove this item from the collection" -msgstr "" +msgstr "Jes, mi volas forigi ĉi tiun dosieron el la kolekto" -#: mediagoblin/user_pages/forms.py:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Kolekto" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" -msgstr "" +msgstr "-- Elektu --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "Rimarko" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" msgstr "komentis je via afiÅo" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Oj, via komento estis malplena." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Via komento estis afiÅita!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Bonvolu kontroli vian enigitaĵon kaj reprovi." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "Necesas elekti aÅ aldoni kolekton" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "«%s» jam estas en la kolekto «%s»" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "Iuj dosieroj de ĉi tiu ero Åajne mankas. Mi tamen forigas." +msgstr "«%s» estis aldonita al la kolekto «%s»" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Vi forigis la dosieron." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "Vi forigis la dosieron el la kolekto." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "La dosiero ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +msgstr "Vi estas forigonta dosieron el kolekto de alia uzanto. Agu singardeme." -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "Vi forigis la kolekton «%s»" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "La kolekto ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 Binary files differindex b06a995e..e2df0731 100644 --- a/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po index 1429f887..21dce0b1 100644 --- a/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -10,6 +10,7 @@ # <juangsub@gmail.com>, 2011. # <juanma@kde.org.ar>, 2011, 2012. # <larjona99@gmail.com>, 2012. +# Laura Arjona Reina <larjona99@gmail.com>, 2013. # Mario Rodriguez <msrodriguez00@gmail.com>, 2011. # <mu@member.fsf.org>, 2011. # <shackra@riseup.net>, 2012. @@ -18,9 +19,9 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 11:41-0500\n" -"PO-Revision-Date: 2012-09-24 17:10+0000\n" -"Last-Translator: Elesa <stardustprincess17@hotmail.com>\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\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" @@ -29,82 +30,96 @@ msgstr "" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Nombre de usuario o correo electrónico inválido." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Este campo no acepta direcciones de correo." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Este campo requiere una dirección de correo." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Nombre de usuario" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Contraseña" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Dirección de correo electrónico" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Nombre de usuario o email" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Los datos ingresados son incorrectos" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 ingresar, editar tu perfil, y enviar imágenes!" +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 +#: mediagoblin/auth/views.py:180 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 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "¡Ya has verificado tu dirección de correo!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Se reenvió tu correo electrónico de verificación." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +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:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "No se pudo encontrar a alguien con ese nombre de usuario o correo electrónico." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." -msgstr "Ahora tu puedes entrar usando tu nueva contraseña." +msgstr "Ahora tu puedes iniciar sesión usando tu nueva contraseña." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "TÃtulo" @@ -113,8 +128,8 @@ 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:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -129,11 +144,11 @@ msgstr "Etiquetas" msgid "Separate tags by commas." msgstr "Separa las etiquetas por comas." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Ficha" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "La ficha no puede estar vacÃa" @@ -172,59 +187,81 @@ msgstr "Escriba la anterior contraseña para demostrar que esta cuenta te perten msgid "New password" msgstr "Nueva contraseña" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "Preferencias de licencia" + +#: mediagoblin/edit/forms.py:80 +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:82 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:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "El tÃtulo no puede estar vacÃo" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 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:93 +#: mediagoblin/edit/forms.py:103 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/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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. Proceder con precaución." +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. Proceder con precaución." +msgstr "Estás editando un perfil de usuario. Procede con precaución." -#: mediagoblin/edit/views.py:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Los cambios de perfil fueron salvados" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Contraseña incorrecta" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "las configuraciones de cuenta fueron salvadas" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Contraseña incorrecta" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Necesitas confirmar el borrado de tu cuenta." -#: mediagoblin/edit/views.py:287 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format -msgid "You already have a collection called \"%s\"!title" -msgstr "Ya tienes una colección llamada \"%s\"!title" +msgid "You already have a collection called \"%s\"!" +msgstr "¡Ya tienes una colección llamada \"%s\"!" -#: mediagoblin/edit/views.py:290 +#: mediagoblin/edit/views.py:326 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:307 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "Estás editando la colección de otro usuario/a. Ten cuidado." @@ -240,54 +277,62 @@ msgstr "No hay directorio activo para este tema\n\n\n" 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/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Lo sentidos, No soportamos ese tipo de archivo :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Ha fallado la conversión de vÃdeo" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "ID del Cliente" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Locación" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "Siguiente URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "Permitir" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "Denegar" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "Nombre" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "El nombre del cliente OAuth" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "Descripción" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "Tipo" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -297,17 +342,17 @@ msgid "" " 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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "Redireccionar URI" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "Este campo es requerido para los clientes públicos" @@ -315,7 +360,22 @@ msgstr "Este campo es requerido para los clientes públicos" msgid "The client {0} has been registered!" msgstr "¡El cliente {0} ha sido registrado!" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Archivo inválido para el formato seleccionado." @@ -323,80 +383,74 @@ msgstr "Archivo inválido para el formato seleccionado." msgid "File" msgstr "Archivo" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Debes proporcionar un archivo." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" -msgstr "¡Yujú! ¡Enviado!" - -#: mediagoblin/submit/views.py:211 mediagoblin/user_pages/views.py:215 -#, python-format -msgid "You already have a collection called \"%s\"!" -msgstr "¡Ya tienes una colección llamada \"%s\"!" +msgstr "¡Yuju! ¡Enviado!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "¡Colección \"%s\" añadida!" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Imagen de 404 goblin estresándose" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "¡Ups!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Parece no haber una página en esta dirección. ¡Lo sentimos!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Si estás seguro que la dirección es correcta, puede ser que la pagina haya sido movida o borrada." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Logo de MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "¡Verifica tu email!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+Agregar contenido" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "+ Añadir colección" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Ver tu perfil" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "cerrar sesión" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Salir " - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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 "Conectarse" +msgstr "Iniciar sesión" + +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Cambiar la configuración de la cuenta" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Añadir contenido" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Crear nueva colección" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "ProveÃdo por <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un proyecto <a href=\"http://gnu.org/\">GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -404,31 +458,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Imagen de un goblin estresándose" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Explorar" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Hola, ¡bienvenido a este sitio de MediaGoblin!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 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/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "¿Aún no tienes una? ¡Es fácil!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -436,17 +494,10 @@ msgid "" " <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 una cuenta en este sitio</a>\n o\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instala Mediagoblin en tu propio servidor</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "El contenido más reciente" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -549,47 +600,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Adjuntos" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Guardar cambios" +#: 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Editando el perfil de %(username)s" @@ -604,13 +693,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Original" @@ -629,7 +717,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "Archivo original" @@ -637,21 +725,71 @@ msgstr "Archivo original" msgid "WebM file (Vorbis codec)" msgstr "Archivo WebM (códec Vorbis)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Lo sentimos, este video no va funcionar porque\n<span class=\"whitespace other\" title=\"Tab\">»</span> Tu navegador web no soporta HTML5\n<span class=\"whitespace other\" title=\"Tab\">»</span> video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Tú puedes conseguir un navegador web moderno que\n<span class=\"whitespace other\" title=\"Tab\">»</span> puede reproducir este vÃdeo en <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!" +" 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:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "Archivo WebM (640p; VP8/Vorbis)" @@ -659,12 +797,6 @@ msgstr "Archivo WebM (640p; VP8/Vorbis)" msgid "Add a collection" msgstr "Añadir una colección" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Añadir " - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -681,43 +813,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Borrar" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -msgstr "<p>\n%(collection_description)s\n</p>" - #: 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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Eliminar permanentemente" - #: 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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -728,76 +857,62 @@ msgstr "Hola %(username)s,\n%(comment_author)s comentó tu publicación (%(comm #: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 #, python-format msgid "%(username)s's media" -msgstr "Contenidos de %(username)s" +msgstr "Contenido de %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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>'s" +msgstr "Contenido de <a href=\"%(user_url)s\">%(username)s</a>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Imágenes para %(media_title)s" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Añadir un comentario" -#: 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 "Puedes usar <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> para el formato." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Añade un comentario " -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "en" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Añadido en</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "Adjuntos" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "Agregar adjunto" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "Añadir %(title)s a la colección" +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:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: 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 "Puedes hacer un seguimiento del estado de tu contenido siendo procesado aquÃ." +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" @@ -846,80 +961,64 @@ msgstr "Alguien ya registró una cuenta con ese nombre de usuario, pero todavÃa 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\">acceder</a> y reenviarlo." +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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Cambiar la configuración de la cuenta" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Explorar colecciones" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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 agregado nada." - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:157 -msgid "Add media" -msgstr "Añadir contenido" +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:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "<br />⎠<a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(borrar)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "En la colección" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "En las colecciones (%(collected)s)" +#: 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/geolocation_map.html:25 -msgid "Location" -msgstr "Locación" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "Ver en <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Todos los derechos reservados" @@ -950,23 +1049,64 @@ msgstr "Más viejo" msgid "Tagged with" msgstr "Marcado con" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "No se pudo leer el archivo de imagen." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: 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:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "Incluir una nota" @@ -974,74 +1114,69 @@ msgstr "Incluir una nota" msgid "commented on your post" msgstr "comentó tu publicación" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Ups, tu comentario estaba vacÃo." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "¡Tu comentario ha sido publicado!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Por favor, revisa tus entradas e inténtalo de nuevo." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "Tienes que seleccionar o añadir una colección" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "%s\" ya está en la colección \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "\"%s\" añadido a la colección \"%s\"" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "Por favor, revisa tus entradas e inténtalo de nuevo." - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "Al parecer algunos de los ficheros en esta entrada se han perdido. Borrando igualmente." - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Eliminaste el contenido" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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. Proceder con precaución." +msgstr "Estás a punto de eliminar un contenido de otro usuario. Procede con precaución." -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "Borraste el Ãtem de la colección." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 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:393 +#: mediagoblin/user_pages/views.py:389 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:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "Borraste la colección \"%s\"" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 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:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 4996877b..4b319ebd 100644 --- a/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po index 2037f36e..028ab5d4 100644 --- a/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -19,82 +19,96 @@ msgstr "" "Language: fa\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "نام کاربری" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "گذرواٰژه" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "آدرس ایمیل" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "Ù…ØªØ§Ø³ÙØ§Ù†Ù‡ØŒØ«Ø¨ØªÙ†Ø§Ù… به طور موقت غیر ÙØ¹Ø§Ù„ است." -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "Ù…ØªØ§Ø³ÙØ§Ù†Ù‡ کاربری با این نام کاربری وجود دارد." -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "ایمیل شما تایید شد.شما Ù…ÛŒ توانید ØØ§Ù„ا وارد شوید،نمایه خود را ویرایش کنید Ùˆ تصاویر خود را ثبت کنید!" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "این کد تاییدیه یا شناسه کاربری صØÛŒØ نیست." -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "ایمیل تاییدیه باز ارسال شد." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "عنوان" @@ -103,8 +117,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +133,11 @@ msgstr "برچسب" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -162,60 +176,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -231,54 +266,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -288,17 +331,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -306,7 +349,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "ÙØ§ÛŒÙ„ÛŒ نا معتبر برای نوع رسانه داده شده." @@ -314,75 +372,74 @@ msgstr "ÙØ§ÛŒÙ„ÛŒ نا معتبر برای نوع رسانه داده شده." msgid "File" msgstr "ÙØ§ÛŒÙ„" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "شما باید ÙØ§ÛŒÙ„ÛŒ ارايه بدهید." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "هورا!ثبت شد!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "اوه" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Ù…ØªØ§Ø³ÙØ§Ù†Ù‡ به نظر نمی رسد Ú©Ù‡ چنین ØµÙØÙ‡ ای در این آدرس وجود داشته باشد!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -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:65 +msgid "log out" +msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "لوگو مدیاگوبلین" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: 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:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -390,31 +447,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -422,17 +483,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -535,47 +589,85 @@ msgid "" "%(verification_url)s" msgstr "سلام %(username)s,\n\nبرای ÙØ¹Ø§Ù„ سازی شناسه کاربری گنو مدیاگوبلین خود ،پیوند زیر را در مرورگر خود باز کنید.\n\n%(verification_url)s" +#: 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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "در ØØ§Ù„ ویرایش نمایه %(username)s" @@ -590,13 +682,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -615,7 +706,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -623,21 +714,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -645,12 +786,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -667,43 +802,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -716,67 +848,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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: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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -838,74 +956,58 @@ msgstr "اگر شما آن کاربر هستید Ùˆ ایمیل تایید خود 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -936,23 +1038,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -960,74 +1103,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 8262b37f..ada992ce 100644 --- a/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po index 560484bb..b4c76bd2 100644 --- a/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po @@ -1,11 +1,14 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # <a5565930@nepwk.com>, 2011. +# <alexispay@gmail.com>, 2012. # <chesuidayeur@yahoo.fr>, 2011. +# <crash_bibit@hotmail.com>, 2013. # <joehillen@gmail.com>, 2011. +# Laurent Pointecouteau <hell_pe@no-log.org>, 2013. # <marktraceur@gmail.com>, 2011. # <maxineb@members.fsf.org>, 2011. # <transifex@wandborg.se>, 2011. @@ -14,8 +17,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+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" @@ -25,82 +28,96 @@ msgstr "" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Nom d'utilisateur ou adresse de courriel invalide." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Nom d'utilisateur" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Mot de passe" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Adresse e-mail" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" -msgstr "" - -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" +msgstr "Nom d'utilisateur ou email" -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 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 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 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 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "E-mail de vérification renvoyé." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 msgid "" -"An email has been sent with instructions on how to change your password." +"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:273 +#: mediagoblin/auth/views.py:261 +msgid "Couldn't find someone with that username." +msgstr "Nom d'utilisateur introuvable." + +#: mediagoblin/auth/views.py:264 +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:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "" - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." -msgstr "" +msgstr "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titre" @@ -109,13 +126,13 @@ msgid "Description of this work" msgstr "Descriptif pour ce travail" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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 "" +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" @@ -123,13 +140,13 @@ msgstr "Tags" #: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 msgid "Separate tags by commas." -msgstr "" +msgstr "Séparez les champs avec des virgules." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Légende" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "La légende ne peut pas être laissée vide." @@ -137,12 +154,12 @@ msgstr "La légende ne peut pas être laissée vide." msgid "" "The title part of this media's address. You usually don't need to change " "this." -msgstr "" +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 "" +msgstr "Licence" #: mediagoblin/edit/forms.py:50 msgid "Bio" @@ -154,7 +171,7 @@ msgstr "Site web" #: mediagoblin/edit/forms.py:58 msgid "This address contains errors" -msgstr "" +msgstr "Cette adresse contiens des erreurs" #: mediagoblin/edit/forms.py:63 msgid "Old password" @@ -162,129 +179,158 @@ msgstr "Ancien mot de passe." #: mediagoblin/edit/forms.py:64 msgid "Enter your old password to prove you own this account." -msgstr "" +msgstr "Entrez votre ancien mot de passe pour prouver que vous êtes bien le propriétaire de ce compte." #: mediagoblin/edit/forms.py:67 msgid "New password" +msgstr "Nouveau mot de passe" + +#: mediagoblin/edit/forms.py:74 +msgid "License preference" msgstr "" -#: mediagoblin/edit/forms.py:72 -msgid "Email me when others comment on my media" +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:82 +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:94 msgid "The title can't be empty" -msgstr "" +msgstr "Le titre ne peut être vide" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Description de cette collection" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "Le titre affiché dans l'URL de la collection. Vous n'avez généralement pas besoin d'y toucher." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" -msgstr "" - -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" -msgstr "" +msgstr "Les changements apportés au profile ont étés sauvegardés" -#: mediagoblin/edit/views.py:252 +#: mediagoblin/edit/views.py:241 msgid "Wrong password" msgstr "Mauvais mot de passe" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:252 +msgid "Account settings saved" +msgstr "Les changements des préférences du compte ont étés sauvegardés" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Vous devez confirmer la suppression de votre compte." + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Vous avez déjà une collection appelée \"%s\" !" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Vous éditez la collection d'un autre utilisateurs. Faites attention." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "" +msgstr "Impossible de lier le thème... Aucun thème associé\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" -msgstr "" +msgstr "Aucun répertoire \"asset\" pour ce thème\n" #: 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 :(" +#: 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/video/processing.py:35 +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 +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/video/processing.py:36 msgid "Video transcoding failed" -msgstr "" +msgstr "L'encodage de la vidéo à échoué" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Position" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "Autoriser" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "Refuser" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Nom" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Description" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Type" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -294,25 +340,40 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "URL de redirection" -#: mediagoblin/plugins/oauth/forms.py:55 +#: mediagoblin/plugins/oauth/forms.py:54 msgid "" "The redirect URI for the applications, this field\n" " is <strong>required</strong> for public clients." -msgstr "" +msgstr "L'URI de redirection pour l'application, ce champ est <strong>requis</strong> pour les clients publics" -#: mediagoblin/plugins/oauth/forms.py:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Ce champ est requis pour les clients publics" #: mediagoblin/plugins/oauth/views.py:59 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/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Le fichier envoyé ne correspond pas au type de média." @@ -320,129 +381,125 @@ msgstr "Le fichier envoyé ne correspond pas au type de média." msgid "File" msgstr "Fichier" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Il vous faut fournir un fichier." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Youhou, c'est envoyé !" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Image de 404 gobelin angoissé" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Zut !" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Il ne semble pas y avoir de page à cette adresse. Désolé !" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Si vous êtes sûr que l'adresse est correcte, peut-être la page que vous recherchez a été déplacée ou supprimée." +msgstr "Collection \"%s\" ajoutée !" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Logo MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Vérifiez votre adresse e-mail !" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "Déconnexion" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" +msgstr "Changer les paramètres du compte" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Ajouter des médias" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Créer une nouvelle collection" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, 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/error.html:24 +msgid "Image of goblin stressing out" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Explorer" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" -msgstr "Bonjour, et bienvenu sur ce site MediaGoblin !" +msgstr "Bonjour, et bienvenue sur ce site MediaGoblin !" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "" +msgstr "Pour ajouter vos propres médias, commenter, et bien plus encore, vous pouvez vous connecter avec votre compte MediaGoblin" -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Vous n'en avez pas ? C'est facile !" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, 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 "" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Créez un compte sur ce site</a>\n ou\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Déployez MediaGoblin sur votre propre serveur</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Tout derniers media" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." -msgstr "" +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 @@ -462,25 +519,25 @@ 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 "" +msgstr "Aucune entrée ayant échoué !" #: mediagoblin/templates/mediagoblin/admin/panel.html:92 msgid "Last 10 successful uploads" -msgstr "" +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 "" +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 "" +msgstr "Enregistrez votre nouveau mot de passe" #: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 msgid "Set password" -msgstr "" +msgstr "Enregistrez votre mot de passe" #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 @@ -541,47 +598,85 @@ msgid "" "%(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/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 "" +msgstr "Éditer les pièces jointes de %(media_title)s" -#: 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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Pièces jointes" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Enregistrer les modifications" +#: 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:47 +#: 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:41 +#: 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:59 +msgid "Delete my account" msgstr "" #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "Modification de %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Modification du profil de %(username)s" @@ -596,13 +691,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 msgid "Download" -msgstr "" +msgstr "Télécharger" #: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Original" @@ -611,56 +705,100 @@ msgid "" "Sorry, this audio will not work because \n" "\tyour web browser does not support HTML5 \n" "\taudio." -msgstr "" +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 "" +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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" -msgstr "" +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/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/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/video.html:40 +#: 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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" -msgstr "" +msgstr "fichier WebM (640p; VP8/Vorbis)" #: mediagoblin/templates/mediagoblin/submit/collection.html:26 msgid "Add a collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" +msgstr "Ajouter une collection" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" -msgstr "" +msgstr "Ajoutez votre média" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format @@ -670,44 +808,41 @@ 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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Effacer" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Voulez-vous vraiment supprimer %(title)s ?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 "" +msgstr "Voulez vous vraiment retirer %(media_title)s de %(collection_title)s ?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 @@ -715,76 +850,62 @@ msgstr "" msgid "" "Hi %(username)s,\n" "%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" -msgstr "" +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:37 +#: 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:46 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" -msgstr "" +msgstr "â– Parcourir les médias de <a href=\"%(user_url)s\">%(username)s</a>" -#: 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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 "" +msgstr "Ajouter un commentaire" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" -msgstr "" +msgstr "Ajouter ce commentaire" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "à " -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" +msgstr "<h3>Ajouté le</h3>\n<p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +msgstr "Ajouter une nouvelle collection" #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 msgid "" @@ -793,7 +914,7 @@ msgstr "Vous pouvez suivre l'état des médias en cours de traitement pour votre #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 msgid "Your last 10 successful uploads" -msgstr "" +msgstr "Vos 10 derniers envois réussis" #: mediagoblin/templates/mediagoblin/user_pages/user.html:31 #: mediagoblin/templates/mediagoblin/user_pages/user.html:89 @@ -844,85 +965,69 @@ msgstr "Si c'est de vous qu'il s'agit, mais que vous avez perdu l'e-mail de vér 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Ajouter des médias" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" +msgstr "Tous droits réservés" #: mediagoblin/templates/mediagoblin/utils/pagination.html:39 msgid "↠Newer" -msgstr "" +msgstr "↠Le plus récent" #: mediagoblin/templates/mediagoblin/utils/pagination.html:45 msgid "Older →" -msgstr "" +msgstr "Le plus vieux →" #: mediagoblin/templates/mediagoblin/utils/pagination.html:48 msgid "Go to page:" @@ -931,109 +1036,145 @@ msgstr "Aller à la page :" #: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 #: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 msgid "newer" -msgstr "" +msgstr "le plus récent" #: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 #: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 msgid "older" -msgstr "" +msgstr "le plus vieux" #: mediagoblin/templates/mediagoblin/utils/tags.html:20 msgid "Tagged with" -msgstr "" +msgstr "Taggé avec" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 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/user_pages/forms.py:23 +msgid "Comment" msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" -msgstr "" +msgstr "-- Sélectionner --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "Inclure une note" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" -msgstr "" +msgstr "a commenté votre post" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Oups, votre commentaire était vide." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Votre commentaire a été posté !" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Veuillez vérifier vos entrées et réessayer." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "Vous devez sélectionner ou ajouter une collection" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "\"%s\" est déjà dans la collection \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" +msgstr "\"%s\" as été ajouté à la collection \"%s\"" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Vous avez supprimé le media." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "Vous avez supprimé cet élément de la collection." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "L'élément n'as pas été supprimé car vous n'avez pas confirmé votre certitude." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +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:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "Vous avez supprimé la collection \"%s\"" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "La collection n'as pas été supprimée car vous n'avez pas confirmé votre certitude" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 Binary files differnew file mode 100644 index 00000000..ce2963f7 --- /dev/null +++ b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po new file mode 100644 index 00000000..12d932c8 --- /dev/null +++ b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1172 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# <genghiskhan@gmx.ca>, 2012. +# Isratine Citizen <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-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\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: he\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 +msgid "Username" +msgstr "×©× ×ž×©×ª×ž×©" + +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 +msgid "Password" +msgstr "סיסמה" + +#: mediagoblin/auth/forms.py:60 +msgid "Email address" +msgstr "כתובת דו×״ל" + +#: mediagoblin/auth/forms.py:78 +msgid "Username or email" +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:174 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "כתובת הדו×״ל שלך ×ומתה. כעת ב×פשרותך להתחבר, לערוך ×ת ×“×™×•×§× ×š, ולשלוח ×ª×ž×•× ×•×ª!" + +#: mediagoblin/auth/views.py:180 +msgid "The verification key or user id is incorrect" +msgstr "מפתח ×”×ימות ×ו זהות משתמש ×”×™× × ×©×’×•×™×™×" + +#: mediagoblin/auth/views.py:198 +msgid "You must be logged in so we know who to send the email to!" +msgstr "עליך להתחבר על ×ž× ×ª ×©× ×“×¢ ×ל מי לשלוח ×ת הדו×״ל!" + +#: mediagoblin/auth/views.py:206 +msgid "You've already verified your email address!" +msgstr "כבר ×ימתת ×ת כתובת הדו×״ל שלך!" + +#: mediagoblin/auth/views.py:219 +msgid "Resent your verification email." +msgstr "שלח שוב ×ת דו×״ל ×”×ימות שלך." + +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "דו×״ל × ×©×œ×— בצירוף הור×ות ×‘× ×•×’×¢ לכיצד × ×™×ª×Ÿ ×œ×©× ×•×ª ×ת סיסמתך." + +#: mediagoblin/auth/views.py:271 +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:328 +msgid "You can now log in using your new password." +msgstr "כעת ביכולתך להתחבר ב×מצעות סיסמתך החדשה." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 +#: 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:97 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:101 +msgid "Slug" +msgstr "חשופית" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 +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:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 +msgid "Email me when others comment on my media" +msgstr "שלח לי דו×״ל ×›×שר ××—×¨×™× ×ž×’×™×‘×™× ×¢×œ המדיה שלי" + +#: mediagoblin/edit/forms.py:94 +msgid "The title can't be empty" +msgstr "הכותרת ×œ× ×™×›×•×œ×” להיות ריקה" + +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "תי×ור ×וסף ×–×”" + +#: mediagoblin/edit/forms.py:103 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "×זור הכותרת של כתובת ×וסף ×–×”. לרוב ×ין הכרח ×œ×©× ×•×ª ×ת חלק ×–×”." + +#: mediagoblin/edit/views.py:66 +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:241 +msgid "Wrong password" +msgstr "סיסמה שגויה" + +#: mediagoblin/edit/views.py:252 +msgid "Account settings saved" +msgstr "הגדרות חשבון × ×©×ž×¨×•" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "כבר יש לך ×וסף שקרוי ×‘×©× \"%s\"!" + +#: mediagoblin/edit/views.py:326 +msgid "A collection with that slug already exists for this user." +msgstr "×וסף ×¢× ×—×©×•×¤×™×ª זו כבר ×§×™×™× ×¢×‘×•×¨ משתמש ×–×”." + +#: mediagoblin/edit/views.py:343 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "×תה עורך ×וסף של משתמש ×חר. המשך בזהירות." + +#: mediagoblin/gmg_commands/theme.py:58 +msgid "Cannot link theme... no theme set\n" +msgstr "×œ× × ×™×ª×Ÿ לקשר ×ל מוטיב... ×œ× ×”×•×’×“×¨ מוטיב\n" + +#: mediagoblin/gmg_commands/theme.py:71 +msgid "No asset directory for this theme\n" +msgstr "×ין מדור × ×›×¡ עבור מוטיב ×–×”\n" + +#: mediagoblin/gmg_commands/theme.py:74 +msgid "However, old link directory symlink found; removed.\n" +msgstr "בכל ×ופן, קישור מדור symlink × ×ž×¦×; הוסר.\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 "" + +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 +msgid "Sorry, I don't support that file type :(" +msgstr "צר לי, ××™× × ×™ תומך בטיפוס קובץ ×–×” :(" + +#: mediagoblin/media_types/video/processing.py:36 +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> - הלקוח יכול\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:59 +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:172 +msgid "Invalid file given for media type." +msgstr "× ×™×ª×Ÿ קובץ שגוי עבור טיפוס מדיה." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "קובץ" + +#: mediagoblin/submit/views.py:51 +msgid "You must provide a file." +msgstr "עליך לספק קובץ." + +#: mediagoblin/submit/views.py:97 +msgid "Woohoo! Submitted!" +msgstr "הידד! × ×©×œ×—!" + +#: mediagoblin/submit/views.py:146 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "×וסף \"%s\" התווסף!" + +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" +msgstr "×מת ×ת הדו×״ל שלך!" + +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "×”×ª× ×ª×§×•×ª" + +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:79 +#, 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:86 +msgid "Change account settings" +msgstr "×©× ×” הגדרות חשבון" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "הוספת מדיה" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "צור ×וסף חדש" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, 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/base.html:125 +#, 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/error.html:24 +msgid "Image of goblin stressing out" +msgstr "×ª×ž×•× ×” של גובלין מת×מץ יתר על המידה" + +#: mediagoblin/templates/mediagoblin/root.html:31 +msgid "Explore" +msgstr "לחקור" + +#: mediagoblin/templates/mediagoblin/root.html:33 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "×©×œ×•× ×œ×š, ברוך בו×ך ×ל ×תר MediaGoblin ×–×”!" + +#: mediagoblin/templates/mediagoblin/root.html:35 +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/root.html:36 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "בכדי להוסיף ×ת המדיה שלך, ×œ×”×©×™× ×ª×’×•×‘×•×ª, ועוד, ביכולתך להתחבר ×¢× ×—×©×‘×•×Ÿ MediaGoblin." + +#: mediagoblin/templates/mediagoblin/root.html:38 +msgid "Don't have one yet? It's easy!" +msgstr "×ין ברשותך חשבון עדיין? ×–×” קל!" + +#: mediagoblin/templates/mediagoblin/root.html:39 +#, 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/root.html:47 +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/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:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "תצריפי×" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:52 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +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:47 +#: 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:59 +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/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/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/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/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 "%(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:53 +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:94 +msgid "Add a comment" +msgstr "הוסף תגובה" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +msgid "Add this comment" +msgstr "הוסף ×ת תגובה זו" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 +msgid "at" +msgstr "×צל" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 +#, python-format +msgid "" +"<h3>Added on</h3>\n" +" <p>%(date)s</p>" +msgstr "<h3>הוסף בת×ריך</h3>\n <p>%(date)s</p>" + +#: 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 "× ×“×¨×© ×ימות דו×״ל" + +#: 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:80 +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/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:56 +msgid "commented on your post" +msgstr "הגיב/×” על פרסומך" + +#: mediagoblin/user_pages/views.py:166 +msgid "Oops, your comment was empty." +msgstr "×ופס, תגובתך היתה ריקה." + +#: mediagoblin/user_pages/views.py:172 +msgid "Your comment has been posted!" +msgstr "תגובתך פורסמה!" + +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "×× × ×‘×“×•×§ ×ת רשומותיך ×•× ×¡×” שוב." + +#: mediagoblin/user_pages/views.py:237 +msgid "You have to select or add a collection" +msgstr "עליך לבחור ×ו להוסיף ×וסף" + +#: mediagoblin/user_pages/views.py:248 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" כבר ×§×™×™× ×‘×וסף \"%s\"" + +#: mediagoblin/user_pages/views.py:264 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" התווסף ×ל ×”×וסף \"%s\"" + +#: mediagoblin/user_pages/views.py:286 +msgid "You deleted the media." +msgstr "מחקת ×ת מדיה זו." + +#: mediagoblin/user_pages/views.py:293 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "המדיה ×œ× × ×ž×—×§×” מכיוון ×©×œ× ×¡×™×ž× ×ª ש×תה בטוח." + +#: mediagoblin/user_pages/views.py:301 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "בחרת למחוק מדיה של משתמש ×חר. המשך בזהירות." + +#: mediagoblin/user_pages/views.py:375 +msgid "You deleted the item from the collection." +msgstr "מחקת ×ת הפריט מן ×וסף ×–×”." + +#: mediagoblin/user_pages/views.py:379 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "הפריט ×œ× ×”×•×¡×¨ מכיוון ×©×œ× ×¡×™×ž× ×ª ש×תה בטוח." + +#: mediagoblin/user_pages/views.py:389 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "בחרת למחוק פריט מן ×וסף של משתמש ×חר. המשך בזהירות." + +#: mediagoblin/user_pages/views.py:422 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "מחקת ×ת ×”×וסף \"%s\"" + +#: mediagoblin/user_pages/views.py:429 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "×”×וסף ×œ× ×”×•×¡×¨ מכיוון ×©×œ× ×¡×™×ž× ×ª ש×תה בטוח." + +#: mediagoblin/user_pages/views.py:439 +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 Binary files differindex b55044f7..d9addaa6 100644 --- a/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po index d16e0022..73180e86 100644 --- a/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -20,82 +20,96 @@ msgstr "" "Language: ia\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Nomine de usator" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Contrasigno" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Adresse de e-posta" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titulo" @@ -104,8 +118,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -120,11 +134,11 @@ msgstr "Etiquettas" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -163,60 +177,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -232,54 +267,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -289,17 +332,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -307,7 +350,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "" @@ -315,75 +373,74 @@ msgstr "" msgid "File" msgstr "File" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:28 -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:70 +#: 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:50 -msgid "MediaGoblin logo" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -391,31 +448,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -423,17 +484,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -536,47 +590,85 @@ msgid "" "%(verification_url)s" 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/edit.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit.html:35 -#, python-format -msgid "Editing %(media_title)s" +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "" @@ -591,13 +683,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -616,7 +707,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -624,21 +715,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -646,12 +787,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -668,43 +803,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -717,67 +849,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" +msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -839,74 +957,58 @@ msgstr "" 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -937,23 +1039,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -961,74 +1104,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differnew file mode 100644 index 00000000..376aace4 --- /dev/null +++ b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.mo 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..1b298a64 --- /dev/null +++ b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1171 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# <tryggvib@fsfi.is>, 2012. +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\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: is_IS\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 +msgid "Username" +msgstr "Notandanafn" + +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 +msgid "Password" +msgstr "Lykilorð" + +#: mediagoblin/auth/forms.py:60 +msgid "Email address" +msgstr "Netfang" + +#: mediagoblin/auth/forms.py:78 +msgid "Username or email" +msgstr "Notandanafn eða 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:174 +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:180 +msgid "The verification key or user id is incorrect" +msgstr "Staðfestingarlykillinn eða notendaauðkennið er rangt" + +#: mediagoblin/auth/views.py:198 +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:206 +msgid "You've already verified your email address!" +msgstr "Þú hefur staðfest netfangið þitt!" + +#: mediagoblin/auth/views.py:219 +msgid "Resent your verification email." +msgstr "Endursendi staðfestingartölvupóst" + +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 +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:271 +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:328 +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:93 +#: 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:97 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:101 +msgid "Slug" +msgstr "Vefslóðarormur" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 +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 "Old password" +msgstr "Gamla lykilorðið" + +#: mediagoblin/edit/forms.py:64 +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:67 +msgid "New password" +msgstr "Nýtt lykilorð" + +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 +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:94 +msgid "The title can't be empty" +msgstr "Þessi titill getur verið innihaldslaus" + +#: mediagoblin/edit/forms.py:96 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:103 +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/views.py:66 +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 "" + +#: 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:241 +msgid "Wrong password" +msgstr "Vitlaust lykilorð" + +#: mediagoblin/edit/views.py:252 +msgid "Account settings saved" +msgstr "Aðgangsstillingar vistaðar" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Þú hefur nú þegar albúm sem kallast \"%s\"!" + +#: mediagoblin/edit/views.py:326 +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:343 +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/gmg_commands/theme.py:58 +msgid "Cannot link theme... no theme set\n" +msgstr "Get ekki hlekkjað à þema... ekkert þema stillt\n" + +#: mediagoblin/gmg_commands/theme.py:71 +msgid "No asset directory for this theme\n" +msgstr "Engin eignamappa fyrir þetta þema\n" + +#: mediagoblin/gmg_commands/theme.py:74 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Fann samt gamlan táknrænan tengil á möppu; fjarlægður.\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 "" + +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 +msgid "Sorry, I don't support that file type :(" +msgstr "Ég styð þvà miður ekki þessa gerð af skrám :(" + +#: mediagoblin/media_types/video/processing.py:36 +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:59 +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 "" + +#: 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 "Bæta við" + +#: mediagoblin/processing/__init__.py:172 +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:51 +msgid "You must provide a file." +msgstr "Þú verður að gefa upp skrá." + +#: mediagoblin/submit/views.py:97 +msgid "Woohoo! Submitted!" +msgstr "Jibbà jei! Það tókst að senda inn!" + +#: mediagoblin/submit/views.py:146 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Albúmið \"%s\" var búið til!" + +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" +msgstr "Staðfestu netfangið þitt!" + +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "útskrá" + +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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áning" + +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Breyta stillingum notandaaðgangs" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Senda inn efni" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Búa til nýtt albúm" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, 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/base.html:125 +#, 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/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Mynd af durt à stresskasti" + +#: mediagoblin/templates/mediagoblin/root.html:31 +msgid "Explore" +msgstr "Skoða" + +#: mediagoblin/templates/mediagoblin/root.html:33 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hæ! Gakktu à bæinn á þetta MediaGoblin vefsvæði!" + +#: mediagoblin/templates/mediagoblin/root.html:35 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Þetta vefsvæði keyrira á <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/root.html:36 +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/root.html:38 +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/root.html:39 +#, 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>\n eða\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Settu upp þinn eigin margmiðlunarþjón</a>" + +#: mediagoblin/templates/mediagoblin/root.html:47 +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/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:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Viðhengi" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:52 +#: 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/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:47 +#: 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:59 +msgid "Delete my account" +msgstr "" + +#: 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/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/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/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/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 "" + +#: 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 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:53 +msgid "Remove" +msgstr "Fjarlægja" + +#: 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 "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 "" + +#: 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:94 +msgid "Add a comment" +msgstr "Bæta við athugasemd" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +msgid "Add this comment" +msgstr "Senda inn þessa athugasemd" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 +msgid "at" +msgstr "hjá" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 +#, python-format +msgid "" +"<h3>Added on</h3>\n" +" <p>%(date)s</p>" +msgstr "<h3>Bætt við:</h3>\n <p>%(date)s</p>" + +#: 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 "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 "" + +#: 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 "" + +#: 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 "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:80 +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/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 "Þú 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 "" + +#: 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:56 +msgid "commented on your post" +msgstr "skrifaði athugasemd við færsluna þÃna" + +#: mediagoblin/user_pages/views.py:166 +msgid "Oops, your comment was empty." +msgstr "ObbosÃ! Athugasemdin þÃn var innihaldslaus." + +#: mediagoblin/user_pages/views.py:172 +msgid "Your comment has been posted!" +msgstr "Athugasemdin þÃn var skráð!" + +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Vinsamlegast kÃktu á innsendingarnar þÃnar og reyndu aftur." + +#: mediagoblin/user_pages/views.py:237 +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:248 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" er nú þegar à albúminu \"%s\"" + +#: mediagoblin/user_pages/views.py:264 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" sett à albúmið \"%s\"" + +#: mediagoblin/user_pages/views.py:286 +msgid "You deleted the media." +msgstr "Þú eyddir þessu efni." + +#: mediagoblin/user_pages/views.py:293 +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:301 +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:375 +msgid "You deleted the item from the collection." +msgstr "Þú tókst þetta efni úr albúminu." + +#: mediagoblin/user_pages/views.py:379 +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:389 +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:422 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Þú eyddir albúminu \"%s\"" + +#: mediagoblin/user_pages/views.py:429 +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:439 +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 Binary files differindex 5658950c..62451511 100644 --- a/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po index fd04ba6e..e13345a7 100644 --- a/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -11,8 +11,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -22,82 +22,96 @@ msgstr "" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Nome utente" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Password" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Indirizzo email" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Nome utente o indirizzo email" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "La chiave di verifica o l'id utente è sbagliato" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Hai già verificato il tuo indirizzo email!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Rispedisci email di verifica" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Impossibile trovare qualcuno con questo nome utente o password." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 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:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titolo" @@ -106,8 +120,8 @@ msgid "Description of this work" msgstr "Descrizione di questo lavoro" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -122,11 +136,11 @@ msgstr "Tags" msgid "Separate tags by commas." msgstr "Separa le tags con la virgola." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -165,60 +179,81 @@ msgstr "Inserisci la vecchia password per dimostrare di essere il proprietario d msgid "New password" msgstr "Nuova password" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 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:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Cambiamenti del profilo salvati" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Password errata" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Impostazioni del profilo salvate" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Password errata" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -234,54 +269,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Mi dispiace, non supporto questo tipo di file :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Transcodifica video fallita" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Posizione" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -291,17 +334,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -309,7 +352,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "File non valido per il tipo di file multimediale indicato." @@ -317,75 +375,74 @@ msgstr "File non valido per il tipo di file multimediale indicato." msgid "File" msgstr "File" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Devi specificare un file." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Evviva! Caricato!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Immagine di 404 folletti che stressano" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Oops!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Non sembra esserci una pagina a questo indirizzo. Spiacente!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Se sei sicuro che l'indirizzo è corretto, forse la pagina che stai cercando è stata spostata o cancellata." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Simbolo di MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Verifica la tua email!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Aggiungi files multimediali" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Vedi il tuo profilo" - #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Esci" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" +msgstr "Cambia le impostazioni dell'account" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Aggiungi files multimediali" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Realizzato con <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un progetto <a href=\"http://gnu.org/\">GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -393,31 +450,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Esplora" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Ciao, benvenuto in questo sito MediaGoblin!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 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/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Non ne hai già uno? E' semplice!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -425,17 +486,10 @@ msgid "" " <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 un account in questo sito</a>\n oppure\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Installa MediaGoblin sul tuo server</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Files multimediali più recenti" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -538,47 +592,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Allegati" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Salva i cambiamenti" +#: 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Stai modificando il profilo di %(username)s" @@ -593,13 +685,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Originale" @@ -618,7 +709,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "File originario" @@ -626,21 +717,71 @@ msgstr "File originario" msgid "WebM file (Vorbis codec)" msgstr "File WebM (codec Vorbis)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Spiacente ma è impossibile visualizzare questo video perché\n\t il tuo browser web non supporta l'HTML5 \n\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Puoi scaricare un browser web moderno,\n\t in grado di visualizzare questo video, qui <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "File WebM (640p; VP8/Vorbis)" @@ -648,12 +789,6 @@ msgstr "File WebM (640p; VP8/Vorbis)" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Aggiungi" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -670,43 +805,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Elimina" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Vuoi davvero eliminare %(title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Elimina definitivamente" - #: 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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -719,67 +851,53 @@ msgstr "Ciao %(username)s,\n%(comment_author)s ha commentato il tuo post (%(comm msgid "%(username)s's media" msgstr "Files multimediali di %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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: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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Aggiungi un commento" -#: 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 "Puoi usare il <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> per la formattazione." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Aggiungi questo commento" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "a" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Aggiunto il</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "Allegati" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "Aggiungi allegato" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -841,74 +959,58 @@ msgstr "Se sei quella persona ma hai perso l'email di verifica, puoi <a href=\"% 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Cambia le impostazioni dell'account" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Aggiungi files multimediali" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/geolocation_map.html:25 -msgid "Location" -msgstr "Posizione" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "Visualizza su <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Tutti i diritti riservati" @@ -939,23 +1041,64 @@ msgstr "più vecchio" msgid "Tagged with" msgstr "Taggato con" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Impossibile leggere il file immagine." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -963,74 +1106,69 @@ msgstr "" msgid "commented on your post" msgstr "ha commentato il tuo post" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Oops, il tuo commento era vuoto." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Il tuo commento è stato aggiunto!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Hai eliminato il file." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex fc0bba8f..1344c9bd 100644 --- a/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po index 5fb16241..008a6d27 100644 --- a/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po @@ -1,15 +1,16 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # <averym@gmail.com>, 2011. +# <parlegon@gmail.com>, 2013. msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -19,82 +20,96 @@ msgstr "" "Language: ja\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "ユーザãƒãƒ¼ãƒ " -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "パスワード" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "メールアドレス" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "申ã—訳ã‚りã¾ã›ã‚“ãŒã€ã“ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã§ç™»éŒ²ã¯ç„¡åйã«ãªã£ã¦ã„ã¾ã™ã€‚" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "申ã—訳ã‚りã¾ã›ã‚“ãŒã€ãã®åå‰ã‚’æŒã¤ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã™ã§ã«å˜åœ¨ã—ã¦ã„ã¾ã™ã€‚" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "メアドãŒç¢ºèªã•れã¦ã„ã¾ã™ã€‚ã“れã§ã€ãƒã‚°ã‚¤ãƒ³ã—ã¦ãƒ—ãƒãƒ•ァイルを編集ã—ã€ç”»åƒã‚’æå‡ºã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ï¼" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "検証ã‚ーã¾ãŸã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼IDãŒé–“é•ã£ã¦ã„ã¾ã™" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "検証メールをå†é€ã—ã¾ã—ãŸã€‚" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "タイトル" @@ -103,8 +118,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +134,11 @@ msgstr "ã‚¿ã‚°" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "スラグ" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "スラグã¯å¿…è¦ã§ã™ã€‚" @@ -162,60 +177,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "ãã®ã‚¹ãƒ©ã‚°ã‚’æŒã¤ã‚¨ãƒ³ãƒˆãƒªã¯ã€ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯æ—¢ã«å˜åœ¨ã—ã¾ã™ã€‚" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -231,54 +267,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -288,17 +332,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -306,7 +350,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "" @@ -314,75 +373,74 @@ msgstr "" msgid "File" msgstr "ファイル" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "ファイルをæä¾›ã™ã‚‹å¿…è¦ãŒã‚りã¾ã™ã€‚" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "投稿終了ï¼" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:28 -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:70 +#: 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:50 -msgid "MediaGoblin logo" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -390,31 +448,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" -msgstr "" +msgstr "ã“ã‚“ã«ã¡ã¯ã€ã“ã®MediaGoblinサイトã¸ã‚ˆã†ã“ãï¼" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -422,17 +484,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -513,7 +568,7 @@ msgstr "ã“ã“ã§ä½œæˆï¼" #: mediagoblin/templates/mediagoblin/auth/login.html:51 msgid "Forgot your password?" -msgstr "" +msgstr "パスワードを忘れã¾ã—ãŸã‹ï¼Ÿ" #: mediagoblin/templates/mediagoblin/auth/register.html:28 #: mediagoblin/templates/mediagoblin/auth/register.html:36 @@ -535,47 +590,85 @@ msgid "" "%(verification_url)s" msgstr "%(username)s様ã¸\n\nGNU MediaGoblinアカウントを検証ã«ã™ã‚‹ã«ã¯ã€ã“ã®URLã‚’é–‹ã„ã¦ãã ã•ã„。\n\n%(verification_url)s" +#: 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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "%(username)sã•ã‚“ã®ãƒ—ãƒãƒ•ィールを編集ä¸" @@ -590,13 +683,12 @@ 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 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 msgid "Download" -msgstr "" +msgstr "ダウンãƒãƒ¼ãƒ‰" #: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -615,7 +707,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -623,21 +715,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -645,12 +787,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -667,22 +803,14 @@ 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:87 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 msgid "Edit" -msgstr "" +msgstr "編集" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -msgstr "" +msgstr "削除" #: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 #: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 @@ -690,20 +818,25 @@ msgstr "" msgid "Really delete %(title)s?" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -716,67 +849,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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: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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -838,74 +957,58 @@ msgstr "ã‚ãªãŸã®ç¢ºèªãƒ¡ãƒ¼ãƒ«ã‚’紛失ã—ãŸå ´åˆã€<a href=\"%(login_url 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -936,23 +1039,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -960,74 +1104,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differnew file mode 100644 index 00000000..69bf72bc --- /dev/null +++ b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.mo 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..ac87c90f --- /dev/null +++ b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1171 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# <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-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\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: ko_KR\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 +msgid "Username" +msgstr "ì‚¬ìš©ìž ì´ë¦„" + +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 +msgid "Password" +msgstr "비밀번호" + +#: mediagoblin/auth/forms.py:60 +msgid "Email address" +msgstr "email 주소" + +#: mediagoblin/auth/forms.py:78 +msgid "Username or email" +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 "죄송합니다. 사용ìžì™€ 해당 ì´ë©”ì¼ì€ ì´ë¯¸ 등ë¡ë˜ì–´ 있습니다." + +#: mediagoblin/auth/views.py:174 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "해당 email 주소가 ì´ë¯¸ ì¸ì¦ ë˜ì–´ 있습니다. 지금 로그ì¸í•˜ì‹œê³ ê³„ì • ì •ë³´ë¥¼ ìˆ˜ì •í•˜ê³ ì‚¬ì§„ì„ ì „ì†¡í•´ 보세요!" + +#: mediagoblin/auth/views.py:180 +msgid "The verification key or user id is incorrect" +msgstr "ì¸ì¦ 키 ë˜ëŠ” ì‚¬ìš©ìž IDê°€ 올바르지 않습니다." + +#: mediagoblin/auth/views.py:198 +msgid "You must be logged in so we know who to send the email to!" +msgstr "로그ì¸ì„ 하셔야 ê³ ë¸”ë¦°ì—서 ë©”ì¼ì„ 보낼 수 있습니다!" + +#: mediagoblin/auth/views.py:206 +msgid "You've already verified your email address!" +msgstr "ì´ë¯¸ ì¸ì¦ë°›ì€ email 주소를 ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤!" + +#: mediagoblin/auth/views.py:219 +msgid "Resent your verification email." +msgstr "ì¸ì¦ ë©”ì¼ì„ 다시 ë³´ë‚´ 주세요." + +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "비밀번호를 변경하는 ë°©ë²•ì— ëŒ€í•œ 설명서가 ë©”ì¼ë¡œ ì „ì†¡ ë˜ì—ˆìŠµë‹ˆë‹¤." + +#: mediagoblin/auth/views.py:271 +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:328 +msgid "You can now log in using your new password." +msgstr "ì´ì œ 새로운 비밀번호로 ë¡œê·¸ì¸ í•˜ì‹¤ 수 있습니다." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 +#: 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:97 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:101 +msgid "Slug" +msgstr "'슬러그'" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 +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 "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:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 +msgid "Email me when others comment on my media" +msgstr "ì œ ë¯¸ë””ì–´ì— ëŒ€í•œ 컨í…ì„ ì›í•œë‹¤ë©´, ë©”ì¼ì„ 보내주세요." + +#: mediagoblin/edit/forms.py:94 +msgid "The title can't be empty" +msgstr "ì œëª©ì€ ê³µë°±ì¼ ìˆ˜ 없습니다." + +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "모ìŒì§‘ì— ëŒ€í•œ 설명" + +#: mediagoblin/edit/forms.py:103 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/views.py:66 +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:241 +msgid "Wrong password" +msgstr "ìž˜ëª»ëœ ë¹„ë°€ë²ˆí˜¸" + +#: mediagoblin/edit/views.py:252 +msgid "Account settings saved" +msgstr "ê³„ì • ì„¤ì •ì´ ì €ìž¥ ë˜ì—ˆìŠµë‹ˆë‹¤." + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "\"%s\" 모ìŒì§‘ì„ ì´ë¯¸ ê°€ì§€ê³ ìžˆìŠµë‹ˆë‹¤!" + +#: mediagoblin/edit/views.py:326 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:343 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "다른 ìœ ì €ì˜ ëª¨ìŒì§‘ì„ ìˆ˜ì • 중 입니다. 주ì˜í•˜ì„¸ìš”." + +#: mediagoblin/gmg_commands/theme.py:58 +msgid "Cannot link theme... no theme set\n" +msgstr "í…Œë§ˆì— ì—°ê²°í• ìˆ˜ 없습니다... 테마 ì…‹ì´ ì—†ìŠµë‹ˆë‹¤.\n" + +#: mediagoblin/gmg_commands/theme.py:71 +msgid "No asset directory for this theme\n" +msgstr "ì´ í…Œë§ˆë¥¼ 위한 ì—ì…‹ ë””ë ‰í† ë¦¬ê°€ 없습니다.\n" + +#: mediagoblin/gmg_commands/theme.py:74 +msgid "However, old link directory symlink found; removed.\n" +msgstr "그런ë°, ì˜¤ëž˜ëœ ë””ë ‰í† ë¦¬ ì‹¬ë³¼ë¦ ë§í¬ë¥¼ 찾았습니다; 지워졌습니다.\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 "" + +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 +msgid "Sorry, I don't support that file type :(" +msgstr "죄송합니다. 해당 íƒ€ìž…ì˜ íŒŒì¼ì€ ì§€ì›í•˜ì§€ 않아요 :(" + +#: mediagoblin/media_types/video/processing.py:36 +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:59 +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:172 +msgid "Invalid file given for media type." +msgstr "알수없는 미디어 íŒŒì¼ ìž…ë‹ˆë‹¤." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "파ì¼" + +#: mediagoblin/submit/views.py:51 +msgid "You must provide a file." +msgstr "파ì¼ì„ 등ë¡í•˜ì…”야 합니다." + +#: mediagoblin/submit/views.py:97 +msgid "Woohoo! Submitted!" +msgstr "ì´í–!! 등ë¡í–ˆìŠµë‹ˆë‹¤!" + +#: mediagoblin/submit/views.py:146 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "\"%s\" 모ìŒì§‘ì´ ì¶”ê°€ë˜ì—ˆìŠµë‹ˆë‹¤!" + +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" +msgstr "ë©”ì¼ì„ 확ì¸í•˜ì„¸ìš”!" + +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" +msgstr "ê³„ì • ì„¤ì • 변경" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "미디어 추가" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, 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/base.html:125 +#, 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/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 +msgid "Explore" +msgstr "íƒìƒ‰" + +#: mediagoblin/templates/mediagoblin/root.html:33 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "안녕하세요! 미디어 ê³ ë¸”ë¦° 사ì´íŠ¸ì— ì˜¨ê±¸ í™˜ì˜ í•©ë‹ˆë‹¤!" + +#: mediagoblin/templates/mediagoblin/root.html:35 +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/root.html:36 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "ìžì‹ ì˜ ë¯¸ë””ì–´ë¥¼ ì¶”ê°€í•˜ê³ , ëŒ“ê¸€ì„ ë‚¨ê¸°ì„¸ìš”! 미디어 ê³ ë¸”ë¦° ê³„ì •ìœ¼ë¡œ ë‚´ì—ì„ í™•ì¸ í•˜ì‹¤ 수 있습니다!" + +#: mediagoblin/templates/mediagoblin/root.html:38 +msgid "Don't have one yet? It's easy!" +msgstr "ì•„ì§ ì•„ë¬´ê²ƒë„ ì—†ìœ¼ì‹œë‹¤êµ¬ìš”? 매우 쉽습니다!" + +#: mediagoblin/templates/mediagoblin/root.html:39 +#, 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/root.html:47 +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/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:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "첨부" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:52 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +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:47 +#: 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:59 +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/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/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/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/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:53 +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:94 +msgid "Add a comment" +msgstr "ë§ê¸€ 달기" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +msgid "Add this comment" +msgstr "ë§ê¸€ 추가" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 +msgid "at" +msgstr "ì—" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 +#, python-format +msgid "" +"<h3>Added on</h3>\n" +" <p>%(date)s</p>" +msgstr "<h3>부가 기능</h3>\n <p>%(date)s</p>" + +#: 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:80 +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/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:56 +msgid "commented on your post" +msgstr "ê²Œì‹œë¬¼ì— ë§ê¸€ì´ ë‹¬ë ¸ìŠµë‹ˆë‹¤." + +#: mediagoblin/user_pages/views.py:166 +msgid "Oops, your comment was empty." +msgstr "오우, ëŒ“ê¸€ì´ ë¹„ì—ˆìŠµë‹ˆë‹¤." + +#: mediagoblin/user_pages/views.py:172 +msgid "Your comment has been posted!" +msgstr "ëŒ“ê¸€ì´ ë“±ë¡ ë˜ì—ˆìŠµë‹ˆë‹¤!" + +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "확ì¸ì„ í•˜ì‹œê³ ë‹¤ì‹œ 시ë„하세요." + +#: mediagoblin/user_pages/views.py:237 +msgid "You have to select or add a collection" +msgstr "모ìŒì§‘ì„ ì¶”ê°€í•˜ê±°ë‚˜ 기존 모ìŒì§‘ì„ ì„ íƒí•˜ì„¸ìš”." + +#: mediagoblin/user_pages/views.py:248 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" 모ìŒì§‘ì´ ì´ë¯¸ 존재 합니다. \"%s\"" + +#: mediagoblin/user_pages/views.py:264 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" 모ìŒì§‘ì„ ì¶”ê°€í–ˆìŠµë‹ˆë‹¤. \"%s\"" + +#: mediagoblin/user_pages/views.py:286 +msgid "You deleted the media." +msgstr "미디어를 ì‚ì œ 했습니다." + +#: mediagoblin/user_pages/views.py:293 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "í™•ì¸ ì²´í¬ë¥¼ 하지 않았습니다. 미디어는 ì‚ì œë˜ì§€ 않았습니다." + +#: mediagoblin/user_pages/views.py:301 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "다른 ì‚¬ëžŒì˜ ë¯¸ë””ì–´ë¥¼ ì‚ì œí•˜ë ¤ê³ í•©ë‹ˆë‹¤. 다시 한번 확ì¸í•˜ì„¸ìš”." + +#: mediagoblin/user_pages/views.py:375 +msgid "You deleted the item from the collection." +msgstr "모ìŒì§‘ì— ìžˆëŠ” í•ëª©ì„ ì‚ì œ 했습니다." + +#: mediagoblin/user_pages/views.py:379 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "확ì¸ì„ 하지 않았습니다. í•ëª©ì€ ì‚ì œí•˜ì§€ 않았습니다." + +#: mediagoblin/user_pages/views.py:389 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "다른 사용ìžì˜ 모ìŒì§‘ì— ìžˆëŠ” í•ëª©ì„ ì‚ì œí•˜ì˜€ìŠµë‹ˆë‹¤. 주ì˜í•˜ì„¸ìš”." + +#: mediagoblin/user_pages/views.py:422 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "\"%s\" 모ìŒì§‘ì„ ì‚ì œí•˜ì…¨ìŠµë‹ˆë‹¤." + +#: mediagoblin/user_pages/views.py:429 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "확ì¸ì„ 하지 않았습니다. 모ìŒì§‘ì€ ì‚ì œí•˜ì§€ 않았습니다." + +#: mediagoblin/user_pages/views.py:439 +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 Binary files differindex c141e456..fe96d40e 100644 --- a/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po index 3b0cf98e..3fd26d23 100644 --- a/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -20,82 +20,96 @@ msgstr "" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Gebruikersnaam" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Wachtwoord" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "E-mail adres" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Gebruikersnaam of email-adres" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Onjuiste invoer" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "De verificatie sleutel of gebruikers-ID is onjuist" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Je hebt je e-mailadres al geverifieerd!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Verificatie e-mail opnieuw opgestuurd." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Kon niemand vinden met die gebruikersnaam of dat e-mailadres." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 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:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titel" @@ -104,8 +118,8 @@ msgid "Description of this work" msgstr "Beschrijving van dit werk" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -120,11 +134,11 @@ msgstr "Etiket" msgid "Separate tags by commas." msgstr "Hou labels gescheiden met komma's." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Slug" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "De slug kan niet leeg zijn" @@ -163,60 +177,81 @@ msgstr "Vul je oude wachtwoord in om te bewijzen dat dit jouw account is" msgid "New password" msgstr "Nieuw wachtwoord" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Profielaanpassingen opgeslagen" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Verkeerd wachtwoord" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Accountinstellingen opgeslagen" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Verkeerd wachtwoord" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -232,54 +267,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Sorry, dat bestandstype wordt niet ondersteunt." -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Locatie" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -289,17 +332,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -307,7 +350,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Verkeerd bestandsformaat voor mediatype opgegeven." @@ -315,75 +373,74 @@ msgstr "Verkeerd bestandsformaat voor mediatype opgegeven." msgid "File" msgstr "Bestand" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "U moet een bestand aangeven." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Mooizo! Toegevoegd!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Afbeelding van de 404 goblin onder stress" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Oeps!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Het lijkt erop dat er geen pagina bestaat op dit adres. Sorry!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Als je zeker weet dat het adres klopt is de pagina misschien verplaatst of verwijderd." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "MediaGoblin logo" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Verifieer je e-mailadres!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Media toevoegen" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Profiel weergeven" - #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Afmelden" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" +msgstr "Accountinstellingen aanpassen" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Voeg media toe" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Hier draait <a href=\"http://mediagoblin.org\">MediaGoblin</a>, een <a href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -391,31 +448,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Verkennen" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Hoi, welkom op deze MediaGoblin website!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Heb je er nog geen? Het is heel eenvoudig!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -423,17 +484,10 @@ msgid "" " <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\">Creëer een account op deze website</a>\n of\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Gebruik MediaGoblin op je eigen server</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Nieuwste media" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -536,47 +590,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Wijzigingen opslaan" +#: 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Het profiel aanpassen van %(username)s" @@ -591,13 +683,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Origineel" @@ -616,7 +707,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -624,21 +715,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Sorry, deze video werkt niet omdat je webbrowser geen HTML5 video ondersteunt." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Je kunt een moderne webbrowser die deze video af kan spelen krijgen op <a href=\"http://getfirefox.com\">http://getfirefox.com</a>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -646,12 +787,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Voeg toe" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -668,43 +803,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Verwijderen" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Zeker weten dat je %(title)s wil verwijderen?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Permanent 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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -717,67 +849,53 @@ msgstr "" msgid "%(username)s's media" msgstr "Media van %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Afbeelding voor %(media_title)s" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Geef een reactie" -#: 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 "Voor opmaak kun je <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> gebruiken." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Voeg dit bericht toe" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "op" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Toegevoegd op</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -839,74 +957,58 @@ msgstr "Als u die persoon bent, maar de verificatie e-mail verloren hebt, kunt u 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Accountinstellingen aanpassen" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Voeg media toe" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/geolocation_map.html:25 -msgid "Location" -msgstr "Locatie" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "Bekijken op <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Alle rechten voorbehouden" @@ -937,23 +1039,64 @@ msgstr "ouder" msgid "Tagged with" msgstr "Getagged met" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Kon het afbeeldingsbestand niet lezen." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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 <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> 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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -961,74 +1104,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Oeps, je bericht was leeg." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Je bericht is geplaatst!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Je hebt deze media verwijderd." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 986475ba..f58e6a45 100644 --- a/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po index 4156c07b..12d34b55 100644 --- a/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po @@ -1,16 +1,17 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: -# <odin.omdal@gmail.com>, 2011, 2012. +# <odin.omdal@gmail.com>, 2013. +# <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: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" -"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-10 13:31+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" @@ -19,92 +20,106 @@ msgstr "" "Language: nn_NO\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Ugyldig brukarnamn eller passord." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Dette feltet tek ikkje epostadresser." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Dette feltet krev ei epostadresse." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Brukarnamn" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Passord" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Epost" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Brukarnamn eller epost" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Ugyldig verdi" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "Registrering er slege av. Orsak." -#: mediagoblin/auth/views.py:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "Stadfestingsnykelen eller brukar-ID-en din er feil." -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Du har allereie verifisiert epostadressa." -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Send ein ny stadfestingsepost." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "Fann ingen med det brukarnamnet." + +#: mediagoblin/auth/views.py:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Fann ingen med det brukarnamnet eller passordet." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 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:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: 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 mediefila" +msgstr "Skildring av verk" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +134,11 @@ msgstr "Merkelappar" msgid "Separate tags by commas." msgstr "Separer merkelappar med komma." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Nettnamn" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Nettnamnet kan ikkje vera tomt" @@ -131,7 +146,7 @@ msgstr "Nettnamnet kan ikkje vera tomt" msgid "" "The title part of this media's address. You usually don't need to change " "this." -msgstr "Nettnamnet (adressetittel) for mediefila di. Trengst ikkje endrast." +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 @@ -162,123 +177,152 @@ msgstr "Skriv inn det gamle passordet ditt for Ã¥ stadfesta at du eig denne kont msgid "New password" msgstr "Nytt passord" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "Lisens-val" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "Dette vil vera standardvalet ditt for lisens." + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" -msgstr "" +msgstr "Send meg epost nÃ¥r andre kjem med innspel pÃ¥ verka mine." -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "Tittelen kjan ikkje vera tom" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Forklaringa til denne samlinga" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "Tittel-delen av denne samlinga si adresse. Du treng normalt sett ikkje endra denne." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "Eit innlegg med denne adressetittelen finst allereie." -#: mediagoblin/edit/views.py:86 +#: mediagoblin/edit/views.py:85 msgid "You are editing another user's media. Proceed with caution." -msgstr "TrÃ¥ varsamt, du endrar nokon andre sine mediefiler." +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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Lagra endring av profilen" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Feil passord" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Lagra kontoinstellingar" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Feil passord" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Du mÃ¥ stadfesta slettinga av kontoen din." -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Du har allereie ei samling med namn «%s»." -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." -msgstr "" +msgstr "Ei samling med den nettadressa finst allereie for denne brukaren." -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Du endrar ein annan brukar si samling. TrÃ¥ varsamt." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "" +msgstr "Cannot link theme... no theme set\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" -msgstr "" +msgstr "No asset directory for this theme\n" #: mediagoblin/gmg_commands/theme.py:74 msgid "However, old link directory symlink found; removed.\n" -msgstr "" +msgstr "However, old link directory symlink found; removed.\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:60 -#: mediagoblin/media_types/__init__.py:120 +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Orsak, stør ikkje den filtypen :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" -msgstr "" +msgstr "Skjedde noko gale med video transkodinga" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Stad" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "Godta" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "Nekt" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Namn" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "Namnet til OAuth-klienten" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Forklaring" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +msgstr "Dette vil vera synleg for brukarar som godtek applikasjonen din til Ã¥ autentisera dei." -#: mediagoblin/plugins/oauth/forms.py:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Type" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -286,103 +330,117 @@ msgid "" " <strong>Public</strong> - The client can't make confidential\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n" " JavaScript client)." -msgstr "" +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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "Omdirigering URI" -#: mediagoblin/plugins/oauth/forms.py:55 +#: mediagoblin/plugins/oauth/forms.py:54 msgid "" "The redirect URI for the applications, this field\n" " is <strong>required</strong> for public clients." -msgstr "" +msgstr "Omdirigerings-URI-en for programmene. Denne feltet <strong>krevst</strong> for opne (public) klientar." -#: mediagoblin/plugins/oauth/forms.py:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Dette feltet krevst for opne (public) klientar" #: mediagoblin/plugins/oauth/views.py:59 msgid "The client {0} has been registered!" -msgstr "" +msgstr "Klienten {0} er registrert." -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." -msgstr "Ugyldig fil for mediatypen." +msgstr "Ugyldig fil for medietypen." #: mediagoblin/submit/forms.py:26 msgid "File" msgstr "Fil" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Du mÃ¥ velja ei fil." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Johoo! Opplasta!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" +msgstr "La til samlinga «%s»." -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Bilete av stressa 404-tusse." - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Oops." - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Det ser ikkje ut til Ã¥ vera noko her... Orsak." - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Er du sikker pÃ¥ at adressa er korrekt, so er sida truleg flytta eller sletta." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Verifiser epostadressa di." -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Legg til medie" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "SjÃ¥ profilen din" - -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "Logg ut" -#: mediagoblin/templates/mediagoblin/base.html:75 +#: mediagoblin/templates/mediagoblin/base.html:70 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Endra kontoinstellingar" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "Logg ut" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Legg til verk" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Lag ny samling" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Drive av <a href=\"http://mediagoblin.org\">MediaGoblin</a>, eit <a href=\"http://gnu.org/\">GNU</a>-prosjekt." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -390,31 +448,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Bilete av stressa goblin" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Utforsk" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Heihei, velkomen til denne MediaGoblin-sida." -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 mediefiler." +msgstr "Denne sida køyrer <a href=\"http://mediagoblin.org\">MediaGoblin</a>, eit superbra program for Ã¥ visa fram dine kreative verk." -#: mediagoblin/templates/mediagoblin/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "" +msgstr "Vil du leggja til eigne verk og innpel, so mÃ¥ du logga inn." -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Har du ikkje ein enno? Det er enkelt!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -422,50 +484,43 @@ msgid "" " <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> eller <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">set opp MediaGoblin pÃ¥ eigen tenar</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" -msgstr "Nyaste mediefiler" - -#: 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 "Mediehandsamingspanel" +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 "" +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 "Media under handsaming" +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 media under handsaming" +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 handsama desse opplasta filene:" +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 "" +msgstr "Ingen feila filer." #: mediagoblin/templates/mediagoblin/admin/panel.html:92 msgid "Last 10 successful uploads" -msgstr "" +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 "" +msgstr "Ingenting prossesert, enno." #: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 #: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 @@ -535,47 +590,85 @@ msgid "" "%(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/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 "" +msgstr "Endrar vedlegg for %(media_title)s" -#: 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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Vedlegg" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +msgid "Delete my account" +msgstr "Slett kontoen min" + #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "Endrar %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Endrar profilen til %(username)s" @@ -586,17 +679,16 @@ msgstr "Endrar profilen til %(username)s" #: mediagoblin/templates/mediagoblin/listings/tag.html:35 #, python-format msgid "Media tagged with: %(tag_name)s" -msgstr "Media merka med: %(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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Opphavleg" @@ -615,179 +707,206 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" -msgstr "" +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/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Orsak, denne videoen fungerer ikkje fordi netlesaren din ikkje stør HTML5-video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Du kan skaffa ein moderne netlesar som kan spela av denne videoen hjÃ¥ <a href=\"http://opera.com/download\">http://opera.com/download</a>." +" 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:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" -msgstr "" +msgstr "WebM fil (640p; VP8/Vorbis)" #: mediagoblin/templates/mediagoblin/submit/collection.html:26 msgid "Add a collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Legg til" +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 mediefiler" +msgstr "Legg til verk" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Slett" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Vil du verkeleg sletta %(title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Slett permanent" - #: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 #, python-format msgid "Really remove %(media_title)s from %(collection_title)s?" -msgstr "" +msgstr "Fjerna %(media_title)s frÃ¥ %(collection_title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 msgid "Remove" -msgstr "" +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 "" +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 "Filene til %(username)s" +msgstr "Verka til %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" -msgstr "<a href=\"%(user_url)s\">%(username)s</a> sine mediefiler" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" -msgstr "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" -msgstr "Bilete for %(media_title)s" +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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Legg att innspel" -#: 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 "Du kan bruka <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> til formatterring." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Legg til dette innspelet" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "hjÃ¥" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Lagt til</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "" +msgid "Add “%(media_title)s†to a collection" +msgstr "Putt «%(media_title)s» i samling" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +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 mediehandsaming av biletene dine her." +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 "" +msgstr "Dine ti siste opplastningar." #: mediagoblin/templates/mediagoblin/user_pages/user.html:31 #: mediagoblin/templates/mediagoblin/user_pages/user.html:89 @@ -838,74 +957,58 @@ msgstr "Viss dette er deg, kan du <a href=\"%(login_url)s\">logga inn</a> for Ã¥ 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Endra kontoinstellingar" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "SjÃ¥ gjennom samlingar" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 #, python-format msgid "View all of %(username)s's media" -msgstr "SjÃ¥ alle %(username)s sine mediefiler" +msgstr "SjÃ¥ alle %(username)s sine verk" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:151 +#: 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 mediefilene dine." +msgstr "Her kjem verka dine." -#: mediagoblin/templates/mediagoblin/user_pages/user.html:157 -msgid "Add media" -msgstr "Legg til mediefiler" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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 mediefiler her nett no." +msgstr "Ser ikkje ut til at det finst nokon verk her nett no." -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(fjern)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "I samling" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "" +#: 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/geolocation_map.html:25 -msgid "Location" -msgstr "Stad" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "SjÃ¥ pÃ¥ <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Alle rettar reservert" @@ -936,98 +1039,134 @@ msgstr "eldre" msgid "Tagged with" msgstr "Merka med" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Klarte ikkje lesa biletefila." -#: mediagoblin/user_pages/forms.py:28 +#: 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!<HAL2000></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/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:32 +#: mediagoblin/user_pages/forms.py:35 msgid "I am sure I want to remove this item from the collection" -msgstr "" +msgstr "Eg er sikker pÃ¥ at eg vil fjerna dette frÃ¥ samlinga" -#: mediagoblin/user_pages/forms.py:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Samling" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" -msgstr "" +msgstr "-- Vel --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "Legg ved eit notat" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" -msgstr "" +msgstr "kom med innspel pÃ¥ innlegget ditt" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Vops, innspelet ditt var tomt." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Innspelet ditt er lagt til." -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Sjekk filene dine og prøv omatt." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "Du mÃ¥ velja eller laga ei samling" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "«%s» er allereie i samling «%s»" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" +msgstr "«%s» lagt til samling «%s»" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." -msgstr "Du sletta fila." +msgstr "Du sletta verket." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." -msgstr "Sletta ikkje fila fordi du ikkje sa du var sikker." +msgstr "Sletta ikkje verket." -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." -msgstr "Du er i ferd med Ã¥ sletta ein annan brukar sine mediefiler. TrÃ¥ varsamt." +msgstr "Du er i ferd med Ã¥ sletta ein annan brukar sine verk. TrÃ¥ varsamt." -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "Du fjerna fila frÃ¥ samlinga." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "Fila var ikkje fjerna fordi du ikkje var sikker." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +msgstr "Du er i ferd med Ã¥ fjerna ei fil frÃ¥ ein annan brukar si samling. TrÃ¥ varsamt." -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "Samlinga «%s» sletta" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "Sletta ikkje samlinga." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 Binary files differindex 77dcfe12..ea905b61 100644 --- a/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po index 7dea3837..9edf8e2b 100644 --- a/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -19,82 +19,96 @@ msgstr "" "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:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Użytkownik" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "HasÅ‚o" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Adres e-mail" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Użytkownik lub adres e-mail" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "NieprawidÅ‚owe dane" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "NieprawidÅ‚owy klucz weryfikacji lub identyfikator użytkownika." -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Twój adres e-mail już zostaÅ‚ zweryfikowany!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "WyÅ›lij ponownie e-mail weryfikujÄ…cy." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Nie znaleziono nikogo o takiej nazwie użytkownika lub adresie e-mail." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "Teraz możesz siÄ™ zalogować używajÄ…c nowe hasÅ‚o." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "TytuÅ‚" @@ -103,8 +117,8 @@ msgid "Description of this work" msgstr "Opis tej pracy" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +133,11 @@ msgstr "Znaczniki" msgid "Separate tags by commas." msgstr "Rozdzielaj znaczniki przecinkami." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Slug" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Slug nie może być pusty" @@ -162,60 +176,81 @@ msgstr "Wprowadź swoje stare hasÅ‚o aby udowodnić, że to twoje konto." msgid "New password" msgstr "Nowe hasÅ‚o" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "Powiadamiaj mnie e-mailem o komentarzach do moich mediów" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "TytuÅ‚ nie może być pusty" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "Opis tej kolekcji" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 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/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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 "" + #: 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 "Edytujesz profil innego użytkownika. Zachowaj ostrożność." -#: mediagoblin/edit/views.py:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Zapisano zmiany profilu" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "NieprawidÅ‚owe hasÅ‚o" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Zapisano ustawienia konta" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "NieprawidÅ‚owe hasÅ‚o" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "Kolekcja \"%s\" już istnieje!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 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:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "Edytujesz kolekcjÄ™ innego użytkownika. Zachowaj ostrożność." @@ -231,54 +266,62 @@ msgstr "Brak katalogu danych dla tego motywu\n" msgid "However, old link directory symlink found; removed.\n" msgstr "Znaleziono stary odnoÅ›nik symboliczny do katalogu; usuniÄ™to.\n" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "NIestety, nie obsÅ‚ugujemy tego typu plików :-(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Konwersja wideo nie powiodÅ‚a siÄ™" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "PoÅ‚ożenie" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "NastÄ™pny adres URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "Zezwól" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "Odrzuć" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "Nazwa" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "Nazwa klienta OAuth" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "Opis" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "Typ" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -288,17 +331,17 @@ msgid "" " 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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "Przekierowanie URI" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "To pole jest wymagane dla klientów publicznych" @@ -306,7 +349,22 @@ msgstr "To pole jest wymagane dla klientów publicznych" msgid "The client {0} has been registered!" msgstr "Klient {0} zostaÅ‚ zarejestrowany!" -#: mediagoblin/processing/__init__.py:138 +#: 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 "Dodaj" + +#: mediagoblin/processing/__init__.py:172 msgid "Invalid file given for media type." msgstr "NiewÅ‚aÅ›ciwy plik dla tego rodzaju mediów." @@ -314,75 +372,74 @@ msgstr "NiewÅ‚aÅ›ciwy plik dla tego rodzaju mediów." msgid "File" msgstr "Plik" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Musisz podać plik." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Hura! WysÅ‚ano!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "Kolekcja \"%s\" zostaÅ‚a dodana!" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Grafika zestresowanego goblina 404." - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Ups!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Niestety, nie ma strony o takim adresie!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "JeÅ›li twoim zdaniem ten adres jest prawidÅ‚owy, to może poszukiwana strona zostaÅ‚a przeniesiona lub usuniÄ™ta." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Logo MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Zweryfikuj swój adres e-mail!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Dodaj media" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "+ Dodaj kolekcjÄ™" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Zobacz swój profil" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Wyloguj siÄ™" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" +msgstr "ZmieÅ„ ustawienia konta" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Dodaj media" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "ObsÅ‚ugiwane przez <a href=\"http://mediagoblin.org\">MediaGoblin</a>, projekt <a href=\"http://gnu.org/\">GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -390,31 +447,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Odkrywaj" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Cześć, witaj na stronie MediaGoblin!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 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/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Jeszcze go nie masz? To proste!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -422,17 +483,10 @@ msgid "" " <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\">Utwórz konto w tym serwisie</a>\n lub\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">załóż wÅ‚asny serwis MediaGoblin</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Najnowsze media" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -535,47 +589,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Załączniki" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Zapisz zmiany" +#: 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:47 +#: 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:41 +#: 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:59 +msgid "Delete my account" +msgstr "" + #: 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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Edycja profilu %(username)s" @@ -590,13 +682,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "OryginaÅ‚" @@ -615,7 +706,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "Oryginalny plik" @@ -623,21 +714,71 @@ msgstr "Oryginalny plik" msgid "WebM file (Vorbis codec)" msgstr "plik WebM (kodek Vorbis)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Niestety nie można wyÅ›wietlić tego filmu, ponieważ\n\t twoja przeglÄ…darka nie obsÅ‚uguje filmów \n\t HTML5." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Możesz pobrać współczesnÄ… przeglÄ…darkÄ™, która obsÅ‚uguje \n\t takie filmy, pod adresem <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "plik WebM (640p; VP8/Vorbis)" @@ -645,12 +786,6 @@ msgstr "plik WebM (640p; VP8/Vorbis)" msgid "Add a collection" msgstr "Dodaj kolekcjÄ™" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Dodaj" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -667,43 +802,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "UsuÅ„" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -msgstr "<p>\n %(collection_description)s\n </p>" - #: 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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "UsuÅ„ na staÅ‚e" - #: 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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 msgid "Remove" msgstr "UsuÅ„" +#: 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 "" @@ -716,67 +848,53 @@ msgstr "Witaj %(username)s,\n%(comment_author)s skomentowaÅ‚ twój wpis (%(comme msgid "%(username)s's media" msgstr "Media użytkownika %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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 użytkownika <a href=\"%(user_url)s\">%(username)s</a>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Grafika dla %(media_title)s" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Dodaj komentarz" -#: 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 "Możesz formatować przy pomocy skÅ‚adni <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Dodaj komentarz" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "na" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Dodane</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "Załączniki" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "Dodaj załącznik" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "Dodaj %(title)s do kolekcji" +msgid "Add “%(media_title)s†to a collection" +msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "Dodaj nowÄ… kolekcjÄ™" @@ -838,74 +956,58 @@ msgstr "JeÅ›li jesteÅ› tÄ… osobÄ…, ale zgubiÅ‚eÅ› swój e-mail weryfikujÄ…cy, to 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "ZmieÅ„ ustawienia konta" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Dodaj media" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "<br />\n <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "<br /><a href=\"%(remove_url)s\" class=\"remove\">(usuÅ„)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "W kolekcjach (%(collected)s)" +#: 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 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/geolocation_map.html:25 -msgid "Location" -msgstr "PoÅ‚ożenie" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "Zobacz na <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Wszystkie prawa zastrzeżone" @@ -936,23 +1038,64 @@ msgstr "starsze" msgid "Tagged with" msgstr "Znaczniki:" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Nie udaÅ‚o siÄ™ odczytać pliku grafiki." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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 "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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "-- wybierz --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "Dodaj notatkÄ™" @@ -960,74 +1103,69 @@ msgstr "Dodaj notatkÄ™" msgid "commented on your post" msgstr "komentarze do twojego wpisu" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Ups, twój komentarz nie zawieraÅ‚ treÅ›ci." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Twój komentarz zostaÅ‚ opublikowany!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Sprawdź swoje wpisy i spróbuj ponownie." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "Musisz wybrać lub dodać kolekcjÄ™" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "\"%s\" już obecne w kolekcji \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "\"%s\" dodano do kolekcji \"%s\"" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "Sprawdź swoje wpisy i spróbuj ponownie." - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "Część plików z tego wpisu wyglÄ…da na nieistniejÄ…ce. Trwa usuwanie." - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Media zostaÅ‚y usuniÄ™te." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "Element zostaÅ‚ usuniÄ™ty z kolekcji." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 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:393 +#: mediagoblin/user_pages/views.py:389 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:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "UsuniÄ™to kolekcjÄ™ \"%s\"" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 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:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 1552ce78..af50e027 100644 --- a/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po index ff431932..3b2ed203 100644 --- a/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po @@ -1,16 +1,18 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: +# Rafael Ferreira <rafael.f.f1@gmail.com>, 2013. # <snd.noise@gmail.com>, 2011. # ufa <ufa@technotroll.org>, 2011. +# Vinicius SM <viniciussm@rocketmail.com>, 2013. msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+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" @@ -20,82 +22,96 @@ msgstr "" "Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Nome de usuário ou email inválido." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Este campo não aceita endereços de email." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Este campo requer um endereço de email." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Nome de Usuário" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Senha" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Endereço de email" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" -msgstr "" - -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" +msgstr "Nome de usuário ou email" -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." -msgstr "Desculpe, um usuário com esse email já esta cadastrado" +msgstr "Desculpe, um usuário com esse email já está cadastrado" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 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 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" -msgstr " " +msgstr "Você precisa entrar primeiro para sabermos para quem mandar o email!" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" -msgstr "Você já verifico seu email!" +msgstr "Você já verificou seu email!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." -msgstr "O email de verificação foi reenviado." +msgstr "O email de verificação foi enviado novamente." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 msgid "" -"An email has been sent with instructions on how to change your password." +"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:273 +#: mediagoblin/auth/views.py:261 +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:264 +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:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "" - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." -msgstr "" +msgstr "Agora você pode entrar usando sua nova senha." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "TÃtulo" @@ -104,13 +120,13 @@ msgid "Description of this work" msgstr "Descrição desse trabalho" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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 "" +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" @@ -118,13 +134,13 @@ msgstr "Etiquetas" #: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 msgid "Separate tags by commas." -msgstr "" +msgstr "Separe as etiquetas com vÃrgulas." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Arquivo" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "O arquivo não pode estar vazio" @@ -132,12 +148,12 @@ msgstr "O arquivo não pode estar vazio" msgid "" "The title part of this media's address. You usually don't need to change " "this." -msgstr "" +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 "" +msgstr "Licença" #: mediagoblin/edit/forms.py:50 msgid "Bio" @@ -149,7 +165,7 @@ msgstr "Website" #: mediagoblin/edit/forms.py:58 msgid "This address contains errors" -msgstr "" +msgstr "Este endereço contém erros" #: mediagoblin/edit/forms.py:63 msgid "Old password" @@ -157,72 +173,93 @@ msgstr "Senha antiga" #: mediagoblin/edit/forms.py:64 msgid "Enter your old password to prove you own this account." -msgstr "" +msgstr "Digite sua senha antiga para provar que esta conta é sua." #: mediagoblin/edit/forms.py:67 msgid "New password" -msgstr "" +msgstr "Nova senha" + +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "Licença preferida" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:80 +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:82 msgid "Email me when others comment on my media" -msgstr "" +msgstr "Me enviar um email quando outras pessoas comentarem em minhas mÃdias" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "O tÃtulo não pode ficar vazio" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Descrição desta coleção" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "A parte do tÃtulo do endereço dessa coleção. Geralmente você não precisa mudar isso." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" -msgstr "" +msgstr "As mudanças no perfil foram salvas" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" -msgstr "" - -#: mediagoblin/edit/views.py:252 +#: mediagoblin/edit/views.py:241 msgid "Wrong password" msgstr "Senha errada" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:252 +msgid "Account settings saved" +msgstr "As mudanças na conta foram salvas" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Você precisa confirmar a exclusão da sua conta." + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Você já tem uma coleção chamada \"%s\"!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." -msgstr "" +msgstr "Já existe uma coleção com este arquivo para este usuário." -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Você está editando a coleção de um outro usuário. Prossiga com cuidado." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "" +msgstr "Não é possÃvel fazer link de tema... nenhum tema definido\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" @@ -232,54 +269,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" -msgstr "" +msgstr "Desculpe, não tenho suporte a este tipo de arquivo :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" -msgstr "" +msgstr "Conversão do vÃdeo falhou" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Localização" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "Permitir" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "Negar" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Nome" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "O nome do cliente OAuth" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Descrição" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Tipo" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -289,25 +334,40 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "Redirecionar URI" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Este campo é necessário para clientes públicos" #: mediagoblin/plugins/oauth/views.py:59 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/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Arquivo inválido para esse tipo de mÃdia" @@ -315,125 +375,121 @@ msgstr "Arquivo inválido para esse tipo de mÃdia" msgid "File" msgstr "Arquivo" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Você deve fornecer um arquivo." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Eba! Enviado!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Imagem do goblin 404 aparecendo" +msgstr "Coleção \"%s\" adicionada!" -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Oops" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Aparentemente não existe uma página com esse endereço. Desculpe!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Se você está certo de que o endereço está correto, talvez a página que esteja procurando tenha sido apagada ou mudou de endereço" - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Logo MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Verifique seu email!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "sair" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Mudar configurações da conta" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Adicionar mÃdia" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Criar nova coleção" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, 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/error.html:24 +msgid "Image of goblin stressing out" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Explorar" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" -msgstr "Olá, bemvindo ao site de MediaGoblin." +msgstr "Olá, bem-vindo a este site MediaGoblin." -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 msgid "" "This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " "extraordinarily great piece of media hosting software." -msgstr "" +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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "" +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/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" -msgstr " " +msgstr " Ainda não tem uma conta? É facil!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, 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 "" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Criar uma conta neste site</a>\nou\n<a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Configurar MediaGoblin em seu próprio servidor</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "MÃdia mais recente" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -457,25 +513,25 @@ 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 "" +msgstr "Nenhuma entrada falhou!" #: mediagoblin/templates/mediagoblin/admin/panel.html:92 msgid "Last 10 successful uploads" -msgstr "" +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 "" +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 "" +msgstr "Defina a sua nova senha" #: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 msgid "Set password" -msgstr "" +msgstr "Definir senha" #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 @@ -536,47 +592,85 @@ msgid "" "%(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/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 "" +msgstr "Editando os anexos de %(media_title)s" -#: 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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Anexos" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Salvar mudanças" +#: 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:47 +#: 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:41 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 #, python-format msgid "Changing %(username)s's account settings" -msgstr "" +msgstr "Alterando as configurações da conta de %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:59 +msgid "Delete my account" +msgstr "Deletar minha conta" #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "Editando %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Editando perfil de %(username)s" @@ -587,17 +681,16 @@ msgstr "Editando perfil de %(username)s" #: mediagoblin/templates/mediagoblin/listings/tag.html:35 #, python-format msgid "Media tagged with: %(tag_name)s" -msgstr "" +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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 msgid "Download" -msgstr "" +msgstr "Baixar" #: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Original" @@ -606,180 +699,207 @@ msgid "" "Sorry, this audio will not work because \n" "\tyour web browser does not support HTML5 \n" "\taudio." -msgstr "" +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 "" +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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" -msgstr "" +msgstr "Arquivo original" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 msgid "WebM file (Vorbis codec)" -msgstr "" +msgstr "Arquivo WebM (codec Vorbis)" + +#: 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/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/video.html:40 +#: 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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "" +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "" +" 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:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" -msgstr "" +msgstr "Arquivo WebM (640p, VP8/Vorbis)" #: mediagoblin/templates/mediagoblin/submit/collection.html:26 msgid "Add a collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" +msgstr "Adicionar uma coleção" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" -msgstr "" +msgstr "Adicionar sua mÃdia" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Apagar" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Realmente apagar %(title)s ?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 "" +msgstr "Realmente remover %(media_title)s de %(collection_title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 msgid "Remove" -msgstr "" +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 "" +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 "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "" +msgstr "â– Vendo mÃdia de <a href=\"%(user_url)s\">%(username)s</a>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" -msgstr "" +msgstr "Adicionar um comentário" -#: 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" -msgstr "" +msgstr "Adicionar este comentário" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" -msgstr "" +msgstr "em" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" -msgstr "" +msgstr "<h3>Adicionado em</h3>\n<p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "" +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:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +msgstr "Adicionar uma nova coleção" #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 msgid "" @@ -788,7 +908,7 @@ msgstr "Você pode verificar como a mÃdia esta sendo processada para sua galeri #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 msgid "Your last 10 successful uploads" -msgstr "" +msgstr "Seus últimos 10 envios bem sucedidos" #: mediagoblin/templates/mediagoblin/user_pages/user.html:31 #: mediagoblin/templates/mediagoblin/user_pages/user.html:89 @@ -807,7 +927,7 @@ 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" +msgstr "Quase pronto! Sua conta ainda precisa ser ativada." #: mediagoblin/templates/mediagoblin/user_pages/user.html:58 msgid "" @@ -839,85 +959,69 @@ msgstr "Se você é essa pessoa, mas você perdeu seu e-mail de verificação, v 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Ver coleções" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Adicionar mÃdia" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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 "Aparentemente não há nenhuma mÃdia aqui ainda..." -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(apagar)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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 "Ã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/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 "" +msgstr "Todos os direitos reservados" #: mediagoblin/templates/mediagoblin/utils/pagination.html:39 msgid "↠Newer" -msgstr "" +msgstr "↠Novos" #: mediagoblin/templates/mediagoblin/utils/pagination.html:45 msgid "Older →" -msgstr "" +msgstr "Antigos →" #: mediagoblin/templates/mediagoblin/utils/pagination.html:48 msgid "Go to page:" @@ -926,109 +1030,145 @@ msgstr "Ir a página:" #: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 #: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 msgid "newer" -msgstr "" +msgstr "mais nova" #: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 #: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 msgid "older" -msgstr "" +msgstr "mais antiga" #: mediagoblin/templates/mediagoblin/utils/tags.html:20 msgid "Tagged with" -msgstr "" +msgstr "Etiquetas" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." -msgstr "" +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/user_pages/forms.py:28 +#: 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/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 pagar isso" +msgstr "Eu tenho certeza de que quero apagar isso" -#: mediagoblin/user_pages/forms.py:32 +#: mediagoblin/user_pages/forms.py:35 msgid "I am sure I want to remove this item from the collection" -msgstr "" +msgstr "Tenho certeza que quero remover este item da coleção" -#: mediagoblin/user_pages/forms.py:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Coleção" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "Incluir uma nota" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" -msgstr "" +msgstr "comentou na sua publicação" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." -msgstr "Opa, seu comentáio estava vazio." +msgstr "Ops, seu comentário estava vazio." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Seu comentário foi postado!" -#: mediagoblin/user_pages/views.py:235 -msgid "You have to select or add a collection" +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:237 +msgid "You have to select or add a collection" +msgstr "Você deve selecionar ou adicionar uma coleção" + +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "\"%s\" já está na coleção \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" +msgstr "\"%s\" adicionado à coleção \"%s\"" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Você deletou a mÃdia." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "A mÃdia não foi apagada porque você não marcou que tinha certeza." -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "Você deletou o item da coleção." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "O item não foi apagado porque você não marcou que tinha certeza." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +msgstr "Você está prestes a remover um item da coleção de um outro usuário. Prossiga com cuidado." -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "Você deletou a coleção \"%s\"" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "A coleção não foi apagada porque você não marcou que tinha certeza." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 Binary files differindex 8a759873..62cbf028 100644 --- a/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po index e391869b..da585d5c 100644 --- a/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po @@ -1,17 +1,17 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: # <gapop@hotmail.com>, 2011. -# George Pop <gapop@hotmail.com>, 2011-2012. +# 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: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" -"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-10 04:13+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" @@ -20,82 +20,96 @@ msgstr "" "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:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Nume de utilizator sau adresă de e-mail nevalidă." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Această rubrică nu este pentru adrese de e-mail." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Această rubrică trebuie completată cu o adresă de e-mail." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Nume de utilizator" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Parolă" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Adresa de e-mail" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Numele de utilizator sau adresa de e-mail" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Input incorect" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "Cheie de verificare sau user ID incorect." -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Adresa ta de e-mail a fost deja verificată!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "E-mail-ul de verificare a fost retrimis." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "Nu există nimeni cu acest nume de utilizator." + +#: mediagoblin/auth/views.py:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Nu s-a găsit nicio persoană cu acel nume de utilizator sau adresă de e-mail." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 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:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titlu" @@ -104,8 +118,8 @@ msgid "Description of this work" msgstr "Descrierea acestui fiÈ™ier" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -114,17 +128,17 @@ msgstr "PoÈ›i folosi\n <a href=\"http://daringfireball.net/ #: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 msgid "Tags" -msgstr "Tag-uri" +msgstr "Cuvinte-cheie" #: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 msgid "Separate tags by commas." -msgstr "Desparte tag-urile prin virgulă." +msgstr "Desparte cuvintele-cheie prin virgulă." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Identificator" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Identificatorul nu poate să lipsească" @@ -163,60 +177,81 @@ msgstr "Introdu vechea parolă pentru a demonstra că eÈ™ti titularul acestui co msgid "New password" msgstr "Noua parolă" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "LicenÈ›a preferată" + +#: mediagoblin/edit/forms.py:80 +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:82 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:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "Titlul nu poate să fie gol" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 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:93 +#: mediagoblin/edit/forms.py:103 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/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Modificările profilului au fost salvate" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Parolă incorectă" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Setările pentru acest cont au fost salvate" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Parolă incorectă" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Trebuie să confirmi È™tergerea contului tău." -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "Ai deja o colecÈ›ie numită \"%s\"!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 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:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "Lucrezi pe colecÈ›ia unui alt utilizator. Se recomandă prudență." @@ -232,54 +267,62 @@ msgstr "Nu există un folder de elemente pentru această temă\n" 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/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Scuze, nu recunosc acest tip de fiÈ™ier :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Transcodarea video a eÈ™uat" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "ID client" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Locul" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "Următorul URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "Permite" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "Refuză" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "Nume" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "Numele clientului OAuth" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "Descriere" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "Tip" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -289,17 +332,17 @@ msgid "" " 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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "URI redirectare" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "Această rubrică este obligatorie pentru clienÈ›ii publici" @@ -307,7 +350,22 @@ msgstr "Această rubrică este obligatorie pentru clienÈ›ii publici" msgid "The client {0} has been registered!" msgstr "Clientul {0} a fost înregistrat!" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Formatul fiÈ™ierului nu corespunde cu tipul de media selectat." @@ -315,75 +373,74 @@ msgstr "Formatul fiÈ™ierului nu corespunde cu tipul de media selectat." msgid "File" msgstr "FiÈ™ier" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Trebuie să selectezi un fiÈ™ier." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Ura! Trimis!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "ColecÈ›ia \"%s\" a fost creată!" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Imagine cu elful 404 stresat." - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Hopa!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Nu există nicio pagină la această adresă. Ne pare rău!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Dacă eÈ™ti sigur că adresa e corectă, poate că pagina pe care o cauÈ›i a fost mutată sau È™tearsă." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "logo MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Verifică adresa de e-mail!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Adaugă fiÈ™ier media" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "+ Creează colecÈ›ie" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Vezi profilul tău" - -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "IeÈ™ire" -#: mediagoblin/templates/mediagoblin/base.html:75 +#: mediagoblin/templates/mediagoblin/base.html:70 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Modifică setările contului" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "IeÈ™ire" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Trimite fiÈ™ier" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Creează colecÈ›ie nouă" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Construit cu <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un proiect <a href=\"http://gnu.org/\">GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -391,31 +448,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Imagine cu un goblin stresat" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Explorează" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Salut, bine ai venit pe acest site MediaGoblin!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 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/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "ÃŽncă nu ai unul? E simplu!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -423,17 +484,10 @@ msgid "" " <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\">Creează un cont pe acest site</a>\n sau\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instalează MediaGoblin pe serverul tău</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Cele mai recente fiÈ™iere" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -498,7 +552,7 @@ msgid "" "\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 elf fericit!" +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!" @@ -536,47 +590,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Anexe" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Salvează modificările" +#: 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Editare profil %(username)s" @@ -587,17 +679,16 @@ msgstr "Editare profil %(username)s" #: mediagoblin/templates/mediagoblin/listings/tag.html:35 #, python-format msgid "Media tagged with: %(tag_name)s" -msgstr "FiÈ™ier etichetat cu tag-urile: %(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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Original" @@ -616,7 +707,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "FiÈ™ierul original" @@ -624,21 +715,71 @@ msgstr "FiÈ™ierul original" msgid "WebM file (Vorbis codec)" msgstr "FiÈ™ier WebM (codec Vorbis)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Ne pare rău, această înregistrare video nu poate fi redată deoarece \n<span class=\"whitespace other\" title=\"Tab\">»</span> browserul tău nu este compatibil cu funcÈ›ia video din HTML5." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "PoÈ›i lua un browser modern\n<span class=\"whitespace other\" title=\"Tab\">»</span> capabil să redea această înregistrare de la <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!" +" 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:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "FiÈ™ier WebM (640p; VP8/Vorbis)" @@ -646,12 +787,6 @@ msgstr "FiÈ™ier WebM (640p; VP8/Vorbis)" msgid "Add a collection" msgstr "Creează o colecÈ›ie" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Adaugă" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -668,43 +803,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Șterge" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -msgstr "<p>\n %(collection_description)s\n </p>" - #: 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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Șterge definitiv" - #: 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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -717,67 +849,53 @@ msgstr "Bună, %(username)s,\n%(comment_author)s a făcut un comentariu la posta msgid "%(username)s's media" msgstr "FiÈ™ierele lui %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Imagine pentru %(media_title)s" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Adaugă un comentariu" -#: 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 "PoÈ›i folosi <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> pentru formatare." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Trimite acest comentariu" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "la" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Adăugat la</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "Anexe" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "AtaÈ™ează" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "Adaugă %(title)s la colecÈ›ie" +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:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "Creează o nouă colecÈ›ie" @@ -839,74 +957,58 @@ msgstr "Dacă tu eÈ™ti persoana respectivă È™i nu mai ai e-mail-ul de verificar 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Modifică setările contului" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Vizitează colecÈ›iile" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Trimite fiÈ™ier" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "<br />\n <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(È™terge)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "<br /><a href=\"%(remove_url)s\" class=\"remove\">(È™terge)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Din colecÈ›ia" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "ÃŽn colecÈ›iile (%(collected)s)" +#: 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/geolocation_map.html:25 -msgid "Location" -msgstr "Locul" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "Vezi pe <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Toate drepturile rezervate" @@ -935,25 +1037,66 @@ msgstr "mai vechi" #: mediagoblin/templates/mediagoblin/utils/tags.html:20 msgid "Tagged with" -msgstr "Etichete" +msgstr "Etichetat cu cuvintele-cheie" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "FiÈ™ierul cu imaginea nu a putut fi citit." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: 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:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "Adaugă o notiță" @@ -961,74 +1104,69 @@ msgstr "Adaugă o notiță" msgid "commented on your post" msgstr "a făcut un comentariu la postarea ta" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Hopa, ai uitat să scrii comentariul." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Comentariul tău a fost trimis!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Verifică datele È™i încearcă din nou." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "Trebuie să alegi sau să creezi o colecÈ›ie" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "\"%s\" este deja în colecÈ›ia \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "\"%s\" a fost adăugat la colecÈ›ia \"%s\"" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "Verifică datele È™i încearcă din nou." - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "Unele fiÈ™iere din acest entry par să lipsească. Ștergem, totuÈ™i." - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Ai È™ters acest fiÈ™ier" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "Ai È™ters acest articol din colecÈ›ie." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 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:393 +#: mediagoblin/user_pages/views.py:389 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:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "Ai È™ters colecÈ›ia \"%s\"" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 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:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex d8967294..759f5337 100644 --- a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po index 8785e8ac..0dc099ed 100644 --- a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po @@ -1,15 +1,16 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: +# <deletesoftware@yandex.ru>, 2013. # <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: 2012-09-24 11:41-0500\n" -"PO-Revision-Date: 2012-09-24 18:15+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-10 15:35+0000\n" "Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -19,82 +20,96 @@ msgstr "" "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:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Ðто поле не Ð´Ð»Ñ Ð°Ð´Ñ€ÐµÑа Ñлектронной почты." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Ðто поле — Ð´Ð»Ñ Ð°Ð´Ñ€ÐµÑа Ñлектронной почты." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Логин" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Пароль" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "ÐÐ´Ñ€ÐµÑ Ñлектронной почты" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ Ð°Ð´Ñ€ÐµÑ Ñлектронной почты" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Введённое не похоже на Ð¸Ð¼Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð¾Ð¹ запиÑи или Ð°Ð´Ñ€ÐµÑ Ñлектронной почты." - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "Извините, на Ñтом Ñайте региÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð·Ð°Ð¿Ñ€ÐµÑ‰ÐµÐ½Ð°." -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "Извините, пользователь Ñ Ñтим именем уже зарегиÑтрирован." -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "Сожалеем, но на Ñтот Ð°Ð´Ñ€ÐµÑ Ñлектронной почты уже зарегиÑтрирована Ð´Ñ€ÑƒÐ³Ð°Ñ ÑƒÑ‡Ñ‘Ñ‚Ð½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ." -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "Ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты потвержден. Ð’Ñ‹ теперь можете войти и начать редактировать Ñвой профиль и загружать новые изображениÑ!" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "Ðеверный ключ проверки или идентификатор пользователÑ" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "Вам надо предÑтавитьÑÑ, чтобы мы знали, кому отправлÑть Ñообщение!" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Ð’Ñ‹ уже потвердили Ñвой Ð°Ð´Ñ€ÐµÑ Ñлектронной почты!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "ПереÑлать Ñообщение Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸ÐµÐ¼ аккаунта." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "Ðе найдено никого Ñ Ñ‚Ð°ÐºÐ¸Ð¼ именем пользователÑ." + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "Вам отправлено Ñлектронное пиÑьмо Ñ Ð¸Ð½ÑтрукциÑми по Ñмене паролÑ." -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "Теперь вы можете войти, иÑÐ¿Ð¾Ð»ÑŒÐ·ÑƒÑ Ð²Ð°Ñˆ новый пароль." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Ðазвание" @@ -103,8 +118,8 @@ msgid "Description of this work" msgstr "ОпиÑание Ñтого произведениÑ" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +134,11 @@ msgstr "Метки" msgid "Separate tags by commas." msgstr "(через запÑтую)" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "ÐžÑ‚Ð»Ð¸Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°Ñть адреÑа" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "ÐžÑ‚Ð»Ð¸Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°Ñть адреÑа необходима" @@ -162,59 +177,81 @@ msgstr "Введите Ñвой Ñтарый пароль в качеÑтве д msgid "New password" msgstr "Ðовый пароль" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "ÐŸÑ€ÐµÐ´Ð¿Ð¾Ñ‡Ð¸Ñ‚Ð°ÐµÐ¼Ð°Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸Ñ" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "Она будет лицензией по умолчанию Ð´Ð»Ñ Ð²Ð°ÑˆÐ¸Ñ… загрузок" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "УведомлÑть Ð¼ÐµÐ½Ñ Ð¿Ð¾ e-mail о комментариÑÑ… к моим файлам" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "Ðазвание не может быть пуÑтым" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "ОпиÑание Ñтой коллекции" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "ÐžÑ‚Ð»Ð¸Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°Ñть адреÑа Ñтой коллекции, оÑÐ½Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð½Ð° названии. Обычно не нужно её изменÑть." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "У Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÑƒÐ¶Ðµ еÑть файл Ñ Ñ‚Ð°ÐºÐ¾Ð¹ отличительной чаÑтью адреÑа." -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ñохранены" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Ðеправильный пароль" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "ÐаÑтройки учётной запиÑи запиÑаны" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Ðеправильный пароль" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "Вам нужно подтвердить, что вы хотите удалить Ñвою учётную запиÑÑŒ." -#: mediagoblin/edit/views.py:287 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format -msgid "You already have a collection called \"%s\"!title" -msgstr "У Ð²Ð°Ñ ÑƒÐ¶Ðµ еÑть ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Â«%s»!title" +msgid "You already have a collection called \"%s\"!" +msgstr "У Ð²Ð°Ñ ÑƒÐ¶Ðµ еÑть ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ «%s»!" -#: mediagoblin/edit/views.py:290 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "У Ñтого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ ÑƒÐ¶Ðµ еÑть ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Ñ Ñ‚Ð°ÐºÐ¾Ð¹ отличительной чаÑтью адреÑа." -#: mediagoblin/edit/views.py:307 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "Ð’Ñ‹ редактируете коллекцию другого пользователÑ. Будьте оÑторожны." @@ -230,54 +267,62 @@ msgstr "У Ñтой темы отÑутÑтвует каталог Ñ Ñлеме msgid "However, old link directory symlink found; removed.\n" msgstr "Однако найдена (и удалена) ÑÑ‚Ð°Ñ€Ð°Ñ ÑимволичеÑÐºÐ°Ñ ÑÑылка на каталог.\n" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Увы, Ñ Ð½Ðµ поддерживаю Ñтот тип файлов :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Перекодировка видео не удалаÑÑŒ" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Ðа карте" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "ОпиÑание" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +msgstr "Его увидÑÑ‚ пользователи, разрешающие вашему приложению дейÑтвовать от их имени." -#: mediagoblin/plugins/oauth/forms.py:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Тип" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -287,25 +332,40 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" #: mediagoblin/plugins/oauth/views.py:59 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/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Ðеправильный формат файла." @@ -313,80 +373,74 @@ msgstr "Ðеправильный формат файла." msgid "File" msgstr "Файл" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Ð’Ñ‹ должны загрузить файл." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Ура! Файл загружен!" -#: mediagoblin/submit/views.py:211 mediagoblin/user_pages/views.py:215 -#, python-format -msgid "You already have a collection called \"%s\"!" -msgstr "У Ð²Ð°Ñ ÑƒÐ¶Ðµ еÑть ÐºÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Ñ Ð½Ð°Ð·Ð²Ð°Ð½Ð¸ÐµÐ¼ «%s»!" - -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "ÐšÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Â«%s» добавлена!" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Изображение 404 нервничающего гоблина" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Ой!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "КажетÑÑ, такой Ñтраницы не ÑущеÑтвует. Уж извините!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -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" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Подтвердите ваш Ð°Ð´Ñ€ÐµÑ Ñлектронной почты!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Добавить файл" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "+ Добавить коллекцию" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "ПоÑмотреть Ñвой профиль" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "завершение ÑеанÑа" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Завершение ÑеанÑа" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Изменить наÑтройки учётной запиÑи" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "Завершение ÑеанÑа" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Добавить файлы" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Создать новую коллекцию" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Работает на <a href=\"http://mediagoblin.org\">MediaGoblin</a>, проекте <a href=\"http://gnu.org/\">GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -394,31 +448,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Изображение нервничающего гоблина" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Смотреть" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Привет! Добро пожаловать на наш MediaGoblin’овый Ñайт!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." msgstr "Ð”Ð»Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑобÑтвенных файлов, ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ Ñ‚. п. вы можете предÑтавитьÑÑ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ вашей MediaGoblin’овой учётной запиÑи." -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "У Ð²Ð°Ñ ÐµÑ‘ ещё нет? Ðе проблема!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -426,17 +484,10 @@ msgid "" " <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/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Самые новые файлы" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -539,47 +590,85 @@ msgid "" "%(verification_url)s" msgstr "Привет, %(username)s!\n\nЧтобы активировать Ñвой аккаунт в GNU MediaGoblin, откройте в Ñвоём вебâ€Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€Ðµ Ñледующую ÑÑылку:\n\n%(verification_url)s" +#: 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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "СопутÑтвующие файлы" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Редактирование Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ %(username)s" @@ -594,13 +683,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Оригинал" @@ -619,7 +707,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "ИÑходный файл" @@ -627,21 +715,71 @@ msgstr "ИÑходный файл" msgid "WebM file (Vorbis codec)" msgstr "WebMâ€Ñ„айл (кодек — Vorbis)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Сожалеем, Ñтот ролик не проиграетÑÑ, âŽ\n» потому что ваш браузер не поддерживает âŽ\n» видео в ÑоответÑтвии Ñо Ñтандартом HTML5." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Ð’Ñ‹ можете Ñкачать Ñовременный браузер,\n<span class=\"whitespace other\" title=\"Tab\">»</span> ÑпоÑобный воÑпроизводить Ñто видео, Ñ <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!" +" 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:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "WebM-файл (640p; VP8/Vorbis)" @@ -649,12 +787,6 @@ msgstr "WebM-файл (640p; VP8/Vorbis)" msgid "Add a collection" msgstr "Добавление коллекции" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Добавить" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -671,43 +803,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Удалить" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 "Ð’ Ñамом деле иÑключить %(media_title)s из %(collection_title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -720,67 +849,53 @@ msgstr "Привет, %(username)s.\nПользователь %(comment_author)s msgid "%(username)s's media" msgstr "Файлы %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Изображение «%(media_title)s»" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 "Ð”Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‚ÐºÐ¸ можете иÑпользовать Ñзык <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Добавить Ñтот комментарий" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "в" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Добавлено</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "СопутÑтвующие файлы" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "Добавить ÑопутÑтвующий файл" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "Добавить %(title)s в коллекцию" +msgid "Add “%(media_title)s†to a collection" +msgstr "Добавление «%(media_title)s» в коллекцию" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "Добавление новой коллекции" @@ -842,74 +957,58 @@ msgstr "ЕÑли Ñто были вы, и еÑли вы потерÑли ÑооР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 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Изменить наÑтройки учётной запиÑи" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Смотреть коллекции" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Добавить файлы" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(иÑключить)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Ð’ коллекциÑÑ…:" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "Ð’ %(collected)s коллекциÑÑ…" +#: 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/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 "ПоÑмотреть на <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Ð’Ñе права Ñохранены" @@ -940,23 +1039,64 @@ msgstr "более Ñтарые" msgid "Tagged with" msgstr "Метки" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Ðе удалоÑÑŒ прочитать файл Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÐ¼." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "КоллекциÑ" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "-- Выберите --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "Примечание" @@ -964,74 +1104,69 @@ msgstr "Примечание" msgid "commented on your post" msgstr "оÑтавил комментарий к вашему файлу" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Ой, ваш комментарий был пуÑÑ‚." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Ваш комментарий размещён!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "ПожалуйÑта, проверьте введённое и попробуйте ещё раз." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "Ðеобходимо выбрать или добавить коллекцию" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "«%s» — уже в коллекции «%s»" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "«%s» добавлено в коллекцию «%s»" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "Ðекоторые файлы от Ñтой запиÑи не обнаружены. Ð’ÑÑ‘ равно удалÑем." - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Ð’Ñ‹ удалили файл." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "Файл не удалён, так как вы не подтвердили Ñвою уверенноÑть галочкой." -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "Ð’Ñ‹ на пороге ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° другого пользователÑ. Будьте оÑторожны." -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "Ð’Ñ‹ иÑключили файл из коллекции." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "Файл не иÑключён из коллекции, так как вы не подтвердили Ñвоё намерение отметкой." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "Ð’Ñ‹ на пороге иÑÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° из коллекции другого пользователÑ. Будьте оÑторожны." -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "Ð’Ñ‹ удалили коллекцию «%s»" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "ÐšÐ¾Ð»Ð»ÐµÐºÑ†Ð¸Ñ Ð½Ðµ удалена, так как вы не подтвердили Ñвоё намерение отметкой." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 48a3873f..bc92bb13 100644 --- a/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po index 4d59ccd5..07932b77 100644 --- a/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po @@ -1,16 +1,20 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# 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 <zatroch.martin@gmail.com>, 2012. +# Morten Juhl-Johansen Zölde-Fejér <morten@writtenandread.net>, 2012. +# Olle Jonsson <olle.jonsson@gmail.com>, 2012. +# Tanja Trudslev <tanja.trudslev@gmail.com>, 2012. # <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: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -20,111 +24,125 @@ msgstr "" "Language: sk\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "Nesprávne použÃvateľské meno alebo e-mailová adresa." + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "Toto pole neakceptuje e-mailové adresy." + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "Toto pole vyžaduje e-mailovú adresu." + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" -msgstr "Prihlasovacie meno" +msgstr "PoužÃvateľské meno" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Heslo" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" -msgstr "E-mailová adresa" +msgstr "Email adresse" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "PoužÃvateľské meno alebo e-mailová adresa" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Nesprávny vstup" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." -msgstr "PrepáÄ, registrácia na tejto inÅ¡tancii nie je povolená." +msgstr "PrepáÄ, registrácia na danej inÅ¡tancii nie je povolená." -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." -msgstr "PrepáÄ, rovnaké prihlasovacie meno už niekto použÃva." +msgstr "PrepáÄ, rovnaké použÃvateľské meno už existuje." -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." -msgstr "PrepáÄ, použÃvateľ s rovnakou e-mailovou adresou už existuje." +msgstr "PrepáÄ, rovnaká e-mailová adresa už bola použitá na vytvorenie úÄtu." -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" -msgstr "Tvoja e-mailová adresa bola úspeÅ¡ne overená. MôžeÅ¡ sa hneÄ prihlásiÅ¥, upraviÅ¥ svoj profil a vkladaÅ¥ výtvory! " +msgstr "Tvoja e-mailová adresa bola overená. Teraz sa môžeÅ¡ prihlásiÅ¥, upravovaÅ¥ profil a vkladaÅ¥ výtvory!" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" -msgstr "Nesprávny overovacà kÄ¾ÃºÄ alebo použÃvateľský identifikátor" +msgstr "Overovacà kľúÄ, prÃpadne použÃvateľské meno je nesprávne." -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" -msgstr "Aby sme ti mohli zaslaÅ¥ e-mailovú správu, je potrebné byÅ¥ prihláseným!" +msgstr "Je potrebné prihlásiÅ¥ sa, aby sme vedeli kam máme e-mail zaslaÅ¥!" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" -msgstr "Tvoja e-mailová adresa už bola raz overená!" +msgstr "Už máš overenú e-mailovú adresu!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." -msgstr "Opätovne zaslaÅ¥ overovaciu správu na e-mail." +msgstr "Opätovne zaslaÅ¥ overovacà e-mail." + +#: mediagoblin/auth/views.py:250 +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:261 +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:263 +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." -msgstr "E-mailová správa s inÅ¡trukciami pre zmenu tvojho hesla bola odoslaná." +msgstr "E-mailová správa z inÅ¡trukciami na zmenu tvojho hesla bola zaslaná." -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 msgid "" "Could not send password recovery email as your username is inactive or your " "account's email address has not been verified." -msgstr "Nebolo ti možné zaslaÅ¥ e-mailovú správu ohľadom obnovy hesla, nakoľko je tvoje použÃvateľské meno buÄ neaktÃvne alebo e-mailová adresa úÄtu neoverená." +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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Nebolo možné nájsÅ¥ nikoho s týmto použÃvateľským menom alebo e-mailovou adresou." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." -msgstr "Teraz sa môžeÅ¡ prihlásiÅ¥ so svojim novým heslom." +msgstr "Už môžeÅ¡ použiÅ¥ nové heslo pri prihlasovanÃ." -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" -msgstr "Nadpis" +msgstr "Titulok" #: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 msgid "Description of this work" -msgstr "Charakteristika tohto diela" +msgstr "Popis výtvoru" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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> pri formátovanÃ." +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 "ZnaÄky" +msgstr "Å tÃtky" #: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 msgid "Separate tags by commas." -msgstr "Oddeľ jednotlivé Å¡tÃtky Äiarkami." +msgstr "Oddeľ Å¡tÃtky pomocou Äiarky." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Unikátna ÄasÅ¥ adresy" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Unikátna ÄasÅ¥ adresy nesmie byÅ¥ prázdna" @@ -132,7 +150,7 @@ msgstr "Unikátna ÄasÅ¥ adresy nesmie byÅ¥ prázdna" msgid "" "The title part of this media's address. You usually don't need to change " "this." -msgstr "Titulná ÄasÅ¥ adresy tohto výtvoru. VäÄÅ¡inou nie je potrebná zmena." +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 @@ -149,7 +167,7 @@ msgstr "Webstránka" #: mediagoblin/edit/forms.py:58 msgid "This address contains errors" -msgstr "Adresa obsahuje chyby" +msgstr "Daná adresa obsahuje chybu" #: mediagoblin/edit/forms.py:63 msgid "Old password" @@ -157,72 +175,93 @@ msgstr "Staré heslo" #: mediagoblin/edit/forms.py:64 msgid "Enter your old password to prove you own this account." -msgstr "PotvrÄ, že vlastnÃÅ¡ tento úÄet zadanÃm svojho starého hesla." +msgstr "Vlož svoje staré heslo na dôkaz toho, že vlastnÃÅ¡ daný úÄet." #: mediagoblin/edit/forms.py:67 msgid "New password" msgstr "Nové heslo" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "Preferencia licencie" + +#: mediagoblin/edit/forms.py:80 +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:82 msgid "Email me when others comment on my media" -msgstr "ZaslaÅ¥ mi e-mail, keÄ ostatnà pridajú komentár k médiu" +msgstr "ZaÅ¡li mi e-mail keÄ ostatnà okomentujú môj výtvor" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" -msgstr "" +msgstr "Titulok nesmie byÅ¥ prázdny." -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Popis danej kolekcie" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "Titulná ÄasÅ¥ adresy danej kolekcie. Zmena poľa nepovinná." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: mediagoblin/edit/views.py:85 msgid "You are editing another user's media. Proceed with caution." -msgstr "UpravujeÅ¡ médiá niekoho iného. Dbaj na to." +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Å¡ použÃvateľský profil. Dbaj na to." +msgstr "UpravujeÅ¡ profil iného použÃvateľa. Pristupuj zodpovedne. " -#: mediagoblin/edit/views.py:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" -msgstr "Úpravy profilu uložené" +msgstr "Zmeny v profile uložené" + +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Nesprávne heslo" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Nastavenia úÄtu uložené" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Nesprávne heslo" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "PotrebujeÅ¡ potvrdiÅ¥ odstránenie svojho úÄtu." -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Už máš kolekciu nazvanú ako \"%s\"!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." -msgstr "" +msgstr "Kolekcia s týmto Å¡tÃtkom už máš." -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "UpravujeÅ¡ kolekciu iného použÃvateľa. Pristupuj zodpovedne. " #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "Nemôžem priradiÅ¥ tému.. žiadny set témy\n" +msgstr "Nemožno pripojiÅ¥ tému... téma nenastavená\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" @@ -230,56 +269,64 @@ msgstr "Žiadny prieÄinok položiek pre túto tému\n" #: mediagoblin/gmg_commands/theme.py:74 msgid "However, old link directory symlink found; removed.\n" -msgstr "Hoci, starý symbolický odkaz na prieÄinok nájdený; odstránený.\n" +msgstr "Odstránené; hoci bol pôvodný symbolický odkaz adresára nájdený.\n" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" -msgstr "PrepáÄ, nepodporujem tento súborový typ =(" +msgstr "PrepáÄ, nepodporujem tento typ súborov =(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "Konvertovanie videa zlyhalo" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Poloha" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "PovoliÅ¥" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "ZakázaÅ¥" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Meno" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "Meno v rámci OAuth klienta" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Popis" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +msgstr "Toto bude viditeľné pre použÃvateľov,\n ktorà sa môžu identifikovaÅ¥ cez tvoju aplikáciu." -#: mediagoblin/plugins/oauth/forms.py:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Typ" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -287,135 +334,153 @@ msgid "" " <strong>Public</strong> - The client can't make confidential\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n" " JavaScript client)." -msgstr "" +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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "Presmerovacie URI" -#: mediagoblin/plugins/oauth/forms.py:55 +#: mediagoblin/plugins/oauth/forms.py:54 msgid "" "The redirect URI for the applications, this field\n" " is <strong>required</strong> for public clients." -msgstr "" +msgstr "Presmerovacie URI pre aplikácie, toto pole\nje <strong>požadované</strong> pre verejných klientov." -#: mediagoblin/plugins/oauth/forms.py:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Dané pole je požadované pre verejných klientov." #: mediagoblin/plugins/oauth/views.py:59 msgid "The client {0} has been registered!" -msgstr "" +msgstr "Klient {0} bol registrovaný!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "OAuth klientské spojenia" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." -msgstr "Odovzdaný nesprávny súbor pre daný typ média." +msgstr "Nesprávny typ súboru pre dané médium." #: mediagoblin/submit/forms.py:26 msgid "File" msgstr "Súbor" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "MusÃÅ¡ poskytnúť súbor." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" -msgstr "Juchú! ÚspeÅ¡ne vložené!" +msgstr "Skvelé! Pridané!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Obrázok stresujúceho goblina pri chybovom kóde Ä. 404" +msgstr "Kolekcia \"%s\" pridaná!" -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Ajaj!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Na danej adrese sa stránka zrejme nenachádza. PrepáÄ!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Ak vieÅ¡ s istotou, že adresa je správna, tak najskôr bola hľadaná stánka presunutá alebo zmazaná." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "MediaGoblin logo" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Over si e-mailovú adresu!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ PridaÅ¥ výtvor" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "ZobraziÅ¥ svoj profil" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "odhlásiÅ¥ sa" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "OdhlásiÅ¥ sa" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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ásenie" +msgstr "PrihlásiÅ¥ sa" + +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "ZmeniÅ¥ nastavenia úÄtu" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "PridaÅ¥ výtvor" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "VytvoriÅ¥ novú kolekciu" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Poháňa nás <a href=\"http://mediagoblin.org\">MediaGoblin</a>, súÄasÅ¥ projektu <a href=\"http://gnu.org/\">GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, 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 "Vydané pod <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Zdrojový kód</a> dostupný." +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/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Obrázok hysterického goblina" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "PreskúmaÅ¥" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Ahoj, vitaj na tejto MediaGoblin stránke!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "Pre umiestnenie vlastných médiÃ, pridanie komentárov a viac, sa môžeÅ¡ prihlásiÅ¥ so svojim MediaGoblin úÄtom." +msgstr "Pre pridanie vlastných výtvorov, komentárov a viac.. sa prihlás zo svojim MediaGoblin úÄtom." -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" -msgstr "EÅ¡te žiaden nemáš? Je to jednoduché!" +msgstr "Har du ikke en endnu? Det er let!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -423,21 +488,14 @@ msgid "" " <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\">ZaložiÅ¥ MediaGoblin na vlastnom serveri</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" -msgstr "NajÄerstvejÅ¡ie výtvory" - -#: 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" +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 tejto inÅ¡tancii." +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 @@ -447,7 +505,7 @@ 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 sa nespracúvajú" +msgstr "Žiadne výtvory v procese spracovania" #: mediagoblin/templates/mediagoblin/admin/panel.html:61 #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 @@ -457,7 +515,7 @@ 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 zlé položky!" +msgstr "Žiadne zlyhané položky!" #: mediagoblin/templates/mediagoblin/admin/panel.html:92 msgid "Last 10 successful uploads" @@ -471,11 +529,11 @@ 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 "Vlož svoje nové heslo" +msgstr "Nastav svoje nové heslo" #: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 msgid "Set password" -msgstr "Vlož heslo" +msgstr "Nastav heslo" #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 @@ -484,7 +542,7 @@ msgstr "ObnoviÅ¥ heslo" #: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 msgid "Send instructions" -msgstr "ZaslaÅ¥ postup" +msgstr "ZaslaÅ¥ inÅ¡trukcie" #: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 #, python-format @@ -506,7 +564,7 @@ msgstr "Prihlásenie zlyhalo!" #: mediagoblin/templates/mediagoblin/auth/login.html:44 msgid "Don't have an account yet?" -msgstr "EÅ¡te nemáš úÄet?" +msgstr "EÅ¡te stále nemáš úÄet?" #: mediagoblin/templates/mediagoblin/auth/login.html:45 msgid "Create one here!" @@ -519,7 +577,7 @@ msgstr "Zabudnuté heslo?" #: mediagoblin/templates/mediagoblin/auth/register.html:28 #: mediagoblin/templates/mediagoblin/auth/register.html:36 msgid "Create an account!" -msgstr "VytvoriÅ¥ úÄet!" +msgstr "Opret en konto!" #: mediagoblin/templates/mediagoblin/auth/register.html:40 msgid "Create" @@ -536,50 +594,88 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "PrÃlohy" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "UložiÅ¥ zmeny" +#: 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:47 +#: 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:41 +#: 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:59 +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 "" +msgstr "Úprava %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" -msgstr "Úprava profilu, ktorý vlastnà %(username)s" +msgstr "Úprava profilu, ktorý vlastnà %(username)s " #: mediagoblin/templates/mediagoblin/listings/collection.html:30 #: mediagoblin/templates/mediagoblin/listings/collection.html:35 @@ -591,13 +687,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Originál" @@ -616,7 +711,7 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "Originálny súbor" @@ -624,33 +719,77 @@ msgstr "Originálny súbor" msgid "WebM file (Vorbis codec)" msgstr "WebM súbor (Vorbis kodek)" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "PrepáÄ, toto video nepôjde prehraÅ¥ \n<span class=\"whitespace other\" title=\"Tab\">»</span> tvoj webový prehliadaÄ nepodporuje HTML5 \n<span class=\"whitespace other\" title=\"Tab\">»</span> video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "MôžeÅ¡ zÃskaÅ¥ moderný prehliadaÄ, ktorý \n<span class=\"whitespace other\" title=\"Tab\">»</span> vie prehraÅ¥ toto video na <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!" +" 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:59 +#: 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 "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "PridaÅ¥" +msgstr "PridaÅ¥ kolekciu" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 @@ -660,126 +799,109 @@ msgstr "Pridaj svoj výtvor" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "OdstrániÅ¥" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "SkutoÄne odstrániÅ¥ %(title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Odstráň permanentne" - #: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 #, python-format msgid "Really remove %(media_title)s from %(collection_title)s?" -msgstr "" +msgstr "SkutoÄne odstrániÅ¥ %(media_title)s z %(collection_title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 msgid "Remove" -msgstr "" +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 skomentoval tvoj prÃspevok (%(comment_url)s) na %(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:37 +#: 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:46 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" -msgstr "â– Prezeranie výtvorov podľa <a href=\"%(user_url)s\">%(username)s</a>" - -#: 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 "Obrázok pre %(media_title)s" +msgstr "â– Prehliadanie výtvorov od <a href=\"%(user_url)s\">%(username)s</a>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" -msgstr "Pridaj komentár" - -#: 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 "MôžeÅ¡ využiÅ¥ <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> pre formátovanie." +msgstr "PridaÅ¥ komentár" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "PridaÅ¥ tento komentár" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "o" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Pridané</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "PrÃlohy" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "PridaÅ¥ prÃlohu" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" -msgstr "" +msgid "Add “%(media_title)s†to a collection" +msgstr "PridaÅ¥ “%(media_title)s†do kolekcie" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +msgstr "PridaÅ¥ novú kolekciu" #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 msgid "" @@ -798,114 +920,98 @@ msgstr "Profil, ktorý vlastnà %(username)s" #: mediagoblin/templates/mediagoblin/user_pages/user.html:43 msgid "Sorry, no such user found." -msgstr "PrepáÄ, zadané použÃvateľské meno nenájdené." +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 "Potrebné overenie e-mailovej adresy" +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 ti musà byÅ¥ aktivovaný úÄet." +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-mailová správa s popisom ako to spraviÅ¥, by mal zanedlho doraziÅ¥." +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 sa tak nestalo:" +msgstr "V prÃpade, že nie:" #: mediagoblin/templates/mediagoblin/user_pages/user.html:65 msgid "Resend verification email" -msgstr "Opätovne zaslaÅ¥ overovaciu správu na e-mail" +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 prihlasovacÃm menom je už registrovaný, avÅ¡ak eÅ¡te stále neaktÃvny." +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áš overovaciu e-mailovú správu, tak sa môžeÅ¡ <a href=\"%(login_url)s\">prihlásiÅ¥</a> a preposlaÅ¥ si ju." +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 "Miesto, kde smieÅ¡ povedaÅ¥ Äo to o sebe ostatným." +msgstr "Na tomto mieste môžeÅ¡ povedaÅ¥ o sebe ostatným." -#: mediagoblin/templates/mediagoblin/user_pages/user.html:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 msgid "This user hasn't filled in their profile (yet)." -msgstr "DotyÄný použÃvateľ eÅ¡te nevyplnil svoj profil (zatiaľ)." +msgstr "DotyÄný použÃvateľ (zatiaľ) nevyplnil svoj profil." -#: mediagoblin/templates/mediagoblin/user_pages/user.html:125 -msgid "Change account settings" -msgstr "ZmeniÅ¥ nastavenia úÄtu" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "PrehliadaÅ¥ kolekcie" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 #, python-format msgid "View all of %(username)s's media" -msgstr "ZhliadnuÅ¥ vÅ¡etky výtvory, ktoré vlastnà %(username)s" +msgstr "ZobraziÅ¥ vÅ¡etky výtvory, ktoré vlastnà %(username)s" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:151 +#: 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, ale zatiaľ nemáš niÄ pridané." +msgstr "VÅ¡etky tvoje výtvory sa objavia práve tu, zatiaľ vÅ¡ak nemáš niÄ pridané." -#: mediagoblin/templates/mediagoblin/user_pages/user.html:157 -msgid "Add media" -msgstr "PridaÅ¥ výtvor" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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 "Najskôr sa tu eÅ¡te nenachádzajú žiadne výtvory..." +msgstr "Pravdepodobne sa tu nenachádzajú žiadne výtvory..." -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(odstrániÅ¥)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Zahrnuté" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" -msgstr "" +#: 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 "ÄŒÃtaÄka Atom" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:25 -msgid "Location" -msgstr "Poloha" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "ZobraziÅ¥ na <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Atom ÄÃtaÄka" #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" @@ -937,98 +1043,134 @@ msgstr "starÅ¡ie" msgid "Tagged with" msgstr "OznaÄené ako" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." -msgstr "Nebolo možné preÄÃtaÅ¥ obrazový súbor." +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/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:28 +#: 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:32 +#: mediagoblin/user_pages/forms.py:35 msgid "I am sure I want to remove this item from the collection" -msgstr "" +msgstr "SkutoÄne chcem odstrániÅ¥ danú položku z kolekcie" -#: mediagoblin/user_pages/forms.py:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Kolekcia" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" -msgstr "" +msgstr "-- VybraÅ¥ --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "PridaÅ¥ poznámku" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" -msgstr "skomentoval tvoj prÃspevok" +msgstr "okmentoval tvoj prÃspevok" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." -msgstr "Ajaj, tvoj komentár bol prázdny." +msgstr "Hopla, tvoj komentár bol prázdny." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" -msgstr "Tvoj komentár bol zaslaný!" +msgstr "Tvoj komentár bol pridaný!" + +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "ProsÃm skontroluj svoje položky a skús znova." -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "MusÃÅ¡ vybraÅ¥, prÃpadne pridaÅ¥ kolekciu" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "\"%s\" sa už nachádza v kolekcii \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "Niektoré súbory s danou položkou zrejme chýbajú.. Odstraňujem napriek tomu." +msgstr "\"%s pridané do kolekcie \"%s\"" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "Výtvor bol tebou odstránený." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." -msgstr "Chystáš sa odstrániÅ¥ výtvory niekoho iného. Dbaj na to." +msgstr "Chystáš sa odstrániÅ¥ výtvory niekoho iného. Pristupuj zodpovedne. " -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "Položka bola z kolekcie odstránená." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "Položka nebola odstránená, nakoľko polÃÄko potvrdenia nebolo oznaÄné." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +msgstr "Chystáš sa odstrániÅ¥ položku z kolekcie iného použÃvateľa. Pristupuj zodpovedne. " -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "Kolekcia \"%s\" bola úspeÅ¡ne odstránená." -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "Kolekcia nebola odstránená, nakoľko polÃÄko potrvdenia nebolo oznaÄené." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 Binary files differindex 38a19ef6..dd3de81b 100644 --- a/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po index d05f1ebc..98d62d59 100644 --- a/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -19,82 +19,96 @@ msgstr "" "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:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "UporabniÅ¡ko ime" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Geslo" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "E-poÅ¡tni naslov" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "Potrditveni kljuÄ ali uporabniÅ¡ka identifikacija je napaÄna" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Ponovno poÅ¡iljanje potrditvene e-poÅ¡te." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Naslov" @@ -103,8 +117,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +133,11 @@ msgstr "Oznake" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Oznaka" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Oznaka ne sme biti prazna" @@ -162,60 +176,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -231,54 +266,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -288,17 +331,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -306,7 +349,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Za vrsto vsebine je bila podana napaÄna datoteka." @@ -314,75 +372,74 @@ msgstr "Za vrsto vsebine je bila podana napaÄna datoteka." msgid "File" msgstr "Datoteka" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Podati morate datoteko." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Juhej! Poslano." -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Slika napake 404 s paniÄnim Å¡kratom" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Opa!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Oprostite. Videti je, da na tem naslovu ni nobene strani." - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "ÄŒe ste v toÄnost naslova prepriÄani, je bila iskana stran morda premaknjena ali pa izbrisana." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Logotip MediaGoblin" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: 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:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Dodaj vsebino" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -390,31 +447,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -422,17 +483,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -535,47 +589,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Shrani spremembe" +#: 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Urejanje profila – %(username)s" @@ -590,13 +682,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -615,7 +706,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -623,21 +714,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -645,12 +786,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -667,43 +802,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -716,67 +848,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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: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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -838,74 +956,58 @@ msgstr "ÄŒe ste ta oseba vi, a ste izgubili potrditveno e-poÅ¡to, se lahko <a hr 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Dodaj vsebino" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -936,23 +1038,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -960,74 +1103,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex be3b75db..276f1273 100644 --- a/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po index 5147dcc3..5c965623 100644 --- a/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po @@ -1,15 +1,16 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: +# Besnik Bleta <besnik@programeshqip.org>, 2012. # 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: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+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" @@ -19,82 +20,96 @@ msgstr "" "Language: sq\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Emër përdoruesi" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Fjalëkalim" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "Adresë email" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "Emër përdoruesi ose email" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "Futje e pasaktë" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 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 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Thuajse e keni verifikuar adresën tuaj email!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Ridërgoni email-in tuaj të verifikimit." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 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:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "Nuk u gjet dot dikush me atë emër përdoruesi ose email." - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 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:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titull" @@ -103,8 +118,8 @@ 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:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +134,11 @@ msgstr "Etiketa" msgid "Separate tags by commas." msgstr "Ndajini etiketat me presje." -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Identifikues" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Identifikuesi s'mund të jetë i zbrazët" @@ -162,123 +177,152 @@ msgstr "Jepni fjalëkalimin tuaj të vjetër që të provohet se këtë llogari msgid "New password" msgstr "Fjalëkalimi i ri" -#: mediagoblin/edit/forms.py:72 -msgid "Email me when others comment on my media" +#: mediagoblin/edit/forms.py:74 +msgid "License preference" msgstr "" -#: mediagoblin/edit/forms.py:84 -msgid "The title can't be empty" +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:82 +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:94 +msgid "The title can't be empty" +msgstr "Titulli s'mund të jetë i zbrazët" + +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "Përshkrim i këtij koleksioni" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "Pjesa titull e adresës së këtij koleksioni. Zakonisht nuk keni pse e ndryshoni këtë." -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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. Bëni kujdes." +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 "" + +#: 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. Bëni kujdes." +msgstr "Po përpunoni profilin e një përdoruesi. Hapni sytë." -#: mediagoblin/edit/views.py:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "Ndryshimet e profilit u ruajtën" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Fjalëkalim i gabuar" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "Rregullimet e llogarisë u ruajtën" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Fjalëkalim i gabuar" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "Keni tashmë një koleksion të quajtur \"%s\"!" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." -msgstr "" +msgstr "Ka tashmë një koleksion me atë identifikues për këtë përdorues." -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "Po përpunoni koleksionin e një tjetër përdoruesi. Hapni sytë." #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "" +msgstr "Nuk krijohet dot lidhje për te tema... nuk ka temë të caktuar\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" -msgstr "" +msgstr "Nuk ka drejtori asetesh për këtë temë\n" #: mediagoblin/gmg_commands/theme.py:74 msgid "However, old link directory symlink found; removed.\n" +msgstr "Sidoqoftë, u gjet simlidhje e vjetër drejtorie lidhjesh; u hoq.\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 "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "Na ndjeni, nuk e mbullojmë këtë lloj kartele :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" -msgstr "" +msgstr "Ndërkodimi i videos dështoi" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Vend" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -msgstr "" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "Lejoje" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "Mohoje" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "Emër" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "Emri i klientit OAuth" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "Përshkrim" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "Lloj" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -286,27 +330,42 @@ msgid "" " <strong>Public</strong> - The client can't make confidential\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n" " JavaScript client)." -msgstr "" +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:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "URI Ridrejtimi" -#: mediagoblin/plugins/oauth/forms.py:55 +#: mediagoblin/plugins/oauth/forms.py:54 msgid "" "The redirect URI for the applications, this field\n" " is <strong>required</strong> for public clients." -msgstr "" +msgstr "URI ridrejtimi për zbatimin, kjo fushë\n është <strong>e domosdoshme</strong> për klientë publikë." -#: mediagoblin/plugins/oauth/forms.py:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "Kjo fushë është e domosdoshme për klientë publikë" #: mediagoblin/plugins/oauth/views.py:59 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 "" -#: mediagoblin/processing/__init__.py:138 +#: 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 "Shtoni" + +#: mediagoblin/processing/__init__.py:172 msgid "Invalid file given for media type." msgstr "Kartelë e gabuar e dhënë për llojin e medias." @@ -314,75 +373,74 @@ msgstr "Kartelë e gabuar e dhënë për llojin e medias." msgid "File" msgstr "Kartelë" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Duhet të jepni një kartelë." -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Yhaaaaaa! U parashtrua!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Figurë 404 e djallushit në siklet" +msgstr "U shtua koleksioni \"%s\"!" -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Oooh!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Nuk duket të ketë ndonjë faqe te kjo adresë. Na ndjeni!" - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Nëse jeni i sigurt se adresa është e saktë, ndoshta faqja që po kërkoni është fshirë ose kaluar gjetkë." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "Logoja e MediaGoblin-it" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Verifikoni email-in tuaj!" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ Shtoni media" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "Shihni profilin tuaj" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" +msgstr "dilni" #: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" -msgstr "Dilni" - -#: mediagoblin/templates/mediagoblin/base.html:75 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "Ndryshoni rregullime llogarie" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Shtoni media" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Krijoni koleksion të ri" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "Bazuar në <a href=\"http://mediagoblin.org\">MediaGoblin</a>, një projekt <a href=\"http://gnu.org/\">GNU</a>." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -390,31 +448,35 @@ msgid "" "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/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Eksploroni" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Tungjatjeta juaj, mirë se vini te ky site MediaGoblin!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "" +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/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Nuk keni ende një të tillë? Është e lehtë!" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -422,21 +484,14 @@ msgid "" " <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\">Krijoni një llogarin te ky site</a>\n ose\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instaloni dhe rregulloni MediaGoblin-in te shërbyesi juaj</a>" -#: mediagoblin/templates/mediagoblin/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Mediat më të reja" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." -msgstr "" +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 @@ -456,16 +511,16 @@ 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 "" +msgstr "Pa zëra të dështuar!" #: mediagoblin/templates/mediagoblin/admin/panel.html:92 msgid "Last 10 successful uploads" -msgstr "" +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 "" +msgstr "Ende pa zëra të përpunuar!" #: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 #: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 @@ -535,47 +590,85 @@ msgid "" "%(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/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 "" +msgstr "Po përpunohen bashkangjitjet për %(media_title)s" -#: 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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "Bashkangjitje" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Ruaji ndryshimet" +#: 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:47 +#: 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:41 +#: 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:59 +msgid "Delete my account" +msgstr "" + #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "Po përpunohet %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Po përpunohet profili i %(username)s" @@ -590,13 +683,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Origjinal" @@ -615,41 +707,85 @@ msgid "" 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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" -msgstr "" +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/video.html:40 +#: 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/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: 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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "Na ndjeni, kjo video s'do të funksionojë, ngaqë \n\t shfletuesi juaj web s'mbulon video HTML5." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "Një shfletues web modern që \n\t mund ta luajë këtë video mund ta merrni te <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" -msgstr "" +msgstr "Kartelë WebM (640p; VP8/Vorbis)" #: mediagoblin/templates/mediagoblin/submit/collection.html:26 msgid "Add a collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "Shtoni" +msgstr "Shtoni një koleksion" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 @@ -659,49 +795,46 @@ msgstr "Shtoni media tuajën" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "Fshije" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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 "Të fshihet vërtet %(title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -msgstr "Fshije përgjithmonë" - #: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 #, python-format msgid "Really remove %(media_title)s from %(collection_title)s?" -msgstr "" +msgstr "Të hiqet vërtet %(media_title)s nga %(collection_title)s?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 msgid "Remove" +msgstr "Hiqe" + +#: 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 @@ -709,76 +842,62 @@ msgstr "" msgid "" "Hi %(username)s,\n" "%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" -msgstr "" +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:37 +#: 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 nga <a href=\"%(user_url)s\">%(username)s</a>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:46 +#: 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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 -#, python-format -msgid "Image for %(media_title)s" -msgstr "Figurë për %(media_title)s" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 msgid "Add a comment" msgstr "Shtoni një koment" -#: 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 "Për formatime mund të përdorni <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "Shtoje këtë koment" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "te" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>Shtuar më</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +msgstr "Shtoni një koleksion të ri" #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 msgid "" @@ -787,7 +906,7 @@ msgstr "Gjendjen e medias që po përpunohet për galerinë tuaj mund ta ndiqni #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 msgid "Your last 10 successful uploads" -msgstr "" +msgstr "10 ngarkimet tuaja më të suksesshme" #: mediagoblin/templates/mediagoblin/user_pages/user.html:31 #: mediagoblin/templates/mediagoblin/user_pages/user.html:89 @@ -838,74 +957,58 @@ msgstr "Nëse jeni ju ai person, por keni humbur email-in tuaj të verifikimit, 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" -msgstr "Ndryshoni rregullime llogarie" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Shtoni media" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(hiqe)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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 "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/geolocation_map.html:25 -msgid "Location" -msgstr "Vend" - -#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 -#, python-format -msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" -msgstr "Shiheni te <a href=\"%(osm_url)s\">OpenStreetMap</a>" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "Tërë të drejtat të rezervuara" @@ -936,98 +1039,134 @@ msgstr "më të vjetra" msgid "Tagged with" msgstr "Etiketuar me" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "Nuk lexoi dot kartelën e figurës." -#: mediagoblin/user_pages/forms.py:28 +#: 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/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 "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:32 +#: 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 "" -#: mediagoblin/user_pages/forms.py:35 +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" -msgstr "" +msgstr "-- Përzgjidhni --" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "Përfshini një shënim" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" -msgstr "" +msgstr "komentoi te postimi juaj" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "Hmmm, komenti juaj qe i zbrazët." -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "Komenti juaj u postua!" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "Ju lutemi, kontrolloni zërat tuaj dhe riprovoni." + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "Duhet të përzgjidhni ose shtoni një koleksion" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "\"%s\" gjendet tashmë te koleksioni \"%s\"" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" +msgstr "\"%s\" u shtua te koleksioni \"%s\"" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "E fshitë median." -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 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:317 +#: mediagoblin/user_pages/views.py:301 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. Bëni kujdes." +msgstr "Ju ndan një hap nga fshirja e medias të një tjetër përdoruesi. Hapni sytë." -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "E fshitë objektin prej koleksionit." -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "Objekti nuk u fshi ngaqë, nuk pohuat se jeni të sigurt për këtë." -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +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:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "E fshitë koleksionin \"%s\"" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "Koleksioni nuk u fshi ngaqë, nuk pohuat se jeni të sigurt për këtë." -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +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 Binary files differindex 132ae65d..f6918f71 100644 --- a/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po index 168337b2..d482151d 100644 --- a/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+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" @@ -18,82 +18,96 @@ msgstr "" "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:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "" @@ -102,8 +116,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -118,11 +132,11 @@ msgstr "" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -161,60 +175,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -230,54 +265,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -287,17 +330,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -305,7 +348,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "" @@ -313,75 +371,74 @@ msgstr "" msgid "File" msgstr "" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: mediagoblin/templates/mediagoblin/auth/login.html:28 -#: mediagoblin/templates/mediagoblin/auth/login.html:36 -#: mediagoblin/templates/mediagoblin/auth/login.html:54 -msgid "Log in" +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -389,31 +446,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -421,17 +482,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -534,47 +588,85 @@ msgid "" "%(verification_url)s" 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/edit.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit.html:35 -#, python-format -msgid "Editing %(media_title)s" +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "" @@ -589,13 +681,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -614,7 +705,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -622,21 +713,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -644,12 +785,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -666,43 +801,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -715,67 +847,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" +msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -837,74 +955,58 @@ msgstr "" 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -935,23 +1037,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -959,74 +1102,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex e2a0bc66..28ea51f8 100644 --- a/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po index fda30875..76bda505 100644 --- a/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+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" @@ -20,82 +20,96 @@ msgstr "" "Language: sv\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "Användarnamn" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "Lösenord" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "E-postadress" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: 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:75 +#: 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:79 +#: 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 +#: mediagoblin/auth/views.py:174 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 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "Verifieringsnyckeln eller användar-IDt är fel." -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 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 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "Du har redan verifierat din e-postadress!" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "Skickade ett nytt verifierings-email." -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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:285 -msgid "Couldn't find someone with that username or email." -msgstr "" - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "Titel" @@ -104,8 +118,8 @@ msgid "Description of this work" msgstr "Beskrivning av verket" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -120,11 +134,11 @@ msgstr "Taggar" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "Sökvägsnamn" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "Sökvägsnamnet kan inte vara tomt" @@ -163,60 +177,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 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:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" +msgstr "Fel lösenord" + +#: mediagoblin/edit/views.py:252 msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:252 -msgid "Wrong password" -msgstr "Fel lösenord" +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -232,54 +267,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -289,17 +332,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -307,7 +350,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "Ogiltig fil för mediatypen." @@ -315,75 +373,74 @@ msgstr "Ogiltig fil för mediatypen." msgid "File" msgstr "Fil" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "Du mÃ¥ste ange en fil" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "Tjohoo! Upladdat!" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Bild av stressat 404-troll." - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "Ojoj!" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "Ledsen, det verkar inte vara nÃ¥gonting här." - -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." -msgstr "Om du är säker pÃ¥ att adressen stämmer sÃ¥ kanske sidan du letar efter har flyttats eller tagits bort." - -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" -msgstr "MediaGoblin-logotyp" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" msgstr "Verifiera din e-postadress" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: 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:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Lägg till media" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -391,31 +448,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "Utforska" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "Hej, välkommen till den här MediaGoblin-sidan!" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "Har du inte ett redan?" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -423,17 +484,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "Senast medier" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -536,47 +590,85 @@ msgid "" "%(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/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/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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" msgstr "Spara ändringar" +#: 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "Redigerar %(username)ss profil" @@ -591,13 +683,12 @@ 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/image.html:23 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "Original" @@ -616,7 +707,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -624,21 +715,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -646,12 +787,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -668,43 +803,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -717,67 +849,53 @@ msgstr "" msgid "%(username)s's media" msgstr "%(username)ss media" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: 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:46 +#: 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: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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -839,74 +957,58 @@ msgstr "Om det är du som är den personen och har förlorat ditt e-postmeddelan 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:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "Lägg till media" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -937,23 +1039,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -961,74 +1104,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 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:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex e341a891..8cef4593 100644 --- a/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po index 17d5910c..3586ee78 100644 --- a/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -19,82 +19,96 @@ msgstr "" "Language: te\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "వాడà±à°•à°°à°¿ పేరà±" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "సంకేతపదం" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "శీరà±à°·à°¿à°•" @@ -103,8 +117,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -119,11 +133,11 @@ msgstr "" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -162,60 +176,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -231,54 +266,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -288,17 +331,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -306,7 +349,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "" @@ -314,75 +372,74 @@ msgstr "" msgid "File" msgstr "" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: mediagoblin/templates/mediagoblin/auth/login.html:28 -#: mediagoblin/templates/mediagoblin/auth/login.html:36 -#: mediagoblin/templates/mediagoblin/auth/login.html:54 -msgid "Log in" +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -390,31 +447,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -422,17 +483,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -535,47 +589,85 @@ msgid "" "%(verification_url)s" 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/edit.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit.html:35 -#, python-format -msgid "Editing %(media_title)s" +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "" @@ -590,13 +682,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -615,7 +706,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -623,21 +714,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -645,12 +786,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -667,43 +802,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -716,67 +848,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" +msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -838,74 +956,58 @@ msgstr "" 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -936,23 +1038,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -960,74 +1103,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 99f0c85b..d75d2eb2 100644 --- a/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po index 0729a2e7..a5e95640 100644 --- a/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po @@ -1,5 +1,5 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# Copyright (C) 2013 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # # Translators: @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+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" @@ -18,82 +18,96 @@ msgstr "" "Language: zh_TW.Big5\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" msgstr "" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" msgstr "" -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "" - -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." msgstr "" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." msgstr "" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." msgstr "" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" msgstr "" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" msgstr "" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" msgstr "" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" msgstr "" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." msgstr "" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." msgstr "" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 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 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." msgstr "" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "" @@ -102,8 +116,8 @@ msgid "Description of this work" msgstr "" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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" @@ -118,11 +132,11 @@ msgstr "" msgid "Separate tags by commas." msgstr "" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" msgstr "" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" msgstr "" @@ -161,60 +175,81 @@ msgstr "" msgid "New password" msgstr "" -#: mediagoblin/edit/forms.py:72 +#: mediagoblin/edit/forms.py:74 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:82 msgid "Email me when others comment on my media" msgstr "" -#: mediagoblin/edit/forms.py:84 +#: mediagoblin/edit/forms.py:94 msgid "The title can't be empty" msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" msgstr "" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." msgstr "" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:86 +#: 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:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" msgstr "" -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" +#: mediagoblin/edit/views.py:241 +msgid "Wrong password" msgstr "" #: mediagoblin/edit/views.py:252 -msgid "Wrong password" +msgid "Account settings saved" msgstr "" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" msgstr "" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." msgstr "" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." msgstr "" @@ -230,54 +265,62 @@ msgstr "" msgid "However, old link directory symlink found; removed.\n" msgstr "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: 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:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" +#: 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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:39 +#: 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:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -287,17 +330,17 @@ msgid "" " JavaScript client)." msgstr "" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" msgstr "" -#: mediagoblin/plugins/oauth/forms.py:55 +#: 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:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" msgstr "" @@ -305,7 +348,22 @@ msgstr "" msgid "The client {0} has been registered!" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "" @@ -313,75 +371,74 @@ msgstr "" msgid "File" msgstr "" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." msgstr "" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" msgstr "" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" +#: mediagoblin/templates/mediagoblin/base.html:64 +msgid "Verify your email!" msgstr "" -#: mediagoblin/templates/mediagoblin/404.html:28 -msgid "" -"If you're sure the address is correct, maybe the page you're looking for has" -" been moved or deleted." +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:50 -msgid "MediaGoblin logo" +#: mediagoblin/templates/mediagoblin/base.html:70 +#: 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:60 -msgid "Verify your email!" +#: mediagoblin/templates/mediagoblin/base.html:79 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" +#: mediagoblin/templates/mediagoblin/base.html:86 +msgid "Change account settings" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:69 -msgid "View your profile" +#: mediagoblin/templates/mediagoblin/base.html:93 +msgid "Log out" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:75 -#: mediagoblin/templates/mediagoblin/auth/login.html:28 -#: mediagoblin/templates/mediagoblin/auth/login.html:36 -#: mediagoblin/templates/mediagoblin/auth/login.html:54 -msgid "Log in" +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" msgstr "" -#: mediagoblin/templates/mediagoblin/base.html:89 +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, python-format msgid "" "Released under the <a " @@ -389,31 +446,35 @@ msgid "" "href=\"%(source_link)s\">Source code</a> available." msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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 +#: mediagoblin/templates/mediagoblin/root.html:36 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 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" msgstr "" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, python-format msgid "" "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" @@ -421,17 +482,10 @@ msgid "" " <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 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." @@ -534,47 +588,85 @@ msgid "" "%(verification_url)s" 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/edit.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit.html:35 -#, python-format -msgid "Editing %(media_title)s" +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" msgstr "" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: 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:59 +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:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" msgstr "" @@ -589,13 +681,12 @@ 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" msgstr "" @@ -614,7 +705,7 @@ msgid "" msgstr "" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 -#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" msgstr "" @@ -622,21 +713,71 @@ msgstr "" msgid "WebM file (Vorbis codec)" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +#: 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/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" -"\t your web browser does not support HTML5 \n" -"\t video." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" msgstr "" @@ -644,12 +785,6 @@ msgstr "" msgid "Add a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "" - #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" @@ -666,43 +801,40 @@ 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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 "" @@ -715,67 +847,53 @@ msgstr "" msgid "%(username)s's media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" +msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" msgstr "" @@ -837,74 +955,58 @@ msgstr "" 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 +#: 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:106 +#: 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:125 -msgid "Change account settings" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: 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:151 +#: 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:157 -msgid "Add media" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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/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 "" @@ -935,23 +1037,64 @@ msgstr "" msgid "Tagged with" msgstr "" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." msgstr "" -#: mediagoblin/user_pages/forms.py:28 +#: 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/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:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" msgstr "" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" msgstr "" @@ -959,74 +1102,69 @@ msgstr "" msgid "commented on your post" msgstr "" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." msgstr "" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" msgstr "" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" msgstr "" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" - -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." msgstr "" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." msgstr "" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." msgstr "" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." msgstr "" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" msgstr "" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." msgstr "" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 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 Binary files differindex 379f1bdc..3d267cfc 100644 --- a/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mo +++ b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po index b8673f51..bd50df78 100644 --- a/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po +++ b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po @@ -1,16 +1,17 @@ # Translations template for PROJECT. -# Copyright (C) 2012 ORGANIZATION +# 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. +# Harry Chen <harryhow@gmail.com>, 2011-2012. +# <medicalwei@gmail.com>, 2012. msgid "" msgstr "" "Project-Id-Version: GNU MediaGoblin\n" "Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" -"POT-Creation-Date: 2012-09-24 14:01-0500\n" -"PO-Revision-Date: 2012-09-24 18:57+0000\n" +"POT-Creation-Date: 2013-03-04 18:04-0600\n" +"PO-Revision-Date: 2013-03-05 00:04+0000\n" "Last-Translator: cwebber <cwebber@dustycloud.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -20,82 +21,96 @@ msgstr "" "Language: zh_TW\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +#: mediagoblin/auth/forms.py:28 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/forms.py:29 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/forms.py:30 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/forms.py:52 mediagoblin/auth/forms.py:67 msgid "Username" msgstr "使用者å稱" -#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/auth/forms.py:56 mediagoblin/auth/forms.py:71 msgid "Password" msgstr "密碼" -#: mediagoblin/auth/forms.py:34 +#: mediagoblin/auth/forms.py:60 msgid "Email address" -msgstr "é›»å郵件ä½ç½®" +msgstr "Email ä½å€" -#: mediagoblin/auth/forms.py:51 +#: mediagoblin/auth/forms.py:78 msgid "Username or email" -msgstr "使用者å稱或是電å郵件" - -#: mediagoblin/auth/forms.py:58 -msgid "Incorrect input" -msgstr "輸入錯誤" +msgstr "使用者å稱或 email" -#: mediagoblin/auth/views.py:55 +#: mediagoblin/auth/views.py:54 msgid "Sorry, registration is disabled on this instance." -msgstr "抱æ‰, é€™å€‹é …ç›®å·²ç¶“è¢«æš«åœè¨»å†Š." +msgstr "抱æ‰ï¼Œæœ¬ç«™å·²ç¶“æš«åœè¨»å†Šã€‚" -#: mediagoblin/auth/views.py:75 +#: mediagoblin/auth/views.py:68 msgid "Sorry, a user with that name already exists." -msgstr "抱æ‰, 這個使用者å稱已經å˜åœ¨." +msgstr "抱æ‰ï¼Œé€™å€‹ä½¿ç”¨è€…å稱已經å˜åœ¨ã€‚" -#: mediagoblin/auth/views.py:79 +#: mediagoblin/auth/views.py:72 msgid "Sorry, a user with that email address already exists." -msgstr "抱æ‰ï¼Œæ¤é›»å郵件已被註冊了。" +msgstr "抱æ‰ï¼Œæ¤ email ä½ç½®å·²ç¶“被註冊了。" -#: mediagoblin/auth/views.py:182 +#: mediagoblin/auth/views.py:174 msgid "" "Your email address has been verified. You may now login, edit your profile, " "and submit images!" -msgstr "ä½ çš„é›»å郵件ä½å€å·²è¢«èªè‰. ä½ ç¾åœ¨å°±å¯ä»¥ç™»å…¥, ç·¨è¼¯ä½ çš„å€‹äººæª”æ¡ˆè€Œä¸”éžäº¤ç…§ç‰‡!" +msgstr "您的 email ä½å€å·²è¢«èªè‰ã€‚您已經å¯ä»¥ç™»å…¥ï¼Œç·¨è¼¯æ‚¨çš„個人檔案並上傳圖片ï¼" -#: mediagoblin/auth/views.py:188 +#: mediagoblin/auth/views.py:180 msgid "The verification key or user id is incorrect" -msgstr "èªè‰ç¢¼æˆ–是使用者帳號錯誤" +msgstr "èªè‰ç¢¼æˆ–是使用者 ID 錯誤" -#: mediagoblin/auth/views.py:206 +#: mediagoblin/auth/views.py:198 msgid "You must be logged in so we know who to send the email to!" -msgstr "ä½ å¿…é ˆç™»å…¥ï¼Œæˆ‘å€‘æ‰çŸ¥é“ä¿¡è¦é€çµ¦èª°ï¼" +msgstr "æ‚¨å¿…é ˆç™»å…¥ï¼Œæˆ‘å€‘æ‰çŸ¥é“ä¿¡è¦é€çµ¦èª°ï¼" -#: mediagoblin/auth/views.py:214 +#: mediagoblin/auth/views.py:206 msgid "You've already verified your email address!" -msgstr "ä½ çš„é›»å郵件已經確èªäº†ï¼" +msgstr "您的電å郵件已經確èªäº†ï¼" -#: mediagoblin/auth/views.py:227 +#: mediagoblin/auth/views.py:219 msgid "Resent your verification email." -msgstr "é‡é€èªè‰ä¿¡." +msgstr "é‡é€èªè‰ä¿¡ã€‚" -#: mediagoblin/auth/views.py:263 +#: mediagoblin/auth/views.py:250 +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:261 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:264 msgid "" "An email has been sent with instructions on how to change your password." -msgstr "修改密碼的指示已經由電å郵件寄é€åˆ°ä½ 的信箱。" +msgstr "修改密碼的指示已經由電å郵件寄é€åˆ°æ‚¨çš„信箱。" -#: mediagoblin/auth/views.py:273 +#: mediagoblin/auth/views.py:271 msgid "" "Could not send password recovery email as your username is inactive or your " "account's email address has not been verified." -msgstr "無法傳é€å¯†ç¢¼å›žå¾©ä¿¡ä»¶ï¼Œå› ç‚ºä½ çš„ä½¿ç”¨è€…å稱已失效或是帳號尚未èªè‰ã€‚" +msgstr "無法傳é€å¯†ç¢¼å›žå¾©ä¿¡ä»¶ï¼Œå› 為您的使用者å稱已失效或是帳號尚未èªè‰ã€‚" -#: mediagoblin/auth/views.py:285 -msgid "Couldn't find someone with that username or email." -msgstr "找ä¸åˆ°ç›¸é—œçš„使用者å稱或是電å郵件。" - -#: mediagoblin/auth/views.py:333 +#: mediagoblin/auth/views.py:328 msgid "You can now log in using your new password." -msgstr "ä½ ç¾åœ¨å¯ä»¥ç”¨æ–°çš„密碼登入了ï¼" +msgstr "您ç¾åœ¨å¯ä»¥ç”¨æ–°çš„密碼登入了ï¼" -#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:83 +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:93 #: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 -#: mediagoblin/user_pages/forms.py:40 +#: mediagoblin/user_pages/forms.py:45 msgid "Title" msgstr "標題" @@ -104,13 +119,13 @@ msgid "Description of this work" msgstr "這個作å“çš„æè¿°" #: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 -#: mediagoblin/edit/forms.py:87 mediagoblin/submit/forms.py:32 -#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:44 +#: mediagoblin/edit/forms.py:97 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> 來排版." +msgstr "您å¯ä»¥ç”¨ <a href=\"http://markdown.tw\">Markdown</a> 來排版。" #: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 msgid "Tags" @@ -120,19 +135,19 @@ msgstr "標籤" msgid "Separate tags by commas." msgstr "用逗號分隔標籤。" -#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:91 +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:101 msgid "Slug" -msgstr "自訂å—串" +msgstr "簡稱" -#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:92 +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:102 msgid "The slug can't be empty" -msgstr "自訂å—串ä¸èƒ½ç©ºç™½" +msgstr "簡稱ä¸èƒ½ç‚ºç©ºç™½" #: mediagoblin/edit/forms.py:40 msgid "" "The title part of this media's address. You usually don't need to change " "this." -msgstr "æ¤åª’體網å€çš„åç¨±ã€‚ä½ é€šå¸¸ä¸éœ€è¦è®Šå‹•這個的。" +msgstr "æ¤åª’體網å€çš„æ¨™é¡Œéƒ¨ä»½ã€‚通常ä¸éœ€è¦ä¿®æ”¹ã€‚" #: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 #: mediagoblin/templates/mediagoblin/utils/license.html:20 @@ -149,7 +164,7 @@ msgstr "網站" #: mediagoblin/edit/forms.py:58 msgid "This address contains errors" -msgstr "ç¶²å€æ˜¯ä¸æ˜¯æ‰“錯了? " +msgstr "本網å€å‡ºéŒ¯äº†" #: mediagoblin/edit/forms.py:63 msgid "Old password" @@ -157,129 +172,158 @@ msgstr "舊的密碼" #: mediagoblin/edit/forms.py:64 msgid "Enter your old password to prove you own this account." -msgstr "è¼¸å…¥ä½ çš„èˆŠå¯†ç¢¼ä¾†è‰æ˜Žä½ æ“æœ‰é€™å€‹å¸³è™Ÿã€‚" +msgstr "è¼¸å…¥æ‚¨çš„èˆŠå¯†ç¢¼ä¾†è‰æ˜Žæ‚¨æ“有這個帳號。" #: mediagoblin/edit/forms.py:67 msgid "New password" msgstr "新密碼" -#: mediagoblin/edit/forms.py:72 -msgid "Email me when others comment on my media" +#: mediagoblin/edit/forms.py:74 +msgid "License preference" msgstr "" -#: mediagoblin/edit/forms.py:84 -msgid "The title can't be empty" +#: mediagoblin/edit/forms.py:80 +msgid "This will be your default license on upload forms." msgstr "" -#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:50 -#: mediagoblin/user_pages/forms.py:43 +#: mediagoblin/edit/forms.py:82 +msgid "Email me when others comment on my media" +msgstr "ç•¶æœ‰äººå°æˆ‘的媒體評論時寄信給我" + +#: mediagoblin/edit/forms.py:94 +msgid "The title can't be empty" +msgstr "標題ä¸èƒ½æ˜¯ç©ºçš„" + +#: mediagoblin/edit/forms.py:96 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 msgid "Description of this collection" -msgstr "" +msgstr "這個è’è—çš„æè¿°" -#: mediagoblin/edit/forms.py:93 +#: mediagoblin/edit/forms.py:103 msgid "" "The title part of this collection's address. You usually don't need to " "change this." -msgstr "" +msgstr "æ¤è’è—ç¶²å€çš„æ¨™é¡Œéƒ¨ä»½ï¼Œé€šå¸¸ä¸éœ€è¦ä¿®æ”¹ã€‚" -#: mediagoblin/edit/views.py:65 +#: mediagoblin/edit/views.py:66 msgid "An entry with that slug already exists for this user." -msgstr "這個自訂å—串已經被其他人用了" +msgstr "這個簡稱已經被其他人用了" -#: mediagoblin/edit/views.py:86 +#: mediagoblin/edit/views.py:85 msgid "You are editing another user's media. Proceed with caution." -msgstr "ä½ æ£åœ¨ç·¨è¼¯ä»–人的媒體檔案. 請謹慎處ç†." +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 "ä½ æ£åœ¨ç·¨è¼¯ä¸€ä½ç”¨æˆ¶çš„æª”案. 請謹慎處ç†." +msgstr "您æ£åœ¨ä¿®æ”¹åˆ¥äººçš„個人檔案,請å°å¿ƒæ“作。" -#: mediagoblin/edit/views.py:198 +#: mediagoblin/edit/views.py:204 msgid "Profile changes saved" -msgstr "ä¿®æ”¹çš„æª”æ¡ˆå·²å˜æª”" - -#: mediagoblin/edit/views.py:227 mediagoblin/edit/views.py:247 -msgid "Account settings saved" -msgstr "帳號è¨å®šå·²å˜æª”" +msgstr "個人檔案修改已儲å˜" -#: mediagoblin/edit/views.py:252 +#: mediagoblin/edit/views.py:241 msgid "Wrong password" msgstr "密碼錯誤" -#: mediagoblin/edit/views.py:288 mediagoblin/submit/views.py:211 -#: mediagoblin/user_pages/views.py:215 +#: mediagoblin/edit/views.py:252 +msgid "Account settings saved" +msgstr "帳號è¨å®šå·²å„²å˜" + +#: mediagoblin/edit/views.py:286 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:322 mediagoblin/submit/views.py:142 +#: mediagoblin/user_pages/views.py:214 #, python-format msgid "You already have a collection called \"%s\"!" -msgstr "" +msgstr "您已經有一個稱åšã€Œ%sã€çš„è’è—了ï¼" -#: mediagoblin/edit/views.py:292 +#: mediagoblin/edit/views.py:326 msgid "A collection with that slug already exists for this user." -msgstr "" +msgstr "這個使用者已經有使用該簡稱的è’è—了。" -#: mediagoblin/edit/views.py:309 +#: mediagoblin/edit/views.py:343 msgid "You are editing another user's collection. Proceed with caution." -msgstr "" +msgstr "您æ£åœ¨ä¿®æ”¹åˆ¥äººçš„è’è—,請å°å¿ƒæ“作。" #: mediagoblin/gmg_commands/theme.py:58 msgid "Cannot link theme... no theme set\n" -msgstr "" +msgstr "無法連çµä½ˆæ™¯â€¦æ²’有æ¤ä½ˆæ™¯\n" #: mediagoblin/gmg_commands/theme.py:71 msgid "No asset directory for this theme\n" -msgstr "" +msgstr "æ¤ä½ˆæ™¯æ²’æœ‰ç´ æç›®éŒ„\n" #: mediagoblin/gmg_commands/theme.py:74 msgid "However, old link directory symlink found; removed.\n" +msgstr "但是舊的目錄連çµå·²ç¶“找到並移除。\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 "" -#: mediagoblin/media_types/__init__.py:60 -#: mediagoblin/media_types/__init__.py:120 +#: mediagoblin/media_types/__init__.py:61 +#: mediagoblin/media_types/__init__.py:102 msgid "Sorry, I don't support that file type :(" msgstr "抱æ‰ï¼Œæˆ‘䏿”¯æ´é€™æ¨£çš„æª”æ¡ˆæ ¼å¼ :(" -#: mediagoblin/media_types/video/processing.py:35 +#: mediagoblin/media_types/video/processing.py:36 msgid "Video transcoding failed" -msgstr "" +msgstr "å½±åƒè½‰ç¢¼å¤±æ•—" -#: mediagoblin/plugins/oauth/forms.py:26 -msgid "Client ID" -msgstr "" +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "ä½ç½®" -#: mediagoblin/plugins/oauth/forms.py:28 -msgid "Next URL" -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:30 +#: mediagoblin/plugins/oauth/forms.py:29 msgid "Allow" -msgstr "" +msgstr "å…許" -#: mediagoblin/plugins/oauth/forms.py:31 +#: mediagoblin/plugins/oauth/forms.py:30 msgid "Deny" -msgstr "" +msgstr "拒絕" -#: mediagoblin/plugins/oauth/forms.py:35 +#: mediagoblin/plugins/oauth/forms.py:34 msgid "Name" -msgstr "" +msgstr "å稱" -#: mediagoblin/plugins/oauth/forms.py:36 +#: mediagoblin/plugins/oauth/forms.py:35 msgid "The name of the OAuth client" -msgstr "" +msgstr "OAuth client çš„å稱" -#: mediagoblin/plugins/oauth/forms.py:37 +#: mediagoblin/plugins/oauth/forms.py:36 msgid "Description" -msgstr "" +msgstr "æè¿°" -#: mediagoblin/plugins/oauth/forms.py:39 +#: mediagoblin/plugins/oauth/forms.py:38 msgid "" "This will be visible to users allowing your\n" " application to authenticate as them." -msgstr "" +msgstr "本æè¿°å°‡æœƒè¢«é€²è¡Œæ‡‰ç”¨ç¨‹å¼èªè¨¼çš„使用者看到。" -#: mediagoblin/plugins/oauth/forms.py:41 +#: mediagoblin/plugins/oauth/forms.py:40 msgid "Type" -msgstr "" +msgstr "類型" -#: mediagoblin/plugins/oauth/forms.py:46 +#: 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" @@ -287,27 +331,42 @@ msgid "" " <strong>Public</strong> - The client can't make confidential\n" " requests to the GNU MediaGoblin instance (e.g. client-side\n" " JavaScript client)." -msgstr "" +msgstr "<strong>秘密</strong> — OAuth client å¯ä»¥å° GNU MediaGoblin ç«™å°ç™¼é€ä¸è¢«ä½¿ç”¨è€…ä»£ç†æ””截的請求 (例如伺æœç«¯çš„ client)。\n<strong>公開</strong> — OAuth client ç„¡æ³•å° GNU MediaGoblin ç«™å°ç™¼é€ç§˜å¯†çš„請求 (例如客戶端的 JavaScript client)。" -#: mediagoblin/plugins/oauth/forms.py:53 +#: mediagoblin/plugins/oauth/forms.py:52 msgid "Redirect URI" -msgstr "" +msgstr "é‡å®šå‘ URI" -#: mediagoblin/plugins/oauth/forms.py:55 +#: mediagoblin/plugins/oauth/forms.py:54 msgid "" "The redirect URI for the applications, this field\n" " is <strong>required</strong> for public clients." -msgstr "" +msgstr "æ¤æ‡‰ç”¨ç¨‹å¼çš„é‡å®šå‘ URI,本欄ä½åœ¨å…¬é–‹é¡žåž‹çš„ OAuth client 為必填。" -#: mediagoblin/plugins/oauth/forms.py:67 +#: mediagoblin/plugins/oauth/forms.py:66 msgid "This field is required for public clients" -msgstr "" +msgstr "本欄ä½åœ¨å…¬é–‹é¡žåž‹çš„ OAuth client 為必填" #: mediagoblin/plugins/oauth/views.py:59 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 "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" msgstr "" -#: mediagoblin/processing/__init__.py:138 +#: 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:172 msgid "Invalid file given for media type." msgstr "指定錯誤的媒體類別ï¼" @@ -315,129 +374,125 @@ msgstr "指定錯誤的媒體類別ï¼" msgid "File" msgstr "檔案" -#: mediagoblin/submit/views.py:57 +#: mediagoblin/submit/views.py:51 msgid "You must provide a file." -msgstr "ä½ å¿…é ˆæä¾›ä¸€å€‹æª”案" +msgstr "æ‚¨å¿…é ˆæä¾›ä¸€å€‹æª”案" -#: mediagoblin/submit/views.py:164 +#: mediagoblin/submit/views.py:97 msgid "Woohoo! Submitted!" -msgstr "呼呼! é€å‡ºåŽ»åš•!" +msgstr "啊哈ï¼PO 上去啦ï¼" -#: mediagoblin/submit/views.py:215 +#: mediagoblin/submit/views.py:146 #, python-format msgid "Collection \"%s\" added!" -msgstr "" - -#: mediagoblin/templates/mediagoblin/404.html:24 -msgid "Image of 404 goblin stressing out" -msgstr "Image of 404 goblin stressing out" - -#: mediagoblin/templates/mediagoblin/404.html:25 -msgid "Oops!" -msgstr "糟糕ï¼" - -#: mediagoblin/templates/mediagoblin/404.html:26 -msgid "There doesn't seem to be a page at this address. Sorry!" -msgstr "這個ä½å€ä¼¼ä¹Žæ²’有網é 。抱æ‰ï¼" +msgstr "è’è—「%sã€æ–°å¢žå®Œæˆï¼" -#: mediagoblin/templates/mediagoblin/404.html:28 -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 標誌" - -#: mediagoblin/templates/mediagoblin/base.html:60 +#: mediagoblin/templates/mediagoblin/base.html:64 msgid "Verify your email!" -msgstr "確èªä½ 的電å郵件" +msgstr "ç¢ºèªæ‚¨çš„é›»å郵件" -#: mediagoblin/templates/mediagoblin/base.html:66 -msgid "+ Add media" -msgstr "+ åŠ å…¥åª’é«”" - -#: mediagoblin/templates/mediagoblin/base.html:67 -msgid "+ Add collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/base.html:69 -msgid "View your profile" -msgstr "æª¢è¦–ä½ å€‹äººæª”æ¡ˆ" - -#: mediagoblin/templates/mediagoblin/base.html:70 -msgid "Log out" +#: mediagoblin/templates/mediagoblin/base.html:65 +msgid "log out" msgstr "登出" -#: mediagoblin/templates/mediagoblin/base.html:75 +#: mediagoblin/templates/mediagoblin/base.html:70 #: 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:89 +#: mediagoblin/templates/mediagoblin/base.html:79 +#, 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:86 +msgid "Change account settings" +msgstr "更改帳號è¨å®š" + +#: mediagoblin/templates/mediagoblin/base.html:90 +#: mediagoblin/templates/mediagoblin/base.html:105 +#: 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:93 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "新增媒體" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "新增新的è’è—" + +#: mediagoblin/templates/mediagoblin/base.html:122 +#, python-format msgid "" -"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " -"href=\"http://gnu.org/\">GNU</a> project." -msgstr "ç”± <a href=\"http://mediagoblin.org\">MediaGoblin</a>製作, 它是一個 <a href=\"http://gnu.org/\">GNU</a> 專案" +"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/base.html:92 +#: mediagoblin/templates/mediagoblin/base.html:125 #, 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>." +msgstr "以 <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a> 授權釋出。備有<a href=\"%(source_link)s\">原始碼</a>。" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "滿臉å•號的哥布林" -#: mediagoblin/templates/mediagoblin/root.html:24 +#: mediagoblin/templates/mediagoblin/root.html:31 msgid "Explore" msgstr "探索" -#: mediagoblin/templates/mediagoblin/root.html:26 +#: mediagoblin/templates/mediagoblin/root.html:33 msgid "Hi there, welcome to this MediaGoblin site!" -msgstr "å˜¿ï¼æ¡è¿Žä¾†åˆ° 媒體怪ç¸(MediaGoblin) 網站" +msgstr "å˜¿ï¼æ¡è¿Žä¾†åˆ° MediaGoblin ç«™å°ï¼ " -#: mediagoblin/templates/mediagoblin/root.html:28 +#: mediagoblin/templates/mediagoblin/root.html:35 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>, 他是一個超讚的媒體分享架站軟體." +msgstr "本站使用 <a href=\"http://mediagoblin.org\">MediaGoblin</a> — 與眾ä¸åŒçš„媒體分享網站。" -#: mediagoblin/templates/mediagoblin/root.html:29 +#: mediagoblin/templates/mediagoblin/root.html:36 msgid "" "To add your own media, place comments, and more, you can log in with your " "MediaGoblin account." -msgstr "" +msgstr "您å¯ä»¥ç™»å…¥æ‚¨çš„ MediaGoblin 帳號以進行上傳媒體ã€å¼µè²¼è©•è«–ç‰ç‰ã€‚" -#: mediagoblin/templates/mediagoblin/root.html:31 +#: mediagoblin/templates/mediagoblin/root.html:38 msgid "Don't have one yet? It's easy!" -msgstr "還沒有嗎?其實éžå¸¸ç°¡å–®ï¼" +msgstr "沒有帳號嗎?開帳號很簡單ï¼" -#: mediagoblin/templates/mediagoblin/root.html:32 +#: mediagoblin/templates/mediagoblin/root.html:39 #, 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\">åœ¨ä½ çš„ä¼ºæœå™¨ä¸Šå»ºç«‹ä¸€å€‹è‡ªå·±çš„媒體怪ç¸(MedaiGoblin)</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/root.html:40 +#: mediagoblin/templates/mediagoblin/root.html:47 msgid "Most recent media" msgstr "最新的媒體" -#: 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/admin/panel.html:29 msgid "" "Here you can track the state of media being processed on this instance." -msgstr "" +msgstr "æ¤è™•您å¯ä»¥è¿½è¹¤æœ¬ç«™å°è™•ç†åª’體的狀態。" #: mediagoblin/templates/mediagoblin/admin/panel.html:32 #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 @@ -452,26 +507,26 @@ msgstr "沒有æ£åœ¨è™•ç†ä¸çš„媒體" #: mediagoblin/templates/mediagoblin/admin/panel.html:61 #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 msgid "These uploads failed to process:" -msgstr "無法處ç†é€™äº›æ›´æ–°" +msgstr "無法處ç†é€™äº›ä¸Šå‚³å…§å®¹ï¼š" #: mediagoblin/templates/mediagoblin/admin/panel.html:90 #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 msgid "No failed entries!" -msgstr "" +msgstr "沒有失敗的紀錄ï¼" #: mediagoblin/templates/mediagoblin/admin/panel.html:92 msgid "Last 10 successful uploads" -msgstr "" +msgstr "最近 10 次æˆåŠŸä¸Šå‚³çš„ç´€éŒ„" #: mediagoblin/templates/mediagoblin/admin/panel.html:112 #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 msgid "No processed entries, yet!" -msgstr "" +msgstr "ç¾åœ¨é‚„沒有處ç†çš„紀錄ï¼" #: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 #: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 msgid "Set your new password" -msgstr "è¨å®šä½ 的新密碼" +msgstr "è¨å®šæ‚¨çš„æ–°å¯†ç¢¼" #: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 msgid "Set password" @@ -498,7 +553,7 @@ msgid "" "\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å¦‚æžœä½ èªç‚ºé€™å€‹æ˜¯å€‹èª¤æœƒï¼Œè«‹å¿½ç•¥æ¤å°ä¿¡ä»¶ï¼Œç¹¼çºŒç•¶å€‹å¿«æ¨‚çš„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!" @@ -506,11 +561,11 @@ msgstr "登入失敗ï¼" #: mediagoblin/templates/mediagoblin/auth/login.html:44 msgid "Don't have an account yet?" -msgstr "還沒有帳號嗎?" +msgstr "還沒有帳號嗎?" #: mediagoblin/templates/mediagoblin/auth/login.html:45 msgid "Create one here!" -msgstr "在這裡建立一個å§!" +msgstr "在這裡建立一個å§ï¼" #: mediagoblin/templates/mediagoblin/auth/login.html:51 msgid "Forgot your password?" @@ -519,7 +574,7 @@ msgstr "忘了密碼嗎?" #: mediagoblin/templates/mediagoblin/auth/register.html:28 #: mediagoblin/templates/mediagoblin/auth/register.html:36 msgid "Create an account!" -msgstr "建立一個帳號!" +msgstr "建立一個帳號ï¼" #: mediagoblin/templates/mediagoblin/auth/register.html:40 msgid "Create" @@ -534,52 +589,90 @@ msgid "" "your web browser:\n" "\n" "%(verification_url)s" -msgstr "å—¨ %(username)s,\n\n啟動 GNU MediaGoblin 帳號, åœ¨ä½ çš„ç€è¦½å™¨ä¸æ‰“開下é¢çš„ç¶²å€:\n\n%(verification_url)s" +msgstr "%(username)s 您好:\n\nè¦å•Ÿå‹• GNU MediaGoblin 帳號,請在您的ç€è¦½å™¨ä¸æ‰“開下é¢çš„ç¶²å€:\n\n%(verification_url)s" + +#: 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 "" +msgstr "編輯 %(media_title)s 的附件" -#: 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/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:159 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:175 +msgid "Attachments" +msgstr "附件" -#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:181 +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:53 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:82 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +#: 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/edit.html:43 -#: mediagoblin/templates/mediagoblin/edit/edit_account.html:47 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:52 #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 msgid "Save changes" 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:47 +#: 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:41 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 #, python-format msgid "Changing %(username)s's account settings" -msgstr "æ£åœ¨æ”¹è®Š %(username)s的帳號è¨å®š" +msgstr "æ£åœ¨æ”¹è®Š %(username)s 的帳號è¨å®š" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:59 +msgid "Delete my account" +msgstr "" #: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 #, python-format msgid "Editing %(collection_title)s" -msgstr "" +msgstr "編輯 %(collection_title)s" #: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 -#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 #, python-format msgid "Editing %(username)s's profile" -msgstr "編輯 %(username)s'的檔案ä¸" +msgstr "編輯 %(username)s 的個人檔案" #: mediagoblin/templates/mediagoblin/listings/collection.html:30 #: mediagoblin/templates/mediagoblin/listings/collection.html:35 @@ -587,122 +680,162 @@ msgstr "編輯 %(username)s'的檔案ä¸" #: mediagoblin/templates/mediagoblin/listings/tag.html:35 #, python-format msgid "Media tagged with: %(tag_name)s" -msgstr "æ¤åª’體被標è˜ç‚ºï¼š%(tag_name)s" +msgstr "æ¤åª’體被 tag æˆï¼š%(tag_name)s" #: 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 +#: 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 -#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 msgid "Original" -msgstr "原始的" +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音訊。" +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\">\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/video.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 msgid "Original file" -msgstr "" +msgstr "原始檔案" #: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 msgid "WebM file (Vorbis codec)" -msgstr "WebM 檔案 (Vorbis codec)" +msgstr "WebM 檔案 (Vorbis 編碼)" + +#: 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/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/video.html:40 +#: 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" -"\t your web browser does not support HTML5 \n" -"\t video." -msgstr "抱æ‰, æ¤å½±ç‰‡ç„¡æ³•ä½¿ç”¨ï¼Œå› ç‚º \n<span class=\"whitespace other\" title=\"Tab\">»</span> ä½ çš„ç€è¦½å™¨ä¸æ”¯æ´ HTML5 \n<span class=\"whitespace other\" title=\"Tab\">»</span> 的影片." +"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:43 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 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 "ä½ å¯ä»¥å–å¾—\n<span class=\"whitespace other\" title=\"Tab\">»</span> æ’æ”¾é€™æ¨£æª”案的最新ç€è¦½å™¨ <a href=\"http://getfirefox.com\">\n<span class=\"whitespace other\" title=\"Tab\">»</span> http://getfirefox.com</a>!" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" -#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 msgid "WebM file (640p; VP8/Vorbis)" -msgstr "" +msgstr "WebM 檔案 (640p; VP8/Vorbis)" #: mediagoblin/templates/mediagoblin/submit/collection.html:26 msgid "Add a collection" -msgstr "" - -#: mediagoblin/templates/mediagoblin/submit/collection.html:30 -#: mediagoblin/templates/mediagoblin/submit/start.html:34 -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:83 -msgid "Add" -msgstr "å¢žåŠ " +msgstr "新增è’è—" #: mediagoblin/templates/mediagoblin/submit/start.html:23 #: mediagoblin/templates/mediagoblin/submit/start.html:30 msgid "Add your media" -msgstr "åŠ å…¥ä½ çš„åª’é«”" +msgstr "åŠ å…¥æ‚¨çš„åª’é«”" #: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 #, python-format msgid "%(collection_title)s (%(username)s's collection)" -msgstr "" +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 "" +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:87 +#: 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:91 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 msgid "Delete" msgstr "刪除" -#: mediagoblin/templates/mediagoblin/user_pages/collection.html:59 -#, python-format -msgid "" -"<p>\n" -" %(collection_description)s\n" -" </p>" -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_confirm_delete.html:47 -#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 -msgid "Delete permanently" -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 "" +msgstr "確定è¦å¾ž %(collection_title)s 移除 %(media_title)s 嗎?" -#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:53 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 @@ -710,95 +843,81 @@ msgstr "" msgid "" "Hi %(username)s,\n" "%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" -msgstr "" +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:37 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 #, python-format -msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" -msgstr "<a href=\"%(user_url)s\">%(username)s</a>的媒體檔案" +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/media.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 #, python-format -msgid "â– Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" -msgstr "â– ç”± <a href=\"%(user_url)s\">%(username)s</a>ç€è¦½åª’體檔案" +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:67 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 #, python-format -msgid "Image for %(media_title)s" -msgstr " %(media_title)s的照片" +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:102 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:94 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 "ä½ å¯ä»¥ç”¨ <a href=\"http://daringfireball.net/projects/markdown/basics\"> Markdown</a> 來排版." - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 msgid "Add this comment" -msgstr "å¢žåŠ æ¤è©•è«–" +msgstr "å¢žåŠ è©•è«–" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:123 msgid "at" msgstr "在" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:144 #, python-format msgid "" "<h3>Added on</h3>\n" " <p>%(date)s</p>" msgstr "<h3>åŠ å…¥æ—¥æœŸ</h3>\n <p>%(date)s</p>" -#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 -#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 -msgid "Attachments" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media.html:192 -msgid "Add attachment" -msgstr "" - -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:35 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 #, python-format -msgid "Add %(title)s to collection" +msgid "Add “%(media_title)s†to a collection" msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 msgid "+" -msgstr "" +msgstr "+" -#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 msgid "Add a new collection" -msgstr "" +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 "é‡å°ä½ 的展示å€ï¼Œä½ å¯ä»¥åœ¨é€™è£¡è¿½è¹¤åª’體處ç†çš„狀態。" +msgstr "您å¯ä»¥åœ¨é€™è£¡è¿½è¹¤æ‚¨çš„è—廊ä¸åª’體處ç†çš„狀態。" #: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 msgid "Your last 10 successful uploads" -msgstr "" +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的個人檔案" +msgstr "%(username)s 的個人檔案" #: mediagoblin/templates/mediagoblin/user_pages/user.html:43 msgid "Sorry, no such user found." -msgstr "抱æ‰ï¼Œæ‰¾ä¸åˆ°é€™å€‹ä½¿ç”¨è€…." +msgstr "抱æ‰ï¼Œæ‰¾ä¸åˆ°é€™å€‹ä½¿ç”¨è€…。" #: mediagoblin/templates/mediagoblin/user_pages/user.html:50 #: mediagoblin/templates/mediagoblin/user_pages/user.html:70 @@ -807,16 +926,16 @@ msgstr "需è¦èªè‰é›»å郵件" #: mediagoblin/templates/mediagoblin/user_pages/user.html:53 msgid "Almost done! Your account still needs to be activated." -msgstr "幾乎完æˆäº†ï¼ä½†ä½ 的帳號ä»ç„¶éœ€è¦è¢«å•Ÿç”¨ã€‚" +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 "馬上會有一å°é›»åéƒµä»¶å‘Šè¨´ä½ å¦‚ä½•åš." +msgstr "啟用æ¥é©Ÿçš„ email 將會寄到您的信箱。" #: mediagoblin/templates/mediagoblin/user_pages/user.html:62 msgid "In case it doesn't:" -msgstr "如果ä»ç„¶ç„¡æ³•èªè‰ï¼Œä½ å¯ä»¥:" +msgstr "如果ä»ç„¶ç„¡æ³•èªè‰ï¼Œæ‚¨å¯ä»¥ï¼š" #: mediagoblin/templates/mediagoblin/user_pages/user.html:65 msgid "Resend verification email" @@ -826,98 +945,82 @@ msgstr "é‡é€èªè‰ä¿¡" msgid "" "Someone has registered an account with this username, but it still has to be" " activated." -msgstr "有人用了這個帳號登錄了,但是這個帳號ä»éœ€è¦è¢«å•Ÿç”¨ã€‚" +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> 然後é‡é€ä¸€æ¬¡." +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 "é€™å€‹åœ°æ–¹èƒ½è®“ä½ å‘他人介紹自己。" +msgstr "這個地方能讓您å‘他人介紹自己。" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:101 -#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +#: 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:106 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 msgid "This user hasn't filled in their profile (yet)." -msgstr "這個使用者還沒(來得åŠ)填寫個人檔案。" +msgstr "這個使用者(é‚„)沒有填寫個人檔案。" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:125 -msgid "Change account settings" -msgstr "更改帳號è¨å®š" +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 #, python-format msgid "View all of %(username)s's media" -msgstr "查看%(username)s的全部媒體檔案" +msgstr "查看 %(username)s 的全部媒體" -#: mediagoblin/templates/mediagoblin/user_pages/user.html:151 +#: 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 "é€™å€‹åœ°æ–¹æ˜¯ä½ çš„åª’é«”æª”æ¡ˆæœƒå‡ºç¾çš„åœ°æ–¹ï¼Œä½†æ˜¯ä½ ä¼¼ä¹Žé‚„æ²’æœ‰åŠ å…¥ä»»ä½•æ±è¥¿ã€‚" +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/collection_gallery.html:87 -#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +#: 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 "似乎還沒有任何的媒體檔案..." +msgstr "那裡好åƒé‚„沒有任何的媒體…" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:39 -#, python-format -msgid "" -"<br />\n" -" <a href=\"%(entry_url)s\">%(note)s</a>" -msgstr "" +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr " (移除)" -#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:47 -#, python-format -msgid "<br /><a href=\"%(remove_url)s\" class=\"remove\">(remove)</a>" +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" msgstr "" -#: mediagoblin/templates/mediagoblin/utils/collections.html:20 -#, python-format -msgid "In collections (%(collected)s)" +#: 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圖示" +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/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 "在 <a href=\"%(osm_url)s\">OpenStreetMap</a>上觀看" - #: mediagoblin/templates/mediagoblin/utils/license.html:25 msgid "All rights reserved" msgstr "版權所有" #: mediagoblin/templates/mediagoblin/utils/pagination.html:39 msgid "↠Newer" -msgstr "↠更新" +msgstr "↠更新的" #: mediagoblin/templates/mediagoblin/utils/pagination.html:45 msgid "Older →" -msgstr "更舊 →" +msgstr "更舊的 →" #: mediagoblin/templates/mediagoblin/utils/pagination.html:48 msgid "Go to page:" @@ -926,109 +1029,145 @@ msgstr "è·³åˆ°é æ•¸ï¼š" #: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 #: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 msgid "newer" -msgstr "æ›´æ–°" +msgstr "æ›´æ–°çš„" #: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 #: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 msgid "older" -msgstr "更舊" +msgstr "更舊的" #: mediagoblin/templates/mediagoblin/utils/tags.html:20 msgid "Tagged with" -msgstr "標籤為" +msgstr "標籤" -#: mediagoblin/tools/exif.py:78 +#: mediagoblin/tools/exif.py:80 msgid "Could not read the image file." -msgstr "無法讀å–å½±åƒæª”案。" +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/user_pages/forms.py:28 +#: 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/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 "我確定我想è¦åˆªé™¤" +msgstr "我確定我è¦åˆªé™¤é€™å€‹åª’é«”" -#: mediagoblin/user_pages/forms.py:32 +#: 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:35 +#: mediagoblin/user_pages/forms.py:40 msgid "-- Select --" -msgstr "" +msgstr "— è«‹é¸æ“‡ —" -#: mediagoblin/user_pages/forms.py:37 +#: mediagoblin/user_pages/forms.py:42 msgid "Include a note" -msgstr "" +msgstr "åŠ è¨»" #: mediagoblin/user_pages/lib.py:56 msgid "commented on your post" -msgstr "" +msgstr "在您的內容張貼評論" -#: mediagoblin/user_pages/views.py:161 +#: mediagoblin/user_pages/views.py:166 msgid "Oops, your comment was empty." -msgstr "å•Šï¼Œä½ çš„ç•™è¨€æ˜¯ç©ºçš„ã€‚" +msgstr "啊,您的留言是空的。" -#: mediagoblin/user_pages/views.py:167 +#: mediagoblin/user_pages/views.py:172 msgid "Your comment has been posted!" -msgstr "ä½ çš„ç•™è¨€å·²ç¶“åˆŠç™»ï¼" +msgstr "您的留言已經張貼完æˆï¼" + +#: mediagoblin/user_pages/views.py:197 +msgid "Please check your entries and try again." +msgstr "è«‹æª¢æŸ¥é …ç›®ä¸¦é‡è©¦ã€‚" -#: mediagoblin/user_pages/views.py:235 +#: mediagoblin/user_pages/views.py:237 msgid "You have to select or add a collection" -msgstr "" +msgstr "您需è¦é¸æ“‡æˆ–是新增一個è’è—" -#: mediagoblin/user_pages/views.py:243 +#: mediagoblin/user_pages/views.py:248 #, python-format msgid "\"%s\" already in collection \"%s\"" -msgstr "" +msgstr "「%sã€å·²ç¶“在「%sã€è’è—" -#: mediagoblin/user_pages/views.py:258 +#: mediagoblin/user_pages/views.py:264 #, python-format msgid "\"%s\" added to collection \"%s\"" -msgstr "" - -#: mediagoblin/user_pages/views.py:266 -msgid "Please check your entries and try again." -msgstr "" - -#: mediagoblin/user_pages/views.py:297 -msgid "" -"Some of the files with this entry seem to be missing. Deleting anyway." -msgstr "" +msgstr "「%sã€åŠ å…¥ã€Œ%sã€è’è—" -#: mediagoblin/user_pages/views.py:302 +#: mediagoblin/user_pages/views.py:286 msgid "You deleted the media." -msgstr "ä½ å·²åˆªé™¤æ¤åª’體檔案。" +msgstr "您已經刪除æ¤åª’體。" -#: mediagoblin/user_pages/views.py:309 +#: mediagoblin/user_pages/views.py:293 msgid "The media was not deleted because you didn't check that you were sure." -msgstr "æ¤åª’é«”æª”æ¡ˆå°šæœªè¢«åˆªé™¤å› ç‚ºä½ é‚„æ²’æœ‰ç¢ºèªä½ 真的è¦åˆªé™¤ã€‚" +msgstr "由於您沒有勾é¸ç¢ºèªï¼Œè©²åª’體沒有被移除。" -#: mediagoblin/user_pages/views.py:317 +#: mediagoblin/user_pages/views.py:301 msgid "You are about to delete another user's media. Proceed with caution." -msgstr "ä½ åœ¨åˆªé™¤å…¶ä»–äººçš„åª’é«”æª”æ¡ˆã€‚è«‹å°å¿ƒè™•ç†å–”。" +msgstr "您æ£åœ¨åˆªé™¤åˆ¥äººçš„媒體,請å°å¿ƒæ“作。" -#: mediagoblin/user_pages/views.py:379 +#: mediagoblin/user_pages/views.py:375 msgid "You deleted the item from the collection." -msgstr "" +msgstr "您已經從該è’è—ä¸åˆªé™¤è©²é …目。" -#: mediagoblin/user_pages/views.py:383 +#: mediagoblin/user_pages/views.py:379 msgid "The item was not removed because you didn't check that you were sure." -msgstr "" +msgstr "由於您沒有勾é¸ç¢ºèªï¼Œè©²é …目沒有被移除。" -#: mediagoblin/user_pages/views.py:393 +#: mediagoblin/user_pages/views.py:389 msgid "" "You are about to delete an item from another user's collection. Proceed with" " caution." -msgstr "" +msgstr "您æ£åœ¨å¾žåˆ¥äººçš„è’è—ä¸åˆªé™¤é …目,請å°å¿ƒæ“作。" -#: mediagoblin/user_pages/views.py:426 +#: mediagoblin/user_pages/views.py:422 #, python-format msgid "You deleted the collection \"%s\"" -msgstr "" +msgstr "您已經刪除「%sã€è’è—。" -#: mediagoblin/user_pages/views.py:433 +#: mediagoblin/user_pages/views.py:429 msgid "" "The collection was not deleted because you didn't check that you were sure." -msgstr "" +msgstr "由於您沒有勾é¸ç¢ºèªï¼Œè©²è’è—æ²’有被移除。" -#: mediagoblin/user_pages/views.py:443 +#: mediagoblin/user_pages/views.py:439 msgid "" "You are about to delete another user's collection. Proceed with caution." -msgstr "" +msgstr "您æ£åœ¨åˆªé™¤åˆ¥äººçš„è’è—,請å°å¿ƒæ“作。" diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py index 9b0025c9..d16027db 100644 --- a/mediagoblin/init/__init__.py +++ b/mediagoblin/init/__init__.py @@ -14,18 +14,17 @@ # You 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 beaker.cache import CacheManager -from beaker.util import parse_cache_config_options 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.workbench import WorkbenchManager +from mediagoblin.tools.workbench import WorkbenchManager from mediagoblin.storage import storage_system_from_config @@ -37,6 +36,11 @@ 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'] @@ -60,15 +64,13 @@ def setup_database(): load_models(app_config) # Set up the database - connection, db = setup_connection_and_db_from_config(app_config) + db = setup_connection_and_db_from_config(app_config) check_db_migrations_current(db) - setup_globals( - db_connection=connection, - database=db) + setup_globals(database=db) - return connection, db + return db def get_jinja_loader(user_template_path=None, current_theme=None, @@ -142,16 +144,3 @@ def setup_workbench(): workbench_manager = WorkbenchManager(app_config['workbench_path']) setup_globals(workbench_manager=workbench_manager) - - -def setup_beaker_cache(): - """ - Setup the Beaker Cache manager. - """ - cache_config = mg_globals.global_config['beaker.cache'] - cache_config = dict( - [(u'cache.%s' % key, value) - for key, value in cache_config.iteritems()]) - cache = CacheManager(**parse_cache_config_options(cache_config)) - setup_globals(cache=cache) - return cache diff --git a/mediagoblin/init/celery/__init__.py b/mediagoblin/init/celery/__init__.py index fc595ea7..169cc935 100644 --- a/mediagoblin/init/celery/__init__.py +++ b/mediagoblin/init/celery/__init__.py @@ -18,6 +18,7 @@ import os import sys from celery import Celery +from mediagoblin.tools.pluginapi import hook_runall MANDATORY_CELERY_IMPORTS = ['mediagoblin.processing.task'] @@ -65,6 +66,8 @@ def setup_celery_app(app_config, global_config, 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, diff --git a/mediagoblin/init/celery/from_celery.py b/mediagoblin/init/celery/from_celery.py index 41fffa45..b395a826 100644 --- a/mediagoblin/init/celery/from_celery.py +++ b/mediagoblin/init/celery/from_celery.py @@ -16,13 +16,13 @@ import os import logging +import logging.config -from configobj import ConfigObj -from ConfigParser import RawConfigParser 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__ @@ -36,49 +36,18 @@ def setup_logging_from_paste_ini(loglevel, **kw): 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_conf = ConfigObj(logging_conf_file) - - config = logging_conf - - # Read raw config to avoid interpolation of formatting parameters - raw_config = RawConfigParser() - raw_config.readfp(open(logging_conf_file)) - - # Set up formatting - # Get the format string and circumvent configobj interpolation of the value - fmt = raw_config.get('formatter_generic', 'format') - - # Create the formatter - formatter = logging.Formatter(fmt) - - # Check for config values - if not config.get('loggers') or not config['loggers'].get('keys'): - print('No loggers found') - return - - # Iterate all teh loggers.keys values - for name in config['loggers']['keys'].split(','): - if not config.get('logger_{0}'.format(name)): - continue - - log_params = config['logger_{0}'.format(name)] - - qualname = log_params['qualname'] if 'qualname' in log_params else name - - if qualname == 'root': - qualname = None - - logger = logging.getLogger(qualname) - - level = getattr(logging, log_params['level']) - logger.setLevel(level) + logging.config.fileConfig(logging_conf_file) - for handler in logger.handlers: - handler.setFormatter(formatter) + hook_runall('celery_logging_setup') setup_logging.connect(setup_logging_from_paste_ini) diff --git a/mediagoblin/init/config.py b/mediagoblin/init/config.py index ac4ab9bf..11a91cff 100644 --- a/mediagoblin/init/config.py +++ b/mediagoblin/init/config.py @@ -14,6 +14,7 @@ # You 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 @@ -21,6 +22,9 @@ 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') @@ -42,6 +46,9 @@ def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): 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 @@ -57,10 +64,45 @@ def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): """ 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( diff --git a/mediagoblin/init/plugins/__init__.py b/mediagoblin/init/plugins/__init__.py index cdf9b5ad..0df4f381 100644 --- a/mediagoblin/init/plugins/__init__.py +++ b/mediagoblin/init/plugins/__init__.py @@ -59,6 +59,4 @@ def setup_plugins(): pman.register_hooks(plugin.hooks) # Execute anything registered to the setup hook. - setup_list = pman.get_hook_callables('setup') - for fun in setup_list: - fun() + pluginapi.hook_runall('setup') diff --git a/mediagoblin/listings/routing.py b/mediagoblin/listings/routing.py index d25f1c8c..ee8f5020 100644 --- a/mediagoblin/listings/routing.py +++ b/mediagoblin/listings/routing.py @@ -14,10 +14,16 @@ # You 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.routing import add_route +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:tag_atom_feed") + "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 index a8824390..35af7148 100644 --- a/mediagoblin/listings/views.py +++ b/mediagoblin/listings/views.py @@ -14,8 +14,8 @@ # You 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.util import media_entries_for_tag_slug, DESCENDING - +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 @@ -36,10 +36,6 @@ def _get_tag_name_from_entries(media_entries, tag_slug): tag_name = tag['name'] break break - # TODO: Remove after SQL-switch, it's mongo specific - if hasattr(media_entries, "rewind"): - media_entries.rewind() - return tag_name @@ -49,7 +45,7 @@ def tag_listing(request, page): tag_slug = request.matchdict[u'tag'] cursor = media_entries_for_tag_slug(request.db, tag_slug) - cursor = cursor.sort('created', DESCENDING) + cursor = cursor.order_by(MediaEntry.created.desc()) pagination = Pagination(page, cursor) media_entries = pagination() @@ -68,26 +64,30 @@ def tag_listing(request, page): ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15 -def tag_atom_feed(request): +def atom_feed(request): """ generates the atom feed with the tag images """ - tag_slug = request.matchdict[u'tag'] - - cursor = media_entries_for_tag_slug(request.db, tag_slug) - cursor = cursor.sort('created', DESCENDING) + 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) - """ - ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI) - """ feed = AtomFeed( - "MediaGoblin: Feed for tag '%s'" % tag_slug, + feed_title, feed_url=request.url, - id='tag:'+request.host+',2011:gallery.tag-%s' % tag_slug, - links=[{'href': request.urlgen( - 'mediagoblin.listings.tags_listing', - qualified=True, tag=tag_slug ), + id=link, + links=[{'href': link, 'rel': 'alternate', 'type': 'text/html'}]) for entry in cursor: diff --git a/mediagoblin/meddleware/__init__.py b/mediagoblin/meddleware/__init__.py index a9c712d7..886c9ad9 100644 --- a/mediagoblin/meddleware/__init__.py +++ b/mediagoblin/meddleware/__init__.py @@ -14,10 +14,9 @@ # You 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.noop:NoOpMeddleware', +ENABLED_MEDDLEWARE = [ 'mediagoblin.meddleware.csrf:CsrfMeddleware', - ) + ] class BaseMeddleware(object): diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py index 1488e6d9..661f0ba2 100644 --- a/mediagoblin/meddleware/csrf.py +++ b/mediagoblin/meddleware/csrf.py @@ -17,11 +17,12 @@ import random import logging -from webob.exc import HTTPForbidden +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__) @@ -127,9 +128,13 @@ class CsrfMeddleware(BaseMeddleware): None) if cookie_token is None: - # the CSRF cookie must be present in the request + # 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') - return HTTPForbidden() + 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) @@ -142,5 +147,6 @@ class CsrfMeddleware(BaseMeddleware): # either the tokens didn't match or the form token wasn't # present; either way, the request is denied - _log.error('CSRF validation failed') - return HTTPForbidden() + errstr = 'CSRF validation failed' + _log.error(errstr) + raise Forbidden(errstr) diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py index 5bf0124c..20e1918e 100644 --- a/mediagoblin/media_types/__init__.py +++ b/mediagoblin/media_types/__init__.py @@ -20,6 +20,7 @@ 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__) @@ -31,6 +32,56 @@ 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 @@ -49,7 +100,7 @@ def sniff_media(media): for media_type, manager in get_media_managers(): _log.info('Sniffing {0}'.format(media_type)) - if manager['sniff_handler'](media_file, media=media): + if manager.sniff_handler(media_file, media=media): _log.info('{0} accepts the file'.format(media_type)) return media_type, manager else: @@ -73,28 +124,12 @@ def get_media_managers(): Generator, yields all enabled media managers ''' for media_type in get_media_types(): - __import__(media_type) - - yield media_type, sys.modules[media_type].MEDIA_MANAGER - + mm = import_component(media_type + ":MEDIA_MANAGER") -def get_media_manager(_media_type): - ''' - Get the MEDIA_MANAGER based on a media type string - - Example:: - get_media_type('mediagoblin.media_types.image') - ''' - if not _media_type: - return False - - for media_type, manager in get_media_managers(): - if media_type in _media_type: - return manager + if isinstance(mm, dict): + mm = CompatMediaManager(mm) - # Nope? Then raise an error - raise FileTypeNotSupported( - "MediaManager not in enabled types. Check media_types in config?") + yield media_type, mm def get_media_type_and_manager(filename): @@ -110,7 +145,7 @@ def get_media_type_and_manager(filename): 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']: + 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( diff --git a/mediagoblin/media_types/ascii/__init__.py b/mediagoblin/media_types/ascii/__init__.py index 856d1d7b..0931e83a 100644 --- a/mediagoblin/media_types/ascii/__init__.py +++ b/mediagoblin/media_types/ascii/__init__.py @@ -14,16 +14,18 @@ # You 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 -MEDIA_MANAGER = { - "human_readable": "ASCII", - "processor": process_ascii, # alternately a string, - # 'mediagoblin.media_types.image.processing'? - "sniff_handler": sniff_handler, - "display_template": "mediagoblin/media_displays/ascii.html", - "default_thumb": "images/media_thumbs/ascii.jpg", - "accepted_extensions": [ - "txt", "asc", "nfo"]} +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 index 108de023..786941f6 100644 --- a/mediagoblin/media_types/ascii/asciitoimage.py +++ b/mediagoblin/media_types/ascii/asciitoimage.py @@ -14,9 +14,14 @@ # You 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 Image -import ImageFont -import ImageDraw +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 diff --git a/mediagoblin/media_types/ascii/models.py b/mediagoblin/media_types/ascii/models.py index 865c216c..c7505292 100644 --- a/mediagoblin/media_types/ascii/models.py +++ b/mediagoblin/media_types/ascii/models.py @@ -15,13 +15,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from mediagoblin.db.sql.base import Base +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" @@ -29,7 +32,8 @@ class AsciiData(Base): 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")) + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) DATA_MODEL = AsciiData diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py index 04d1166c..2f6079be 100644 --- a/mediagoblin/media_types/ascii/processing.py +++ b/mediagoblin/media_types/ascii/processing.py @@ -15,7 +15,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import chardet import os -import Image +try: + from PIL import Image +except ImportError: + import Image import logging from mediagoblin import mg_globals as mgg @@ -38,12 +41,15 @@ def sniff_handler(media_file, **kw): return False -def process_ascii(entry): - ''' - Code to process a txt file - ''' +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'] - workbench = mgg.workbench_manager.create_workbench() # Conversions subdirectory to avoid collisions conversions_subdir = os.path.join( workbench.dir, 'conversions') @@ -124,8 +130,14 @@ def process_ascii(entry): 'ascii', 'xmlcharrefreplace')) - mgg.queue_store.delete_file(queued_filepath) + # 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 diff --git a/mediagoblin/media_types/audio/__init__.py b/mediagoblin/media_types/audio/__init__.py index 4f3ead60..2eb7300e 100644 --- a/mediagoblin/media_types/audio/__init__.py +++ b/mediagoblin/media_types/audio/__init__.py @@ -14,12 +14,17 @@ # You 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 -MEDIA_MANAGER = { - 'human_readable': 'Audio', - 'processor': process_audio, - 'sniff_handler': sniff_handler, - 'display_template': 'mediagoblin/media_displays/audio.html', - 'accepted_extensions': ['mp3', 'flac', 'wav', 'm4a']} + +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/models.py b/mediagoblin/media_types/audio/models.py index 5f18d2c2..d01367d5 100644 --- a/mediagoblin/media_types/audio/models.py +++ b/mediagoblin/media_types/audio/models.py @@ -15,13 +15,16 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from mediagoblin.db.sql.base import Base +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" @@ -29,7 +32,8 @@ class AudioData(Base): 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")) + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) DATA_MODEL = AudioData diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py index fada083f..101b83e5 100644 --- a/mediagoblin/media_types/audio/processing.py +++ b/mediagoblin/media_types/audio/processing.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging -import tempfile +from tempfile import NamedTemporaryFile import os from mediagoblin import mg_globals as mgg @@ -42,10 +42,15 @@ def sniff_handler(media_file, **kw): return False -def process_audio(entry): - audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio'] +def process_audio(proc_state): + """Code to process uploaded audio. Will be run by celery. - workbench = mgg.workbench_manager.create_workbench() + 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( @@ -73,7 +78,7 @@ def process_audio(entry): transcoder = AudioTranscoder() - with tempfile.NamedTemporaryFile() as webm_audio_tmp: + with NamedTemporaryFile(dir=workbench.dir) as webm_audio_tmp: progress_callback = ProgressCallback(entry) transcoder.transcode( @@ -99,7 +104,7 @@ def process_audio(entry): original=os.path.splitext( queued_filepath[-1])[0])) - with tempfile.NamedTemporaryFile(suffix='.ogg') as wav_tmp: + with NamedTemporaryFile(dir=workbench.dir, suffix='.ogg') as wav_tmp: _log.info('Creating OGG source for spectrogram') transcoder.transcode( queued_filename, @@ -109,7 +114,7 @@ def process_audio(entry): thumbnailer = AudioThumbnailer() - with tempfile.NamedTemporaryFile(suffix='.jpg') as spectrogram_tmp: + with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as spectrogram_tmp: thumbnailer.spectrogram( wav_tmp.name, spectrogram_tmp.name, @@ -122,7 +127,7 @@ def process_audio(entry): entry.media_files['spectrogram'] = spectrogram_filepath - with tempfile.NamedTemporaryFile(suffix='.jpg') as thumb_tmp: + with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as thumb_tmp: thumbnailer.thumbnail_spectrogram( spectrogram_tmp.name, thumb_tmp.name, @@ -142,9 +147,10 @@ def process_audio(entry): else: entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg'] - mgg.queue_store.delete_file(queued_filepath) - - entry.save() - - # clean up workbench - workbench.destroy_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. + 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 index 458855c1..dd4d0299 100644 --- a/mediagoblin/media_types/audio/spectrogram.py +++ b/mediagoblin/media_types/audio/spectrogram.py @@ -19,7 +19,10 @@ # Bram de Jong <bram.dejong at domain.com where domain in gmail> # 2012, Joar Wandborg <first name at last name dot se> -from PIL import Image +try: + from PIL import Image +except ImportError: + import Image import math import numpy diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py index f3d49c30..84e6af7e 100644 --- a/mediagoblin/media_types/audio/transcoders.py +++ b/mediagoblin/media_types/audio/transcoders.py @@ -14,9 +14,11 @@ # You 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 pdb import logging -import Image +try: + from PIL import Image +except ImportError: + import Image from mediagoblin.processing import BadMediaFail from mediagoblin.media_types.audio import audioprocessing @@ -233,5 +235,3 @@ if __name__ == '__main__': thumbnailer = AudioThumbnailer() thumbnailer.spectrogram(*sys.argv[1:], width=640) - - pdb.set_trace() diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py index d4720fab..5130ef48 100644 --- a/mediagoblin/media_types/image/__init__.py +++ b/mediagoblin/media_types/image/__init__.py @@ -14,15 +14,42 @@ # You 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 -MEDIA_MANAGER = { - "human_readable": "Image", - "processor": process_image, # alternately a string, - # 'mediagoblin.media_types.image.processing'? - "sniff_handler": sniff_handler, - "display_template": "mediagoblin/media_displays/image.html", - "default_thumb": "images/media_thumbs/image.jpg", - "accepted_extensions": ["jpg", "jpeg", "png", "gif", "tiff"]} +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/models.py b/mediagoblin/media_types/image/models.py index fc518daa..b2ea3960 100644 --- a/mediagoblin/media_types/image/models.py +++ b/mediagoblin/media_types/image/models.py @@ -15,12 +15,15 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from mediagoblin.db.sql.base import Base +from mediagoblin.db.base import Base from sqlalchemy import ( Column, Integer, Float, ForeignKey) from sqlalchemy.orm import relationship, backref -from mediagoblin.db.sql.extratypes import JSONEncoded +from mediagoblin.db.extratypes import JSONEncoded + + +BACKREF_NAME = "image__media_data" class ImageData(Base): @@ -30,7 +33,8 @@ class ImageData(Base): 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")) + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) width = Column(Integer) height = Column(Integer) diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py index bdb2290f..bc0ce3f8 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -14,45 +14,85 @@ # You 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 Image +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, \ - create_pub_filepath, FilenameBuilder +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(entry, filename, new_path, exif_tags, workdir, new_size, - size_limits=(0, 0)): + +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: - entry -- the entry for the image to resize - filename -- the filename of the original image being resized - new_path -- public file path for the new resized image + 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 """ - try: - resized = Image.open(filename) - except IOError: - raise BadMediaFail() + config = mgg.global_config['media_type:mediagoblin.media_types.image'] + resized = exif_fix_image_orientation(resized, exif_tags) # Fix orientation - resized.thumbnail(new_size, Image.ANTIALIAS) + + 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, new_path[-1]) + tmp_resized_filename = os.path.join(workdir, target_name) with file(tmp_resized_filename, 'w') as resized_file: - resized.save(resized_file) - mgg.public_store.copy_local_to_storage(tmp_resized_filename, new_path) + 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'] @@ -76,19 +116,21 @@ def sniff_handler(media_file, **kw): return False -def process_image(entry): - """ - Code to process an image +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. """ - workbench = mgg.workbench_manager.create_workbench() + 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_filepath = entry.queued_media_file - queued_filename = workbench.localized_file( - mgg.queue_store, queued_filepath, - 'source') + + queued_filename = proc_state.get_queued_filename() name_builder = FilenameBuilder(queued_filename) # EXIF extraction @@ -96,52 +138,20 @@ def process_image(entry): gps_data = get_gps_data(exif_tags) # Always create a small thumbnail - thumb_filepath = create_pub_filepath( - entry, name_builder.fill('{basename}.thumbnail{ext}')) - resize_image(entry, queued_filename, thumb_filepath, - exif_tags, conversions_subdir, - (mgg.global_config['media:thumb']['max_width'], - mgg.global_config['media:thumb']['max_height'])) - - # If the size of the original file exceeds the specified size of a `medium` - # file, a `.medium.jpg` files is created and later associated with the media - # entry. - medium = Image.open(queued_filename) - if medium.size[0] > mgg.global_config['media:medium']['max_width'] \ - or medium.size[1] > mgg.global_config['media:medium']['max_height'] \ - or exif_image_needs_rotation(exif_tags): - medium_filepath = create_pub_filepath( - entry, name_builder.fill('{basename}.medium{ext}')) - resize_image( - entry, queued_filename, medium_filepath, - exif_tags, conversions_subdir, - (mgg.global_config['media:medium']['max_width'], - mgg.global_config['media:medium']['max_height'])) - else: - medium_filepath = None - - # we have to re-read because unlike PIL, not everything reads - # things in string representation :) - queued_file = file(queued_filename, 'rb') + resize_tool(proc_state, True, 'thumb', + name_builder.fill('{basename}.thumbnail{ext}'), + conversions_subdir, exif_tags) - with queued_file: - original_filepath = create_pub_filepath( - entry, name_builder.fill('{basename}{ext}')) + # Possibly create a medium + resize_tool(proc_state, False, 'medium', + name_builder.fill('{basename}.medium{ext}'), + conversions_subdir, exif_tags) - with mgg.public_store.get_file(original_filepath, 'wb') \ - as original_file: - original_file.write(queued_file.read()) + # 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 - mgg.queue_store.delete_file(queued_filepath) - entry.queued_media_file = [] - - # Insert media file information into database - media_files_dict = entry.setdefault('media_files', {}) - media_files_dict[u'thumb'] = thumb_filepath - media_files_dict[u'original'] = original_filepath - if medium_filepath: - media_files_dict[u'medium'] = medium_filepath + proc_state.delete_queue_file() # Insert exif data into database exif_all = clean_exif(exif_tags) @@ -154,8 +164,6 @@ def process_image(entry): gps_data['gps_' + key] = gps_data.pop(key) entry.media_data_init(**gps_data) - # clean up workbench - workbench.destroy_self() if __name__ == '__main__': import sys 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 Binary files differnew file mode 100644 index 00000000..dd356a06 --- /dev/null +++ b/mediagoblin/media_types/stl/assets/blender_render.blend 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 index 3faa5b9f..569cf11a 100644 --- a/mediagoblin/media_types/video/__init__.py +++ b/mediagoblin/media_types/video/__init__.py @@ -14,16 +14,23 @@ # You 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 -MEDIA_MANAGER = { - "human_readable": "Video", - "processor": process_video, # alternately a string, - # 'mediagoblin.media_types.image.processing'? - "sniff_handler": 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"]} +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/migrations.py b/mediagoblin/media_types/video/migrations.py index f54c23ea..442bbd8d 100644 --- a/mediagoblin/media_types/video/migrations.py +++ b/mediagoblin/media_types/video/migrations.py @@ -14,4 +14,19 @@ # You 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 index 35ed92bf..0b52c53f 100644 --- a/mediagoblin/media_types/video/models.py +++ b/mediagoblin/media_types/video/models.py @@ -15,25 +15,83 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -from mediagoblin.db.sql.base import Base +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("video__media_data", cascade="all, delete-orphan")) + 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 index ce47313f..ff2c94a0 100644 --- a/mediagoblin/media_types/video/processing.py +++ b/mediagoblin/media_types/video/processing.py @@ -14,8 +14,9 @@ # You 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 +from tempfile import NamedTemporaryFile import logging +import datetime from mediagoblin import mg_globals as mgg from mediagoblin.processing import \ @@ -23,6 +24,7 @@ from mediagoblin.processing import \ 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) @@ -52,19 +54,20 @@ def sniff_handler(media_file, **kw): return False -def process_video(entry): +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'] - workbench = mgg.workbench_manager.create_workbench() - queued_filepath = entry.queued_media_file - queued_filename = workbench.localized_file( - mgg.queue_store, queued_filepath, - 'source') + queued_filename = proc_state.get_queued_filename() name_builder = FilenameBuilder(queued_filename) medium_filepath = create_pub_filepath( @@ -73,34 +76,61 @@ def process_video(entry): thumbnail_filepath = create_pub_filepath( entry, name_builder.fill('{basename}.thumbnail.jpg')) - # Create a temporary file for the video destination - tmp_dst = tempfile.NamedTemporaryFile() - + # 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) - 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) - # Push transcoded video to public storage - _log.debug('Saving medium...') - mgg.public_store.get_file(medium_filepath, 'wb').write( - tmp_dst.read()) - _log.debug('Saved medium') + 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) - entry.media_files['webm_640'] = medium_filepath + 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=transcoder.dst_data.videowidth, - height=transcoder.dst_data.videoheight) + width=dst_dimensions[0], + height=dst_dimensions[1]) - # Create a temporary file for the video thumbnail - tmp_thumb = tempfile.NamedTemporaryFile(suffix='.jpg') + # 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 @@ -111,30 +141,72 @@ def process_video(entry): # Push the thumbnail to public storage _log.debug('Saving thumbnail...') - mgg.public_store.get_file(thumbnail_filepath, 'wb').write( - tmp_thumb.read()) - _log.debug('Saved thumbnail') - + mgg.public_store.copy_local_to_storage(tmp_thumb.name, thumbnail_filepath) entry.media_files['thumb'] = thumbnail_filepath - if video_config['keep_original']: + # 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 - queued_file = file(queued_filename, 'rb') + _log.debug('Saving original...') + proc_state.copy_original(queued_filepath[-1]) - with queued_file: - original_filepath = create_pub_filepath( - entry, - queued_filepath[-1]) + # Remove queued media file from storage and database + proc_state.delete_queue_file() - with mgg.public_store.get_file(original_filepath, 'wb') as \ - original_file: - _log.debug('Saving original...') - original_file.write(queued_file.read()) - _log.debug('Saved original') - entry.media_files['original'] = original_filepath - - mgg.queue_store.delete_file(queued_filepath) - - # Save the MediaEntry - entry.save() +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 index 26f96b5f..90a767dd 100644 --- a/mediagoblin/media_types/video/transcoders.py +++ b/mediagoblin/media_types/video/transcoders.py @@ -26,7 +26,10 @@ import pygst pygst.require('0.10') import gst import struct -import Image +try: + from PIL import Image +except ImportError: + import Image from gst.extend import discoverer @@ -53,283 +56,6 @@ def pixbuf_to_pilbuf(buf): return data -class VideoThumbnailer: - # Declaration of thumbnailer states - STATE_NULL = 0 - STATE_HALTING = 1 - STATE_PROCESSING = 2 - - # The current thumbnailer state - - def __init__(self, source_path, dest_path): - ''' - Set up playbin pipeline in order to get video properties. - - Initializes and runs the gobject.MainLoop() - - Abstract - - Set up a playbin with a fake audio sink and video sink. Load the video - into the playbin - - Initialize - ''' - # This will contain the thumbnailing pipeline - self.state = self.STATE_NULL - self.thumbnail_pipeline = None - self.buffer_probes = {} - self.errors = [] - - self.source_path = source_path - self.dest_path = dest_path - - self.loop = gobject.MainLoop() - - # Set up the playbin. It will be used to discover certain - # properties of the input file - self.playbin = gst.element_factory_make('playbin') - - self.videosink = gst.element_factory_make('fakesink', 'videosink') - self.playbin.set_property('video-sink', self.videosink) - - self.audiosink = gst.element_factory_make('fakesink', 'audiosink') - self.playbin.set_property('audio-sink', self.audiosink) - - self.bus = self.playbin.get_bus() - self.bus.add_signal_watch() - self.watch_id = self.bus.connect('message', self._on_bus_message) - - self.playbin.set_property('uri', 'file:{0}'.format( - urllib.pathname2url(self.source_path))) - - self.playbin.set_state(gst.STATE_PAUSED) - - self.run() - - def run(self): - self.loop.run() - - def _on_bus_message(self, bus, message): - _log.debug(' thumbnail playbin: {0}'.format(message)) - - if message.type == gst.MESSAGE_ERROR: - _log.error('thumbnail playbin: {0}'.format(message)) - gobject.idle_add(self._on_bus_error) - - elif message.type == gst.MESSAGE_STATE_CHANGED: - # The pipeline state has changed - # Parse state changing data - _prev, state, _pending = message.parse_state_changed() - - _log.debug('State changed: {0}'.format(state)) - - if state == gst.STATE_PAUSED: - if message.src == self.playbin: - gobject.idle_add(self._on_bus_paused) - - def _on_bus_paused(self): - ''' - Set up thumbnailing pipeline - ''' - current_video = self.playbin.get_property('current-video') - - if current_video == 0: - _log.debug('Found current video from playbin') - else: - _log.error('Could not get any current video from playbin!') - - self.duration = self._get_duration(self.playbin) - _log.info('Video length: {0}'.format(self.duration / gst.SECOND)) - - _log.info('Setting up thumbnailing pipeline') - self.thumbnail_pipeline = gst.parse_launch( - 'filesrc location="{0}" ! decodebin ! ' - 'ffmpegcolorspace ! videoscale ! ' - 'video/x-raw-rgb,depth=24,bpp=24,pixel-aspect-ratio=1/1,width=180 ! ' - 'fakesink signal-handoffs=True'.format(self.source_path)) - - self.thumbnail_bus = self.thumbnail_pipeline.get_bus() - self.thumbnail_bus.add_signal_watch() - self.thumbnail_watch_id = self.thumbnail_bus.connect( - 'message', self._on_thumbnail_bus_message) - - self.thumbnail_pipeline.set_state(gst.STATE_PAUSED) - - #gobject.timeout_add(3000, self._on_timeout) - - return False - - def _on_thumbnail_bus_message(self, bus, message): - _log.debug('thumbnail: {0}'.format(message)) - - if message.type == gst.MESSAGE_ERROR: - _log.error(message) - gobject.idle_add(self._on_bus_error) - - if message.type == gst.MESSAGE_STATE_CHANGED: - _log.debug('State changed') - _prev, state, _pending = message.parse_state_changed() - - if (state == gst.STATE_PAUSED and - not self.state == self.STATE_PROCESSING and - message.src == self.thumbnail_pipeline): - _log.info('Pipeline paused, processing') - self.state = self.STATE_PROCESSING - - for sink in self.thumbnail_pipeline.sinks(): - name = sink.get_name() - factoryname = sink.get_factory().get_name() - - if factoryname == 'fakesink': - sinkpad = sink.get_pad('sink') - - self.buffer_probes[name] = sinkpad.add_buffer_probe( - self.buffer_probe_handler, name) - - _log.info('Added buffer probe') - - break - - # Apply the wadsworth constant, fallback to 1 second - # TODO: Will break if video is shorter than 1 sec - seek_amount = max(self.duration / 100 * 30, 1 * gst.SECOND) - - _log.debug('seek amount: {0}'.format(seek_amount)) - - 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: - self.errors.append('COULD_NOT_SEEK') - _log.error('Couldn\'t seek! result: {0}'.format( - seek_result)) - _log.info(message) - self.shutdown() - else: - _log.debug('Seek successful') - self.thumbnail_pipeline.set_state(gst.STATE_PAUSED) - else: - _log.debug('Won\'t seek: \t{0}\n\t{1}'.format( - self.state, - message.src)) - - def buffer_probe_handler_real(self, pad, buff, name): - ''' - Capture buffers as gdk_pixbufs when told to. - ''' - _log.info('Capturing frame') - try: - caps = buff.caps - if caps is None: - _log.error('No caps passed to buffer probe handler!') - self.shutdown() - return False - - _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 thumbnail') - - self.shutdown() - - except gst.QueryError as e: - _log.error('QueryError: {0}'.format(e)) - - return False - - def buffer_probe_handler(self, pad, buff, name): - ''' - Proxy function for buffer_probe_handler_real - ''' - _log.debug('Attaching real buffer handler to gobject idle event') - gobject.idle_add( - lambda: self.buffer_probe_handler_real(pad, buff, name)) - - return True - - def _get_duration(self, pipeline, retries=0): - ''' - Get the duration of a pipeline. - - Retries 5 times. - ''' - if retries == 5: - return 0 - - try: - return pipeline.query_duration(gst.FORMAT_TIME)[0] - except gst.QueryError: - return self._get_duration(pipeline, retries + 1) - - def _on_timeout(self): - _log.error('Timeout in thumbnailer!') - self.shutdown() - - def _on_bus_error(self, *args): - _log.error('AHAHAHA! Error! args: {0}'.format(args)) - - def shutdown(self): - ''' - Tell gobject to call __halt when the mainloop is idle. - ''' - _log.info('Shutting down') - self.__halt() - - def __halt(self): - ''' - Halt all pipelines and shut down the main loop - ''' - _log.info('Halting...') - self.state = self.STATE_HALTING - - self.__disconnect() - - gobject.idle_add(self.__halt_final) - - def __disconnect(self): - _log.debug('Disconnecting...') - if not self.playbin is None: - self.playbin.set_state(gst.STATE_NULL) - for sink in self.playbin.sinks(): - name = sink.get_name() - factoryname = sink.get_factory().get_name() - - _log.debug('Disconnecting {0}'.format(name)) - - if factoryname == "fakesink": - pad = sink.get_pad("sink") - pad.remove_buffer_probe(self.buffer_probes[name]) - del self.buffer_probes[name] - - self.playbin = None - - if self.bus is not None: - self.bus.disconnect(self.watch_id) - self.bus = None - - def __halt_final(self): - _log.info('Done') - if self.errors: - _log.error(','.join(self.errors)) - - self.loop.quit() - - class VideoThumbnailerMarkII(object): ''' Creates a thumbnail from a video file. Rewrite of VideoThumbnailer. @@ -398,8 +124,8 @@ class VideoThumbnailerMarkII(object): self.run() except Exception as exc: _log.critical( - 'Exception "{0}" caught, disconnecting and re-raising'\ - .format(exc)) + 'Exception "{0}" caught, shutting down mainloop and re-raising'\ + .format(exc)) self.disconnect() raise @@ -410,7 +136,8 @@ class VideoThumbnailerMarkII(object): self.mainloop.run() def on_playbin_message(self, message_bus, message): - _log.debug('playbin message: {0}'.format(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)) @@ -433,17 +160,20 @@ pending: {2}'.format( def on_playbin_paused(self): if self.has_reached_playbin_pause: - _log.warn('Has already reached logic for playbin pause. Aborting \ + _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('thumbnail could not get any video data \ + _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 @@ -474,11 +204,12 @@ from playbin') return False def on_thumbnail_message(self, message_bus, message): - _log.debug('thumbnail message: {0}'.format(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)) - gobject.idle_add(self.on_thumbnail_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 = \ @@ -490,29 +221,10 @@ pending: {2}'.format( cur_state, pending_state)) - if cur_state == gst.STATE_PAUSED and\ - not self.state == self.STATE_PROCESSING_THUMBNAIL: - self.state = self.STATE_PROCESSING_THUMBNAIL - + 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. - 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 - seek_amount = self.position_callback(self.duration, gst) seek_result = self.thumbnail_pipeline.seek( @@ -525,10 +237,30 @@ pending: {2}'.format( 0) if not seek_result: - _log.critical('Could not seek.') + _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.debug('Already processing thumbnail') + _log.info('Already processing thumbnail') def on_pad_buffer_probe(self, *args): _log.debug('buffer probe handler: {0}'.format(args)) @@ -570,10 +302,37 @@ pending: {2}'.format( return False - def on_thumbnail_error(self): - _log.error('Thumbnailing failed.') + 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 @@ -622,7 +381,7 @@ pending: {2}'.format( return self.get_duration(pipeline, attempt + 1) -class VideoTranscoder: +class VideoTranscoder(object): ''' Video transcoder @@ -636,7 +395,7 @@ class VideoTranscoder: ''' def __init__(self): _log.info('Initializing VideoTranscoder...') - + self.progress_percentage = None self.loop = gobject.MainLoop() def transcode(self, src, dst, **kwargs): @@ -673,6 +432,7 @@ class VideoTranscoder: self._setup() self._run() + # XXX: This could be a static method. def discover(self, src): ''' Discover properties about a media file @@ -793,7 +553,8 @@ class VideoTranscoder: self.audioconvert = gst.element_factory_make('audioconvert', 'audioconvert') self.pipeline.add(self.audioconvert) - self.audiocapsfilter = gst.element_factory_make('capsfilter', 'audiocapsfilter') + self.audiocapsfilter = gst.element_factory_make('capsfilter', + 'audiocapsfilter') audiocaps = ['audio/x-raw-float'] self.audiocapsfilter.set_property( 'caps', @@ -913,12 +674,14 @@ class VideoTranscoder: elif message.type == gst.MESSAGE_ELEMENT: if message.structure.get_name() == 'progress': data = dict(message.structure) - - if self._progress_callback: - self._progress_callback(data.get('percent')) - - _log.info('{percent}% done...'.format( - percent=data.get('percent'))) + # 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: @@ -980,6 +743,10 @@ if __name__ == '__main__': 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: @@ -999,10 +766,11 @@ if __name__ == '__main__': 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).__dict__ + 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 index 80d8ece7..d58f13d4 100644 --- a/mediagoblin/messages.py +++ b/mediagoblin/messages.py @@ -14,16 +14,24 @@ # You 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() @@ -33,4 +41,10 @@ def fetch_messages(request, clear_from_session=True): # 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 index fffa1dda..26ed66fa 100644 --- a/mediagoblin/mg_globals.py +++ b/mediagoblin/mg_globals.py @@ -26,15 +26,9 @@ import threading # General mediagoblin globals ############################# -# mongokit.Connection -db_connection = None - -# mongokit.Connection +# SQL database engine database = None -# beaker's cache manager -cache = None - # should be the same as the public_store = None queue_store = None @@ -45,11 +39,13 @@ workbench_manager = None # A thread-local scope thread_scope = threading.local() -# gettext -thread_scope.translations = gettext.find( +# 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', 'translations'), ['en']) + 'mediagoblin', 'i18n'), ['en'], fallback=True) # app and global config objects app_config = None diff --git a/mediagoblin/plugins/api/__init__.py b/mediagoblin/plugins/api/__init__.py index d3fdf2ef..1eddd9e0 100644 --- a/mediagoblin/plugins/api/__init__.py +++ b/mediagoblin/plugins/api/__init__.py @@ -23,11 +23,11 @@ _log = logging.getLogger(__name__) PLUGIN_DIR = os.path.dirname(__file__) -config = pluginapi.get_config(__name__) - def setup_plugin(): _log.info('Setting up API...') + config = pluginapi.get_config(__name__) + _log.debug('API config: {0}'.format(config)) routes = [ diff --git a/mediagoblin/plugins/api/tools.py b/mediagoblin/plugins/api/tools.py index ecc50364..92411f4b 100644 --- a/mediagoblin/plugins/api/tools.py +++ b/mediagoblin/plugins/api/tools.py @@ -18,9 +18,9 @@ import logging import json from functools import wraps -from webob import exc, Response 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 @@ -54,23 +54,23 @@ class Auth(object): def json_response(serializable, _disable_cors=False, *args, **kw): ''' - Serializes a json objects and returns a webob.Response object with the + 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 - webob.Response.__init__ method. + Response.__init__ method. ''' - response = Response(json.dumps(serializable), *args, **kw) - response.headers['Content-Type'] = 'application/json' + 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'} - response.headers.update(cors_headers) + for key, value in cors_headers.iteritems(): + response.headers.set(key, value) return response @@ -136,14 +136,14 @@ def api_auth(controller): auth_candidates = [] for auth in PluginManager().get_hook_callables('auth'): - _log.debug('Plugin auth: {0}'.format(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: - return exc.HTTPForbidden() + raise Forbidden() # For now, just select the first one in the list auth = auth_candidates[0] @@ -157,7 +157,7 @@ def api_auth(controller): 'status': 403, 'errors': auth.errors}) - return exc.HTTPForbidden() + raise Forbidden() return controller(request, *args, **kw) diff --git a/mediagoblin/plugins/api/views.py b/mediagoblin/plugins/api/views.py index a1b1bcac..fde76fe4 100644 --- a/mediagoblin/plugins/api/views.py +++ b/mediagoblin/plugins/api/views.py @@ -16,22 +16,18 @@ import json import logging -import uuid from os.path import splitext -from webob import exc, Response -from werkzeug.utils import secure_filename -from werkzeug.datastructures import FileStorage -from celery import registry +from werkzeug.exceptions import BadRequest, Forbidden +from werkzeug.wrappers import Response -from mediagoblin.db.util import ObjectId from mediagoblin.decorators import require_active_login -from mediagoblin.processing import mark_entry_failed -from mediagoblin.processing.task import ProcessMedia 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 _log = logging.getLogger(__name__) @@ -47,20 +43,17 @@ def post_entry(request): if request.method != 'POST': _log.debug('Must POST against post_entry') - return exc.HTTPBadRequest() + raise BadRequest() - if not 'file' in request.files \ - or not isinstance(request.files['file'], FileStorage) \ - or not request.files['file'].stream: + if not check_file_field(request, 'file'): _log.debug('File field not found') - return exc.HTTPBadRequest() + raise BadRequest() media_file = request.files['file'] media_type, media_manager = sniff_media(media_file) entry = request.db.MediaEntry() - entry.id = ObjectId() entry.media_type = unicode(media_type) entry.title = unicode(request.form.get('title') or splitext(media_file.filename)[0]) @@ -72,28 +65,14 @@ def post_entry(request): entry.generate_slug() - task_id = unicode(uuid.uuid4()) - - # Now store generate the queueing related filename - queue_filepath = request.app.queue_store.get_unique_filepath( - ['media_entries', - task_id, - secure_filename(media_file.filename)]) - # queue appropriately - queue_file = request.app.queue_store.get_file( - queue_filepath, 'wb') + queue_file = prepare_queue_task(request.app, entry, media_file.filename) with queue_file: queue_file.write(request.files['file'].stream.read()) - # Add queued filename to the entry - entry.queued_media_file = queue_filepath - - entry.queued_task_id = task_id - # Save now so we have this data before kicking off processing - entry.save(validate=True) + entry.save() if request.form.get('callback_url'): metadata = request.db.ProcessingMetaData() @@ -105,36 +84,23 @@ def post_entry(request): # # (... don't change entry after this point to avoid race # conditions with changes to the document via processing code) - process_media = registry.tasks[ProcessMedia.name] - try: - process_media.apply_async( - [unicode(entry._id)], {}, - task_id=task_id) - except BaseException as e: - # 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, e) - # re-raise the exception - raise + 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): - if not request.user: - return exc.HTTPForbidden() - 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)) 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/templates/mediagoblin/utils/geolocation_map.html b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html index cd57d1f8..70f837ff 100644 --- a/mediagoblin/templates/mediagoblin/utils/geolocation_map.html +++ b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html @@ -17,8 +17,7 @@ #} {% block geolocation_map %} - {% if app_config['geolocation_map_visible'] - and media.media_data.gps_latitude is defined + {% 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 %} @@ -33,6 +32,21 @@ <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 ©<a + href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> + contributors + </li><li>Imaging ©<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 -%} 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 index d3d2065e..99b6a4b0 100644 --- a/mediagoblin/plugins/httpapiauth/__init__.py +++ b/mediagoblin/plugins/httpapiauth/__init__.py @@ -15,9 +15,8 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging -import base64 -from werkzeug.exceptions import BadRequest, Unauthorized +from werkzeug.exceptions import Unauthorized from mediagoblin.plugins.api.tools import Auth @@ -41,7 +40,7 @@ class HTTPAuth(Auth): return False user = request.db.User.query.filter_by( - username=request.authorization['username']).first() + username=unicode(request.authorization['username'])).first() if user.check_login(request.authorization['password']): request.user = user diff --git a/mediagoblin/plugins/oauth/README.rst b/mediagoblin/plugins/oauth/README.rst index 405a67e2..753b180f 100644 --- a/mediagoblin/plugins/oauth/README.rst +++ b/mediagoblin/plugins/oauth/README.rst @@ -7,6 +7,10 @@ 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. diff --git a/mediagoblin/plugins/oauth/__init__.py b/mediagoblin/plugins/oauth/__init__.py index 4714d95d..5762379d 100644 --- a/mediagoblin/plugins/oauth/__init__.py +++ b/mediagoblin/plugins/oauth/__init__.py @@ -34,7 +34,7 @@ def setup_plugin(): _log.debug('OAuth config: {0}'.format(config)) routes = [ - ('mediagoblin.plugins.oauth.authorize', + ('mediagoblin.plugins.oauth.authorize', '/oauth/authorize', 'mediagoblin.plugins.oauth.views:authorize'), ('mediagoblin.plugins.oauth.authorize_client', diff --git a/mediagoblin/plugins/oauth/forms.py b/mediagoblin/plugins/oauth/forms.py index 2a956dad..5edd992a 100644 --- a/mediagoblin/plugins/oauth/forms.py +++ b/mediagoblin/plugins/oauth/forms.py @@ -19,14 +19,13 @@ import wtforms from urlparse import urlparse from mediagoblin.tools.extlib.wtf_html5 import URLField -from mediagoblin.tools.translate import fake_ugettext_passthrough as _ +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ class AuthorizationForm(wtforms.Form): - client_id = wtforms.HiddenField(_(u'Client ID'), - [wtforms.validators.Required()]) - next = wtforms.HiddenField(_(u'Next URL'), - [wtforms.validators.Required()]) + 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')) diff --git a/mediagoblin/plugins/oauth/migrations.py b/mediagoblin/plugins/oauth/migrations.py index f2af3907..d7b89da3 100644 --- a/mediagoblin/plugins/oauth/migrations.py +++ b/mediagoblin/plugins/oauth/migrations.py @@ -14,16 +14,109 @@ # You 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 MetaData, Table +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.sql.util import RegisterMigration +from mediagoblin.db.migration_tools import RegisterMigration +from mediagoblin.db.models import User -from mediagoblin.plugins.oauth.models import OAuthClient, OAuthToken, \ - OAuthUserClient, OAuthCode 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) @@ -38,9 +131,28 @@ def remove_and_replace_token_and_code(db): code_table.drop() - OAuthClient.__table__.create(db.bind) - OAuthUserClient.__table__.create(db.bind) - OAuthToken.__table__.create(db.bind) - OAuthCode.__table__.create(db.bind) + 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 index 7e247c1a..439424d3 100644 --- a/mediagoblin/plugins/oauth/models.py +++ b/mediagoblin/plugins/oauth/models.py @@ -14,17 +14,17 @@ # You 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 bcrypt from datetime import datetime, timedelta -from mediagoblin.db.sql.base import Base -from mediagoblin.db.sql.models import User from sqlalchemy import ( Column, Unicode, Integer, DateTime, ForeignKey, Enum) -from sqlalchemy.orm import relationship +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. @@ -41,11 +41,14 @@ class OAuthClient(Base): name = Column(Unicode) description = Column(Unicode) - identifier = Column(Unicode, unique=True, index=True) - secret = Column(Unicode, index=True) + 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='registered_clients') + owner = relationship( + User, + backref=backref('registered_clients', cascade='all, delete-orphan')) redirect_uri = Column(Unicode) @@ -54,14 +57,8 @@ class OAuthClient(Base): u'public', name=u'oauth__client_type')) - def generate_identifier(self): - self.identifier = unicode(uuid.uuid4()) - - def generate_secret(self): - self.secret = unicode( - bcrypt.hashpw( - unicode(uuid.uuid4()), - bcrypt.gensalt())) + def update_secret(self): + self.secret = generate_secret() def __repr__(self): return '<{0} {1}:{2} ({3})>'.format( @@ -76,10 +73,15 @@ class OAuthUserClient(Base): id = Column(Integer, primary_key=True) user_id = Column(Integer, ForeignKey(User.id)) - user = relationship(User, backref='oauth_clients') + user = relationship( + User, + backref=backref('oauth_client_relations', + cascade='all, delete-orphan')) client_id = Column(Integer, ForeignKey(OAuthClient.id)) - client = relationship(OAuthClient, backref='users') + client = relationship( + OAuthClient, + backref=backref('oauth_user_relations', cascade='all, delete-orphan')) state = Column(Enum( u'approved', @@ -103,15 +105,18 @@ class OAuthToken(Base): 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) + token = Column(Unicode, index=True, default=generate_token) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) - user = relationship(User) + user = relationship( + User, + backref=backref('oauth_tokens', cascade='all, delete-orphan')) client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False) - client = relationship(OAuthClient) + client = relationship( + OAuthClient, + backref=backref('oauth_tokens', cascade='all, delete-orphan')) def __repr__(self): return '<{0} #{1} expires {2} [{3}, {4}]>'.format( @@ -121,6 +126,34 @@ class OAuthToken(Base): 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' @@ -130,14 +163,17 @@ class OAuthCode(Base): default=datetime.now) expires = Column(DateTime, nullable=False, default=lambda: datetime.now() + timedelta(minutes=5)) - code = Column(Unicode, index=True) + code = Column(Unicode, index=True, default=generate_code) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) - user = relationship(User) + user = relationship(User, backref=backref('oauth_codes', + cascade='all, delete-orphan')) client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False) - client = relationship(OAuthClient) + client = relationship(OAuthClient, backref=backref( + 'oauth_codes', + cascade='all, delete-orphan')) def __repr__(self): return '<{0} #{1} expires {2} [{3}, {4}]>'.format( @@ -150,6 +186,7 @@ class OAuthCode(Base): MODELS = [ OAuthToken, + OAuthRefreshToken, OAuthCode, OAuthClient, OAuthUserClient] diff --git a/mediagoblin/plugins/oauth/templates/oauth/authorize.html b/mediagoblin/plugins/oauth/templates/oauth/authorize.html index 647fa41f..8a00c925 100644 --- a/mediagoblin/plugins/oauth/templates/oauth/authorize.html +++ b/mediagoblin/plugins/oauth/templates/oauth/authorize.html @@ -16,7 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -#} {% extends "mediagoblin/base.html" %} -{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} +{% import "mediagoblin/utils/wtforms.html" as wtforms_util %} {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.plugins.oauth.authorize_client') }}" diff --git a/mediagoblin/plugins/oauth/tools.py b/mediagoblin/plugins/oauth/tools.py index d21c8a5b..27ff32b4 100644 --- a/mediagoblin/plugins/oauth/tools.py +++ b/mediagoblin/plugins/oauth/tools.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # @@ -14,13 +15,26 @@ # You 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.oauth.models import OAuthClient 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'): @@ -41,3 +55,60 @@ def require_client_auth(controller): 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 index cf605fd2..d6fd314f 100644 --- a/mediagoblin/plugins/oauth/views.py +++ b/mediagoblin/plugins/oauth/views.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # @@ -15,22 +16,21 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging -import json -from webob import exc, Response from urllib import urlencode -from uuid import uuid4 -from datetime import datetime + +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, ERROR +from mediagoblin.messages import add_message, SUCCESS from mediagoblin.tools.translate import pass_to_ugettext as _ -from mediagoblin.plugins.oauth.models import OAuthCode, OAuthToken, \ - OAuthClient, OAuthUserClient +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 +from mediagoblin.plugins.oauth.tools import require_client_auth, \ + create_token from mediagoblin.plugins.api.tools import json_response _log = logging.getLogger(__name__) @@ -45,14 +45,11 @@ def register_client(request): if request.method == 'POST' and form.validate(): client = OAuthClient() - client.name = unicode(request.form['name']) - client.description = unicode(request.form['description']) - client.type = unicode(request.form['type']) + 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(request.form['redirect_uri']) - - client.generate_identifier() - client.generate_secret() + client.redirect_uri = unicode(form.redirect_uri.data) client.save() @@ -92,9 +89,9 @@ def authorize_client(request): form.client_id.data).first() if not client: - _log.error('''No such client id as received from client authorization - form.''') - return exc.HTTPBadRequest() + _log.error('No such client id as received from client authorization \ +form.') + raise BadRequest() if form.validate(): relation = OAuthUserClient() @@ -105,11 +102,11 @@ def authorize_client(request): elif form.deny.data: relation.state = u'rejected' else: - return exc.HTTPBadRequest + raise BadRequest() relation.save() - return exc.HTTPFound(location=form.next.data) + return redirect(request, location=form.next.data) return render_to_response( request, @@ -136,7 +133,7 @@ def authorize(request, client): return json_response({ 'status': 400, 'errors': - [u'Public clients MUST have a redirect_uri pre-set']}, + [u'Public clients should have a redirect_uri pre-set.']}, _disable_cors=True) redirect_uri = client.redirect_uri @@ -146,11 +143,10 @@ def authorize(request, client): if not redirect_uri: return json_response({ 'status': 400, - 'errors': [u'Can not find a redirect_uri for client: {0}'\ - .format(client.name)]}, _disable_cors=True) + 'errors': [u'No redirect_uri supplied!']}, + _disable_cors=True) code = OAuthCode() - code.code = unicode(uuid4()) code.user = request.user code.client = client code.save() @@ -162,7 +158,7 @@ def authorize(request, client): _log.debug('Redirecting to {0}'.format(redirect_uri)) - return exc.HTTPFound(location=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 @@ -180,56 +176,79 @@ def authorize(request, 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 code: - if code.client.type == u'confidential': - client_identifier = request.GET.get('client_id') - - if not client_identifier: - return json_response({ - 'error': 'invalid_request', - 'error_description': - 'Missing client_id in request'}) - - 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 == code.client.secret or \ - not client_identifier == code.client.identifier: - return json_response({ - 'error': 'invalid_client', - 'error_description': - 'The client_id or client_secret does not match the' - ' code'}) - - token = OAuthToken() - token.token = unicode(uuid4()) - token.user = code.user - token.client = code.client - token.save() - - access_token_data = { - 'access_token': token.token, - 'token_type': 'bearer', - 'expires_in': int( - round( - (token.expires - datetime.now()).total_seconds()))} - return json_response(access_token_data, _disable_cors=True) - else: + if not code: return json_response({ 'error': 'invalid_request', 'error_description': - 'Invalid code'}) - else: - return json_response({ - 'error': 'invalid_request', - 'error_descriptin': - 'Missing `code` parameter in request'}) + '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..400be615 --- /dev/null +++ b/mediagoblin/plugins/piwigo/tools.py @@ -0,0 +1,152 @@ +# 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 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__) + + +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") + _fill_element(r, result) + return Response(ET.tostring(r, encoding="utf-8", xml_declaration=True), + mimetype='text/xml') + + +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.error): + _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..b59247ad --- /dev/null +++ b/mediagoblin/plugins/piwigo/views.py @@ -0,0 +1,183 @@ +# 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 werkzeug.exceptions import MethodNotAllowed, BadRequest, NotImplemented +from werkzeug.wrappers import BaseResponse + +from mediagoblin.meddleware.csrf import csrf_exempt +from mediagoblin.submit.lib import check_file_field +from mediagoblin.auth.lib import fake_login_attempt +from .tools import CmdTable, PwgNamedArray, response_xml, check_form, \ + PWGSession +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") + _log.debug("Login for %r/%r...", username, password) + user = request.db.User.query.filter_by(username=username).first() + if not user: + _log.info("User %r not found", username) + fake_login_attempt() + return False + if not user.check_login(password): + _log.warn("Wrong password for %r", username) + return False + _log.info("Logging %r in", username) + 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"},) + 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("addimple: %r %s %r", request.form, " ".join(dump), request.files) + + if not check_file_field(request, 'image'): + raise BadRequest() + + return {'image_id': 123456, 'url': ''} + + +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/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 index 6b2d50e2..f3a85940 100644 --- a/mediagoblin/processing/__init__.py +++ b/mediagoblin/processing/__init__.py @@ -38,7 +38,7 @@ class ProgressCallback(object): def create_pub_filepath(entry, filename): return mgg.public_store.get_unique_filepath( ['media_entries', - unicode(entry._id), + unicode(entry.id), filename]) @@ -74,6 +74,61 @@ class FilenameBuilder(object): 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. @@ -93,7 +148,7 @@ def mark_entry_failed(entry_id, exc): # 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}, + {'id': entry_id}, {u'state': u'failed', u'fail_error': unicode(exc.exception_path), u'fail_metadata': exc.metadata}) @@ -104,7 +159,7 @@ def mark_entry_failed(entry_id, exc): # metadata (in fact overwrite it if somehow it had previous info # here) atomic_update(mgg.database.MediaEntry, - {'_id': entry_id}, + {'id': entry_id}, {u'state': u'failed', u'fail_error': None, u'fail_metadata': {}}) diff --git a/mediagoblin/processing/task.py b/mediagoblin/processing/task.py index 187b893d..9af192ed 100644 --- a/mediagoblin/processing/task.py +++ b/mediagoblin/processing/task.py @@ -15,13 +15,14 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging +import urllib +import urllib2 -from celery.task import Task +from celery import registry, task from mediagoblin import mg_globals as mgg -from mediagoblin.db.util import ObjectId -from mediagoblin.media_types import get_media_manager -from mediagoblin.processing import mark_entry_failed, BaseProcessingFail +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__) @@ -29,39 +30,79 @@ 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): +class ProcessMedia(task.Task): """ Pass this entry off for processing. """ - def run(self, media_id): + 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 = mgg.database.MediaEntry.one( - {'_id': ObjectId(media_id)}) + entry = MediaEntry.query.get(media_id) # Try to process, and handle expected errors. try: - manager = get_media_manager(entry.media_type) - entry.state = u'processing' entry.save() _log.debug('Processing {0}'.format(entry)) - manager['processor'](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) + mark_entry_failed(entry.id, exc) json_processing_callback(entry) return @@ -72,7 +113,7 @@ class ProcessMedia(Task): entry.title, exc)) - mark_entry_failed(entry._id, exc) + mark_entry_failed(entry.id, exc) json_processing_callback(entry) except Exception as exc: @@ -80,7 +121,7 @@ class ProcessMedia(Task): + ' processing {0}'.format( entry)) - mark_entry_failed(entry._id, exc) + mark_entry_failed(entry.id, exc) json_processing_callback(entry) raise @@ -98,3 +139,7 @@ class ProcessMedia(Task): 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 index defbc4ba..a650f22f 100644 --- a/mediagoblin/routing.py +++ b/mediagoblin/routing.py @@ -14,42 +14,29 @@ # You 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.routing import Map, Rule +import logging -url_map = Map() +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 -view_functions = {} -def add_route(endpoint, url, controller): - """ - Add a route to the url mapping - """ - # XXX: We cannot use this, since running tests means that the plugin - # routes will be populated over and over over the same session. - # - # assert endpoint not in view_functions.keys(), 'Trying to overwrite a rule' +_log = logging.getLogger(__name__) - view_functions.update({endpoint: controller}) - url_map.add(Rule(url, endpoint=endpoint)) +def get_url_map(): + add_route('index', '/', 'mediagoblin.views:root_view') + mount('/auth', auth_routes) + mount('/a', admin_routes) -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) + import mediagoblin.submit.routing + import mediagoblin.user_pages.routing + import mediagoblin.edit.routing + import mediagoblin.webfinger.routing + import mediagoblin.listings.routing -add_route('index', '/', 'mediagoblin.views:root_view') + for route in PluginManager().get_routes(): + add_route(*route) -from mediagoblin.admin.routing import admin_routes -from mediagoblin.auth.routing import auth_routes -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 + return url_map diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css index 4120f965..0cb36753 100644 --- a/mediagoblin/static/css/base.css +++ b/mediagoblin/static/css/base.css @@ -113,10 +113,12 @@ input, textarea { header { width: 100%; + max-width: 940px; + margin-left: auto; + margin-right: auto; padding: 0; margin-bottom: 42px; - background-color: #303030; - border-bottom: 1px solid #252525; + border-bottom: 1px solid #333; } .header_right { @@ -125,19 +127,24 @@ header { float: right; } -.header_right ul { - display: none; - position: absolute; - top: 42px; - right: 0px; - background: #252525; - padding: 20px; +.header_dropdown { + margin-bottom: 20px; } -.header_right li { +.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; @@ -145,7 +152,7 @@ a.logo { .logo img { vertical-align: middle; - margin: 6px 8px; + margin: 6px 8px 6px 0; } .mediagoblin_content { @@ -168,7 +175,7 @@ footer { width: 640px; margin-left: 0px; margin-right: 10px; - float: left; + float: left; } .media_sidebar { @@ -219,17 +226,6 @@ footer { font-family: 'Lato', sans-serif; } -.button_collect { - background-image: url("../images/icon_collect.png"); - background-repeat: no-repeat; - background-position:top center; - height: 30px; - width: 30px; - margin: 0px; - padding: 3px 3px 2px 3px; - position: relative; -} - .pagination { text-align: center; } @@ -259,6 +255,10 @@ text-align: center; height: 0; } +.hidden { + display: none; +} + .media_sidebar h3 { font-size: 1em; margin: 0 0 5px; @@ -338,6 +338,12 @@ textarea#description, textarea#bio { height: 100px; } +.delete { + margin-top: 36px; + display: block; + text-align: center; +} + /* comments */ .comment_wrapper { @@ -354,6 +360,25 @@ textarea#description, textarea#bio { 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; @@ -378,9 +403,8 @@ textarea#comment_content { float: left; padding: 0px; width: 180px; - height: 156px; overflow: hidden; - margin: 0px 4px 10px; + margin: 0px 3px 10px; text-align: center; font-size: 0.875em; background-color: #222; @@ -389,6 +413,9 @@ textarea#comment_content { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; + border-color: #0D0D0D; + border-style: solid; + border-width: 1px 1px 2px; } .media_thumbnail a { @@ -397,6 +424,11 @@ textarea#comment_content { display: block; } +.media_thumbnail a.remove { + color: #86D4B1; + text-decoration: underline; +} + a.thumb_entry_title { padding: 8px; } @@ -409,34 +441,6 @@ a.thumb_entry_title { margin-right: 0px; } -/* collection media */ - -.collection_thumbnail { - float: left; - padding: 0px; - width: 180px; - margin: 0px 4px 10px; - text-align: left; - font-size: 0.875em; - background-color: #222; - border-radius: 0 0 5px 5px; - padding: 0 0 6px; - text-overflow: ellipsis; -} - -.collection_thumbnail a { - color: #eee; - text-decoration: none; -} - -.collection_thumbnail a.remove { - color: #86D4B1; -} - -.collection_thumbnail img { - max-height: 135px; -} - /* media detail */ h2.media_title { @@ -555,6 +559,7 @@ table.media_panel { table.media_panel th { font-weight: bold; padding-bottom: 4px; + text-align: left; } diff --git a/mediagoblin/static/css/pdf_viewer.css b/mediagoblin/static/css/pdf_viewer.css new file mode 100644 index 00000000..c04c8981 --- /dev/null +++ b/mediagoblin/static/css/pdf_viewer.css @@ -0,0 +1,1448 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +* { + padding: 0; + margin: 0; +} + +html { + height: 100%; +} + +body { + height: 100%; + background-color: #404040; + background-image: url(../extlib/pdf.js/web/images/texture.png); +} + +body, +input, +button, +select { + font: message-box; +} + +.hidden { + display: none; +} +[hidden] { + display: none !important; +} + +#viewerContainer:-webkit-full-screen { + top: 0px; + border-top: 2px solid transparent; + background-color: #404040; + background-image: url(../extlib/pdf.js/web/images/texture.png); + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; +} + +#viewerContainer:-moz-full-screen { + top: 0px; + border-top: 2px solid transparent; + background-color: #404040; + background-image: url(../extlib/pdf.js/web/images/texture.png); + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; +} + +#viewerContainer:fullscreen { + top: 0px; + border-top: 2px solid transparent; + background-color: #404040; + background-image: url(../extlib/pdf.js/web/images/texture.png); + width: 100%; + height: 100%; + overflow: hidden; + cursor: none; +} + + +:-webkit-full-screen .page { + margin-bottom: 100%; +} + +:-moz-full-screen .page { + margin-bottom: 100%; +} + +:fullscreen .page { + margin-bottom: 100%; +} + +#viewerContainer.presentationControls { + cursor: default; +} + +/* outer/inner center provides horizontal center */ +html[dir='ltr'] .outerCenter { + float: right; + position: relative; + right: 50%; +} +html[dir='rtl'] .outerCenter { + float: left; + position: relative; + left: 50%; +} +html[dir='ltr'] .innerCenter { + float: right; + position: relative; + right: -50%; +} +html[dir='rtl'] .innerCenter { + float: left; + position: relative; + left: -50%; +} + +#outerContainer { + width: 100%; + height: 100%; +} + +#sidebarContainer { + left: 0; + right: 0; + height: 200px; + visibility: hidden; + -webkit-transition-duration: 200ms; + -webkit-transition-timing-function: ease; + -moz-transition-duration: 200ms; + -moz-transition-timing-function: ease; + -ms-transition-duration: 200ms; + -ms-transition-timing-function: ease; + -o-transition-duration: 200ms; + -o-transition-timing-function: ease; + transition-duration: 200ms; + transition-timing-function: ease; + +} +html[dir='ltr'] #sidebarContainer { + -webkit-transition-property: top; + -moz-transition-property: top; + -ms-transition-property: top; + -o-transition-property: top; + transition-property: top; + top: -200px; +} +html[dir='rtl'] #sidebarContainer { + -webkit-transition-property: top; + -ms-transition-property: top; + -o-transition-property: top; + transition-property: top; + top: -200px; +} + +#outerContainer.sidebarMoving > #sidebarContainer, +#outerContainer.sidebarOpen > #sidebarContainer { + visibility: visible; +} +html[dir='ltr'] #outerContainer.sidebarOpen > #sidebarContainer { + left: 0px; +} +html[dir='rtl'] #outerContainer.sidebarOpen > #sidebarContainer { + right: 0px; +} + +#mainContainer { + top: 0; + right: 0; + bottom: 0; + left: 0; + min-width: 320px; + -webkit-transition-duration: 200ms; + -webkit-transition-timing-function: ease; + -moz-transition-duration: 200ms; + -moz-transition-timing-function: ease; + -ms-transition-duration: 200ms; + -ms-transition-timing-function: ease; + -o-transition-duration: 200ms; + -o-transition-timing-function: ease; + transition-duration: 200ms; + transition-timing-function: ease; +} +html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer { + -webkit-transition-property: left; + -moz-transition-property: left; + -ms-transition-property: left; + -o-transition-property: left; + transition-property: left; + left: 200px; +} +html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer { + -webkit-transition-property: right; + -moz-transition-property: right; + -ms-transition-property: right; + -o-transition-property: right; + transition-property: right; + right: 200px; +} + +#sidebarContent { + top: 32px; + bottom: 0; + overflow: auto; + height: 200px; + + background-color: hsla(0,0%,0%,.1); + box-shadow: inset -1px 0 0 hsla(0,0%,0%,.25); +} +html[dir='ltr'] #sidebarContent { + left: 0; +} +html[dir='rtl'] #sidebarContent { + right: 0; +} + +#viewerContainer { + overflow: auto; + box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05); + top: 32px; + right: 0; + bottom: 0; + left: 0; + height: 480px; + width: 640px; +} + +.toolbar { + left: 0; + right: 0; + height: 32px; + z-index: 9999; + cursor: default; +} + +#toolbarContainer { + width: 100%; +} + +#toolbarSidebar { + width: 200px; + height: 32px; + background-image: url(../extlib/pdf.js/web/images/texture.png), + -webkit-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + -moz-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + -ms-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + -o-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95)); + box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25), + + inset 0 -1px 0 hsla(0,0%,100%,.05), + 0 1px 0 hsla(0,0%,0%,.15), + 0 0 1px hsla(0,0%,0%,.1); +} + +#toolbarViewer, .findbar { + position: relative; + height: 32px; + background-color: #474747; /* IE9 */ + background-image: url(../extlib/pdf.js/web/images/texture.png), + -webkit-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + -moz-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + -ms-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + -o-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); + background-image: url(../extlib/pdf.js/web/images/texture.png), + linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); + box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08), + inset 0 1px 1px hsla(0,0%,0%,.15), + inset 0 -1px 0 hsla(0,0%,100%,.05), + 0 1px 0 hsla(0,0%,0%,.15), + 0 1px 1px hsla(0,0%,0%,.1); +} + +.findbar { + top: 64px; + z-index: 10000; + height: 32px; + + min-width: 16px; + padding: 0px 6px 0px 6px; + margin: 4px 2px 4px 2px; + color: hsl(0,0%,85%); + font-size: 12px; + line-height: 14px; + text-align: left; + cursor: default; +} + +html[dir='ltr'] .findbar { + left: 68px; +} + +html[dir='rtl'] .findbar { + right: 68px; +} + +.findbar label { + -webkit-user-select: none; + -moz-user-select: none; +} + +#findInput[data-status="pending"] { + background-image: url(../extlib/pdf.js/web/images/loading-small.png); + background-repeat: no-repeat; + background-position: right; +} + +.doorHanger { + border: 1px solid hsla(0,0%,0%,.5); + border-radius: 2px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); +} +.doorHanger:after, .doorHanger:before { + bottom: 100%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + pointer-events: none; +} +.doorHanger:after { + border-bottom-color: hsla(0,0%,32%,.99); + border-width: 8px; +} +.doorHanger:before { + border-bottom-color: hsla(0,0%,0%,.5); + border-width: 9px; +} + +html[dir='ltr'] .doorHanger:after { + left: 13px; + margin-left: -8px; +} + +html[dir='ltr'] .doorHanger:before { + left: 13px; + margin-left: -9px; +} + +html[dir='rtl'] .doorHanger:after { + right: 13px; + margin-right: -8px; +} + +html[dir='rtl'] .doorHanger:before { + right: 13px; + margin-right: -9px; +} + +#findMsg { + font-style: italic; + color: #A6B7D0; +} + +.notFound { + background-color: rgb(255, 137, 153); +} + +html[dir='ltr'] #toolbarViewerLeft { + margin-left: -1px; +} +html[dir='rtl'] #toolbarViewerRight { + margin-left: -1px; +} + + +html[dir='ltr'] #toolbarViewerLeft, +html[dir='rtl'] #toolbarViewerRight { + position: absolute; + top: 0; + left: 0; +} +html[dir='ltr'] #toolbarViewerRight, +html[dir='rtl'] #toolbarViewerLeft { + position: absolute; + top: 0; + right: 0; +} +html[dir='ltr'] #toolbarViewerLeft > *, +html[dir='ltr'] #toolbarViewerMiddle > *, +html[dir='ltr'] #toolbarViewerRight > *, +html[dir='ltr'] .findbar > * { + float: left; +} +html[dir='rtl'] #toolbarViewerLeft > *, +html[dir='rtl'] #toolbarViewerMiddle > *, +html[dir='rtl'] #toolbarViewerRight > *, +html[dir='rtl'] .findbar > * { + float: right; +} + +html[dir='ltr'] .splitToolbarButton { + margin: 3px 2px 4px 0; + display: inline-block; +} +html[dir='rtl'] .splitToolbarButton { + margin: 3px 0 4px 2px; + display: inline-block; +} +html[dir='ltr'] .splitToolbarButton > .toolbarButton { + border-radius: 0; + float: left; +} +html[dir='rtl'] .splitToolbarButton > .toolbarButton { + border-radius: 0; + float: right; +} + +.toolbarButton { + border: 0 none; + background-color: rgba(0, 0, 0, 0); + width: 32px; + height: 25px; +} + +.toolbarButton > span { + display: inline-block; + width: 0; + height: 0; + overflow: hidden; +} + +.toolbarButton[disabled] { + opacity: .5; +} + +.toolbarButton.group { + margin-right: 0; +} + +.splitToolbarButton.toggled .toolbarButton { + margin: 0; +} + +.splitToolbarButton:hover > .toolbarButton, +.splitToolbarButton:focus > .toolbarButton, +.splitToolbarButton.toggled > .toolbarButton, +.toolbarButton.textButton { + background-color: hsla(0,0%,0%,.12); + background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-clip: padding-box; + border: 1px solid hsla(0,0%,0%,.35); + border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); + box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, + 0 0 1px hsla(0,0%,100%,.15) inset, + 0 1px 0 hsla(0,0%,100%,.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 150ms; + -webkit-transition-timing-function: ease; + -moz-transition-property: background-color, border-color, box-shadow; + -moz-transition-duration: 150ms; + -moz-transition-timing-function: ease; + -ms-transition-property: background-color, border-color, box-shadow; + -ms-transition-duration: 150ms; + -ms-transition-timing-function: ease; + -o-transition-property: background-color, border-color, box-shadow; + -o-transition-duration: 150ms; + -o-transition-timing-function: ease; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; + +} +.splitToolbarButton > .toolbarButton:hover, +.splitToolbarButton > .toolbarButton:focus, +.dropdownToolbarButton:hover, +.toolbarButton.textButton:hover, +.toolbarButton.textButton:focus { + background-color: hsla(0,0%,0%,.2); + box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, + 0 0 1px hsla(0,0%,100%,.15) inset, + 0 0 1px hsla(0,0%,0%,.05); + z-index: 199; +} +html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child, +html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child { + position: relative; + margin: 0; + margin-right: -1px; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + border-right-color: transparent; +} +html[dir='ltr'] .splitToolbarButton > .toolbarButton:last-child, +html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child { + position: relative; + margin: 0; + margin-left: -1px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-left-color: transparent; +} +.splitToolbarButtonSeparator { + padding: 8px 0; + width: 1px; + background-color: hsla(0,0%,00%,.5); + z-index: 99; + box-shadow: 0 0 0 1px hsla(0,0%,100%,.08); + display: inline-block; + margin: 5px 0; +} +html[dir='ltr'] .splitToolbarButtonSeparator { + float: left; +} +html[dir='rtl'] .splitToolbarButtonSeparator { + float: right; +} +.splitToolbarButton:hover > .splitToolbarButtonSeparator, +.splitToolbarButton.toggled > .splitToolbarButtonSeparator { + padding: 12px 0; + margin: 1px 0; + box-shadow: 0 0 0 1px hsla(0,0%,100%,.03); + -webkit-transition-property: padding; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: ease; + -moz-transition-property: padding; + -moz-transition-duration: 10ms; + -moz-transition-timing-function: ease; + -ms-transition-property: padding; + -ms-transition-duration: 10ms; + -ms-transition-timing-function: ease; + -o-transition-property: padding; + -o-transition-duration: 10ms; + -o-transition-timing-function: ease; + transition-property: padding; + transition-duration: 10ms; + transition-timing-function: ease; +} + +.toolbarButton, +.dropdownToolbarButton { + min-width: 16px; + padding: 2px 6px 0; + border: 1px solid transparent; + border-radius: 2px; + color: hsl(0,0%,95%); + font-size: 12px; + line-height: 14px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + /* Opera does not support user-select, use <... unselectable="on"> instead */ + cursor: default; + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 150ms; + -webkit-transition-timing-function: ease; + -moz-transition-property: background-color, border-color, box-shadow; + -moz-transition-duration: 150ms; + -moz-transition-timing-function: ease; + -ms-transition-property: background-color, border-color, box-shadow; + -ms-transition-duration: 150ms; + -ms-transition-timing-function: ease; + -o-transition-property: background-color, border-color, box-shadow; + -o-transition-duration: 150ms; + -o-transition-timing-function: ease; + transition-property: background-color, border-color, box-shadow; + transition-duration: 150ms; + transition-timing-function: ease; +} + +html[dir='ltr'] .toolbarButton, +html[dir='ltr'] .dropdownToolbarButton { + margin: 3px 2px 4px 0; +} +html[dir='rtl'] .toolbarButton, +html[dir='rtl'] .dropdownToolbarButton { + margin: 3px 0 4px 2px; +} + +.toolbarButton:hover, +.toolbarButton:focus, +.dropdownToolbarButton { + background-color: hsla(0,0%,0%,.12); + background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-clip: padding-box; + border: 1px solid hsla(0,0%,0%,.35); + border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); + box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, + 0 0 1px hsla(0,0%,100%,.15) inset, + 0 1px 0 hsla(0,0%,100%,.05); +} + +.toolbarButton:hover:active, +.dropdownToolbarButton:hover:active { + background-color: hsla(0,0%,0%,.2); + background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45); + box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset, + 0 0 1px hsla(0,0%,0%,.2) inset, + 0 1px 0 hsla(0,0%,100%,.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: linear; + -moz-transition-property: background-color, border-color, box-shadow; + -moz-transition-duration: 10ms; + -moz-transition-timing-function: linear; + -ms-transition-property: background-color, border-color, box-shadow; + -ms-transition-duration: 10ms; + -ms-transition-timing-function: linear; + -o-transition-property: background-color, border-color, box-shadow; + -o-transition-duration: 10ms; + -o-transition-timing-function: linear; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} + +.toolbarButton.toggled, +.splitToolbarButton.toggled > .toolbarButton.toggled { + background-color: hsla(0,0%,0%,.3); + background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.45) hsla(0,0%,0%,.5); + box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset, + 0 0 1px hsla(0,0%,0%,.2) inset, + 0 1px 0 hsla(0,0%,100%,.05); + -webkit-transition-property: background-color, border-color, box-shadow; + -webkit-transition-duration: 10ms; + -webkit-transition-timing-function: linear; + -moz-transition-property: background-color, border-color, box-shadow; + -moz-transition-duration: 10ms; + -moz-transition-timing-function: linear; + -ms-transition-property: background-color, border-color, box-shadow; + -ms-transition-duration: 10ms; + -ms-transition-timing-function: linear; + -o-transition-property: background-color, border-color, box-shadow; + -o-transition-duration: 10ms; + -o-transition-timing-function: linear; + transition-property: background-color, border-color, box-shadow; + transition-duration: 10ms; + transition-timing-function: linear; +} + +.toolbarButton.toggled:hover:active, +.splitToolbarButton.toggled > .toolbarButton.toggled:hover:active { + background-color: hsla(0,0%,0%,.4); + border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55); + box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset, + 0 0 1px hsla(0,0%,0%,.3) inset, + 0 1px 0 hsla(0,0%,100%,.05); +} + +.dropdownToolbarButton { + width: 120px; + max-width: 120px; + padding: 3px 2px 2px; + overflow: hidden; + background: url(../extlib/pdf.js/web/images/toolbarButton-menuArrows.png) no-repeat; +} +html[dir='ltr'] .dropdownToolbarButton { + background-position: 95%; +} +html[dir='rtl'] .dropdownToolbarButton { + background-position: 5%; +} + +.dropdownToolbarButton > select { + -webkit-appearance: none; + -moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */ + min-width: 140px; + font-size: 12px; + color: hsl(0,0%,95%); + margin: 0; + padding: 0; + border: none; + background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */ +} + +.dropdownToolbarButton > select > option { + background: hsl(0,0%,24%); +} + +#customScaleOption { + display: none; +} + +#pageWidthOption { + border-bottom: 1px rgba(255, 255, 255, .5) solid; +} + +html[dir='ltr'] .splitToolbarButton:first-child, +html[dir='ltr'] .toolbarButton:first-child, +html[dir='rtl'] .splitToolbarButton:last-child, +html[dir='rtl'] .toolbarButton:last-child { + margin-left: 4px; +} +html[dir='ltr'] .splitToolbarButton:last-child, +html[dir='ltr'] .toolbarButton:last-child, +html[dir='rtl'] .splitToolbarButton:first-child, +html[dir='rtl'] .toolbarButton:first-child { + margin-right: 4px; +} + +.toolbarButtonSpacer { + width: 30px; + display: inline-block; + height: 1px; +} + +.toolbarButtonFlexibleSpacer { + -webkit-box-flex: 1; + -moz-box-flex: 1; + min-width: 30px; +} + +.toolbarButton#sidebarToggle::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-sidebarToggle.png); +} + +html[dir='ltr'] #findPrevious { + margin-left: 3px; +} +html[dir='ltr'] #findNext { + margin-right: 3px; +} + +html[dir='rtl'] #findPrevious { + margin-right: 3px; +} +html[dir='rtl'] #findNext { + margin-left: 3px; +} + +html[dir='ltr'] .toolbarButton.findPrevious::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/findbarButton-previous.png); +} + +html[dir='rtl'] .toolbarButton.findPrevious::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/findbarButton-previous-rtl.png); +} + +html[dir='ltr'] .toolbarButton.findNext::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/findbarButton-next.png); +} + +html[dir='rtl'] .toolbarButton.findNext::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/findbarButton-next-rtl.png); +} + +html[dir='ltr'] .toolbarButton.pageUp::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-pageUp.png); +} + +html[dir='rtl'] .toolbarButton.pageUp::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-pageUp-rtl.png); +} + +html[dir='ltr'] .toolbarButton.pageDown::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-pageDown.png); +} + +html[dir='rtl'] .toolbarButton.pageDown::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-pageDown-rtl.png); +} + +.toolbarButton.zoomOut::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-zoomOut.png); +} + +.toolbarButton.zoomIn::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-zoomIn.png); +} + +.toolbarButton.fullscreen::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-fullscreen.png); +} + +.toolbarButton.print::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-print.png); +} + +.toolbarButton.openFile::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-openFile.png); +} + +.toolbarButton.download::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-download.png); +} + +.toolbarButton.bookmark { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-top: 3px; + padding-top: 4px; +} + +#viewBookmark[href='#'] { + opacity: .5; + pointer-events: none; +} + +.toolbarButton.bookmark::before { + content: url(../extlib/pdf.js/web/images/toolbarButton-bookmark.png); +} + +#viewThumbnail.toolbarButton::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-viewThumbnail.png); +} + +#viewOutline.toolbarButton::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-viewOutline.png); +} + +#viewFind.toolbarButton::before { + display: inline-block; + content: url(../extlib/pdf.js/web/images/toolbarButton-search.png); +} + + +.toolbarField { + padding: 3px 6px; + margin: 4px 0 4px 0; + border: 1px solid transparent; + border-radius: 2px; + background-color: hsla(0,0%,100%,.09); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-clip: padding-box; + border: 1px solid hsla(0,0%,0%,.35); + border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); + box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset, + 0 1px 0 hsla(0,0%,100%,.05); + color: hsl(0,0%,95%); + font-size: 12px; + line-height: 14px; + outline-style: none; + -moz-transition-property: background-color, border-color, box-shadow; + -moz-transition-duration: 150ms; + -moz-transition-timing-function: ease; +} + +.toolbarField[type=checkbox] { + display: inline-block; + margin: 8px 0px; +} + +.toolbarField.pageNumber { + min-width: 16px; + text-align: right; + width: 40px; +} + +.toolbarField.pageNumber::-webkit-inner-spin-button, +.toolbarField.pageNumber::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.toolbarField:hover { + background-color: hsla(0,0%,100%,.11); + border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45); +} + +.toolbarField:focus { + background-color: hsla(0,0%,100%,.15); + border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9); +} + +.toolbarLabel { + min-width: 16px; + padding: 3px 6px 3px 2px; + margin: 4px 2px 4px 0; + border: 1px solid transparent; + border-radius: 2px; + color: hsl(0,0%,85%); + font-size: 12px; + line-height: 14px; + text-align: left; + -webkit-user-select: none; + -moz-user-select: none; + cursor: default; +} + +#thumbnailView { + top: 0; + bottom: 0; + padding: 10px 10px 0; + overflow: auto; +} + +.thumbnail { + float: left; +} + +.thumbnail:not([data-loaded]) { + border: 1px dashed rgba(255, 255, 255, 0.5); + margin-bottom: 10px; +} + +.thumbnailImage { + -moz-transition-duration: 150ms; + border: 1px solid transparent; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); + opacity: 0.8; + z-index: 99; +} + +.thumbnailSelectionRing { + border-radius: 2px; + padding: 7px; + -moz-transition-duration: 150ms; +} + +a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage, +.thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage { + opacity: .9; +} + +a:focus > .thumbnail > .thumbnailSelectionRing, +.thumbnail:hover > .thumbnailSelectionRing { + background-color: hsla(0,0%,100%,.15); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-clip: padding-box; + box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, + 0 0 1px hsla(0,0%,100%,.2) inset, + 0 0 1px hsla(0,0%,0%,.2); + color: hsla(0,0%,100%,.9); +} + +.thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage { + box-shadow: 0 0 0 1px hsla(0,0%,0%,.5); + opacity: 1; +} + +.thumbnail.selected > .thumbnailSelectionRing { + background-color: hsla(0,0%,100%,.3); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-clip: padding-box; + box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, + 0 0 1px hsla(0,0%,100%,.1) inset, + 0 0 1px hsla(0,0%,0%,.2); + color: hsla(0,0%,100%,1); +} + +#outlineView { + width: 192px; + top: 0; + bottom: 0; + padding: 4px 4px 0; + overflow: auto; + -webkit-user-select: none; + -moz-user-select: none; +} + +html[dir='ltr'] .outlineItem > .outlineItems { + margin-left: 20px; +} + +html[dir='rtl'] .outlineItem > .outlineItems { + margin-right: 20px; +} + +.outlineItem > a { + text-decoration: none; + display: inline-block; + min-width: 95%; + height: auto; + margin-bottom: 1px; + border-radius: 2px; + color: hsla(0,0%,100%,.8); + font-size: 13px; + line-height: 15px; + -moz-user-select: none; + white-space: normal; +} + +html[dir='ltr'] .outlineItem > a { + padding: 2px 0 5px 10px; +} + +html[dir='rtl'] .outlineItem > a { + padding: 2px 10px 5px 0; +} + +.outlineItem > a:hover { + background-color: hsla(0,0%,100%,.02); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-clip: padding-box; + box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, + 0 0 1px hsla(0,0%,100%,.2) inset, + 0 0 1px hsla(0,0%,0%,.2); + color: hsla(0,0%,100%,.9); +} + +.outlineItem.selected { + background-color: hsla(0,0%,100%,.08); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-clip: padding-box; + box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, + 0 0 1px hsla(0,0%,100%,.1) inset, + 0 0 1px hsla(0,0%,0%,.2); + color: hsla(0,0%,100%,1); +} + +.noOutline, +.noResults { + font-size: 12px; + color: hsla(0,0%,100%,.8); + font-style: italic; + cursor: default; +} + +#findScrollView { + position: absolute; + top: 10px; + bottom: 10px; + left: 10px; + width: 280px; +} + +#sidebarControls { + position:absolute; + width: 180px; + height: 32px; + left: 15px; + bottom: 35px; +} + +canvas { + margin: auto; + display: block; +} + +.page { + direction: ltr; + width: 816px; + height: 1056px; + margin: 1px auto -8px auto; + position: relative; + overflow: visible; + border: 9px solid transparent; + background-clip: content-box; + border-image: url(../extlib/pdf.js/web/images/shadow.png) 9 9 repeat; + background-color: white; +} + +.page > a { + display: block; + position: absolute; +} + +.page > a:hover { + opacity: 0.2; + background: #ff0; + box-shadow: 0px 2px 10px #ff0; +} + +.loadingIcon { + position: absolute; + display: block; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: url('../extlib/pdf.js/web/images/loading-icon.gif') center no-repeat; +} + +#loadingBox { + position: absolute; + top: 50%; + margin-top: -25px; + left: 0; + right: 0; + text-align: center; + color: #ddd; + font-size: 14px; +} + +#loadingBar { + display: inline-block; + clear: both; + margin: 0px; + margin-top: 5px; + line-height: 0; + border-radius: 2px; + width: 200px; + height: 25px; + + background-color: hsla(0,0%,0%,.3); + background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); + border: 1px solid #000; + box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset, + 0 0 1px hsla(0,0%,0%,.2) inset, + 0 0 1px 1px rgba(255, 255, 255, 0.1); +} + +#loadingBar .progress { + display: inline-block; + float: left; + + background: #666; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2b2b2), color-stop(100%,#898989)); + background: -webkit-linear-gradient(top, #b2b2b2 0%,#898989 100%); + background: -moz-linear-gradient(top, #b2b2b2 0%,#898989 100%); + background: -ms-linear-gradient(top, #b2b2b2 0%,#898989 100%); + background: -o-linear-gradient(top, #b2b2b2 0%,#898989 100%); + background: linear-gradient(top, #b2b2b2 0%,#898989 100%); + + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + + width: 0%; + height: 100%; +} + +#loadingBar .progress.full { + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +#loadingBar .progress.indeterminate { + width: 100%; + height: 25px; + background-image: -moz-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040); + background-image: -webkit-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040); + background-image: -ms-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040); + background-image: -o-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040); + background-size: 75px 25px; + -moz-animation: progressIndeterminate 1s linear infinite; + -webkit-animation: progressIndeterminate 1s linear infinite; +} + +@-moz-keyframes progressIndeterminate { + from { background-position: 0px 0px; } + to { background-position: 75px 0px; } +} + +@-webkit-keyframes progressIndeterminate { + from { background-position: 0px 0px; } + to { background-position: 75px 0px; } +} + +.textLayer { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + color: #000; + font-family: sans-serif; + overflow: hidden; +} + +.textLayer > div { + color: transparent; + position: absolute; + line-height: 1; + white-space: pre; + cursor: text; +} + +.textLayer .highlight { + margin: -1px; + padding: 1px; + + background-color: rgba(180, 0, 170, 0.2); + border-radius: 4px; +} + +.textLayer .highlight.begin { + border-radius: 4px 0px 0px 4px; +} + +.textLayer .highlight.end { + border-radius: 0px 4px 4px 0px; +} + +.textLayer .highlight.middle { + border-radius: 0px; +} + +.textLayer .highlight.selected { + background-color: rgba(0, 100, 0, 0.2); +} + +/* TODO: file FF bug to support ::-moz-selection:window-inactive + so we can override the opaque grey background when the window is inactive; + see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */ +::selection { background:rgba(0,0,255,0.3); } +::-moz-selection { background:rgba(0,0,255,0.3); } + +.annotText > div { + z-index: 200; + position: absolute; + padding: 0.6em; + max-width: 20em; + background-color: #FFFF99; + box-shadow: 0px 2px 10px #333; + border-radius: 7px; +} + +.annotText > img { + position: absolute; + opacity: 0.6; +} + +.annotText > img:hover { + cursor: pointer; + opacity: 1; +} + +.annotText > div > h1 { + font-size: 1.2em; + border-bottom: 1px solid #000000; + margin: 0px; +} + +#errorWrapper { + background: none repeat scroll 0 0 #FF5555; + color: white; + left: 0; + position: absolute; + right: 0; + top: 32px; + z-index: 1000; + padding: 3px; + font-size: 0.8em; +} + +#errorMessageLeft { + float: left; +} + +#errorMessageRight { + float: right; +} + +#errorMoreInfo { + background-color: #FFFFFF; + color: black; + padding: 3px; + margin: 3px; + width: 98%; +} + +.clearBoth { + clear: both; +} + +.fileInput { + background: white; + color: black; + margin-top: 5px; +} + +#PDFBug { + background: none repeat scroll 0 0 white; + border: 1px solid #666666; + position: fixed; + top: 32px; + right: 0; + bottom: 0; + font-size: 10px; + padding: 0; + width: 300px; +} +#PDFBug .controls { + background:#EEEEEE; + border-bottom: 1px solid #666666; + padding: 3px; +} +#PDFBug .panels { + bottom: 0; + left: 0; + overflow: auto; + position: absolute; + right: 0; + top: 27px; +} +#PDFBug button.active { + font-weight: bold; +} +.debuggerShowText { + background: none repeat scroll 0 0 yellow; + color: blue; + opacity: 0.3; +} +.debuggerHideText:hover { + background: none repeat scroll 0 0 yellow; + opacity: 0.3; +} +#PDFBug .stats { + font-family: courier; + font-size: 10px; + white-space: pre; +} +#PDFBug .stats .title { + font-weight: bold; +} +#PDFBug table { + font-size: 10px; +} + +#viewer.textLayer-visible .textLayer > div, +#viewer.textLayer-hover .textLayer > div:hover { + background-color: white; + color: black; +} + +#viewer.textLayer-shadow .textLayer > div { + background-color: rgba(255,255,255, .6); + color: black; +} + +@page { + margin: 0; +} + +#printContainer { + display: none; +} + +@media print { + /* Rules for browsers that don't support mozPrintCallback. */ + #sidebarContainer, .toolbar, #loadingBox, #errorWrapper, .textLayer { + display: none; + } + + #mainContainer, #viewerContainer, .page, .page canvas { + position: static; + padding: 0; + margin: 0; + } + + .page { + float: left; + display: none; + box-shadow: none; + } + + .page[data-loaded] { + display: block; + } + + /* Rules for browsers that support mozPrintCallback */ + body[data-mozPrintCallback] #outerContainer { + display: none; + } + body[data-mozPrintCallback] #printContainer { + display: block; + } + #printContainer canvas { + position: relative; + top: 0; + left: 0; + } +} + +@media all and (max-width: 950px) { + html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter, + html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter { + float: left; + left: 180px; + } + html[dir='rtl'] #outerContainer.sidebarMoving .outerCenter, + html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter { + float: right; + right: 180px; + } +} + +@media all and (max-width: 770px) { + #sidebarContainer { + top: 33px; + z-index: 100; + } + #sidebarContent { + top: 32px; + background-color: hsla(0,0%,0%,.7); + } + + html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer { + left: 0px; + } + html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer { + right: 0px; + } + + html[dir='ltr'] .outerCenter { + float: left; + left: 180px; + } + html[dir='rtl'] .outerCenter { + float: right; + right: 180px; + } +} + +@media all and (max-width: 600px) { + .hiddenSmallView { + display: none; + } + html[dir='ltr'] .outerCenter { + left: 156px; + } + html[dir='rtr'] .outerCenter { + right: 156px; + } + .toolbarButtonSpacer { + width: 0; + } +} + +@media all and (max-width: 500px) { + #scaleSelectContainer, #pageNumberLabel { + display: none; + } +} + 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/images/icon_collect.png b/mediagoblin/static/images/icon_collect.png Binary files differdeleted file mode 100644 index 2911af24..00000000 --- a/mediagoblin/static/images/icon_collect.png +++ /dev/null diff --git a/mediagoblin/static/images/media_thumbs/image.png b/mediagoblin/static/images/media_thumbs/image.png Binary files differnew file mode 100644 index 00000000..8437a298 --- /dev/null +++ b/mediagoblin/static/images/media_thumbs/image.png diff --git a/mediagoblin/static/js/extlib/html5shiv.js b/mediagoblin/static/js/extlib/html5shiv.js deleted file mode 120000 index ca7358c7..00000000 --- a/mediagoblin/static/js/extlib/html5shiv.js +++ /dev/null @@ -1 +0,0 @@ -../../../../extlib/html5shiv/html5shiv.js
\ 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/extlib/video-js b/mediagoblin/static/js/extlib/video-js deleted file mode 120000 index 35da21ca..00000000 --- a/mediagoblin/static/js/extlib/video-js +++ /dev/null @@ -1 +0,0 @@ -../../../../extlib/video-js
\ No newline at end of file diff --git a/mediagoblin/static/js/geolocation-map.js b/mediagoblin/static/js/geolocation-map.js index de49a37d..26d94c5d 100644 --- a/mediagoblin/static/js/geolocation-map.js +++ b/mediagoblin/static/js/geolocation-map.js @@ -31,19 +31,15 @@ $(document).ready(function () { 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 = 'Map data © ' - + String(new Date().getFullYear()) - + ' OpenStreetMap contributors, CC-BY-SA.' - + ' Imaging © ' - + String(new Date().getFullYear()) - + ' <a target="_blank" href="http://mapquest.com">MapQuest</a>.'; + var mqtileAttrib = '<a id="osm_license_link">see map license</a>'; var mqtile = new L.TileLayer( mqtileUrl, {maxZoom: 18, attribution: mqtileAttrib, subdomains: '1234'}); - var location = new L.LatLng(latitude, longitude); + map.attributionControl.setPrefix(''); + var location = new L.LatLng(latitude, longitude); map.setView(location, 13).addLayer(mqtile); var marker = new L.Marker(location); 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/pdf_viewer.js b/mediagoblin/static/js/pdf_viewer.js new file mode 100644 index 00000000..79c1e708 --- /dev/null +++ b/mediagoblin/static/js/pdf_viewer.js @@ -0,0 +1,3615 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* globals PDFJS, PDFBug, FirefoxCom, Stats */ + +'use strict'; + +var DEFAULT_SCALE = 'auto'; +var DEFAULT_SCALE_DELTA = 1.1; +var UNKNOWN_SCALE = 0; +var CACHE_SIZE = 20; +var CSS_UNITS = 96.0 / 72.0; +var SCROLLBAR_PADDING = 40; +var VERTICAL_PADDING = 5; +var MIN_SCALE = 0.25; +var MAX_SCALE = 4.0; +var IMAGE_DIR = './images/'; +var SETTINGS_MEMORY = 20; +var ANNOT_MIN_SIZE = 10; +var RenderingStates = { + INITIAL: 0, + RUNNING: 1, + PAUSED: 2, + FINISHED: 3 +}; +var FindStates = { + FIND_FOUND: 0, + FIND_NOTFOUND: 1, + FIND_WRAPPED: 2, + FIND_PENDING: 3 +}; + +//#if (FIREFOX || MOZCENTRAL || B2G || GENERIC || CHROME) +//PDFJS.workerSrc = '../build/pdf.js'; +//#endif + +var mozL10n = document.mozL10n || document.webL10n; + +function getFileName(url) { + var anchor = url.indexOf('#'); + var query = url.indexOf('?'); + var end = Math.min( + anchor > 0 ? anchor : url.length, + query > 0 ? query : url.length); + return url.substring(url.lastIndexOf('/', end) + 1, end); +} + +function scrollIntoView(element, spot) { + // Assuming offsetParent is available (it's not available when viewer is in + // hidden iframe or object). We have to scroll: if the offsetParent is not set + // producing the error. See also animationStartedClosure. + var parent = element.offsetParent; + var offsetY = element.offsetTop + element.clientTop; + if (!parent) { + console.error('offsetParent is not set -- cannot scroll'); + return; + } + while (parent.clientHeight == parent.scrollHeight) { + offsetY += parent.offsetTop; + parent = parent.offsetParent; + if (!parent) + return; // no need to scroll + } + if (spot) + offsetY += spot.top; + parent.scrollTop = offsetY; +} + +var Cache = function cacheCache(size) { + var data = []; + this.push = function cachePush(view) { + var i = data.indexOf(view); + if (i >= 0) + data.splice(i); + data.push(view); + if (data.length > size) + data.shift().destroy(); + }; +}; + +var ProgressBar = (function ProgressBarClosure() { + + function clamp(v, min, max) { + return Math.min(Math.max(v, min), max); + } + + function ProgressBar(id, opts) { + + // Fetch the sub-elements for later + this.div = document.querySelector(id + ' .progress'); + + // Get options, with sensible defaults + this.height = opts.height || 100; + this.width = opts.width || 100; + this.units = opts.units || '%'; + + // Initialize heights + this.div.style.height = this.height + this.units; + } + + ProgressBar.prototype = { + + updateBar: function ProgressBar_updateBar() { + if (this._indeterminate) { + this.div.classList.add('indeterminate'); + return; + } + + var progressSize = this.width * this._percent / 100; + + if (this._percent > 95) + this.div.classList.add('full'); + else + this.div.classList.remove('full'); + this.div.classList.remove('indeterminate'); + + this.div.style.width = progressSize + this.units; + }, + + get percent() { + return this._percent; + }, + + set percent(val) { + this._indeterminate = isNaN(val); + this._percent = clamp(val, 0, 100); + this.updateBar(); + } + }; + + return ProgressBar; +})(); + +//#if FIREFOX || MOZCENTRAL +//#include firefoxcom.js +//#endif + +// Settings Manager - This is a utility for saving settings +// First we see if localStorage is available +// If not, we use FUEL in FF +// Use asyncStorage for B2G +var Settings = (function SettingsClosure() { +//#if !(FIREFOX || MOZCENTRAL || B2G) + var isLocalStorageEnabled = (function localStorageEnabledTest() { + // Feature test as per http://diveintohtml5.info/storage.html + // The additional localStorage call is to get around a FF quirk, see + // bug #495747 in bugzilla + try { + return 'localStorage' in window && window['localStorage'] !== null && + localStorage; + } catch (e) { + return false; + } + })(); +//#endif + + function Settings(fingerprint) { + this.fingerprint = fingerprint; + this.initializedPromise = new PDFJS.Promise(); + + var resolvePromise = (function settingsResolvePromise(db) { + this.initialize(db || '{}'); + this.initializedPromise.resolve(); + }).bind(this); + +//#if B2G +// asyncStorage.getItem('database', resolvePromise); +//#endif + +//#if FIREFOX || MOZCENTRAL +// resolvePromise(FirefoxCom.requestSync('getDatabase', null)); +//#endif + +//#if !(FIREFOX || MOZCENTRAL || B2G) + if (isLocalStorageEnabled) + resolvePromise(localStorage.getItem('database')); +//#endif + } + + Settings.prototype = { + initialize: function settingsInitialize(database) { + database = JSON.parse(database); + if (!('files' in database)) + database.files = []; + if (database.files.length >= SETTINGS_MEMORY) + database.files.shift(); + var index; + for (var i = 0, length = database.files.length; i < length; i++) { + var branch = database.files[i]; + if (branch.fingerprint == this.fingerprint) { + index = i; + break; + } + } + if (typeof index != 'number') + index = database.files.push({fingerprint: this.fingerprint}) - 1; + this.file = database.files[index]; + this.database = database; + }, + + set: function settingsSet(name, val) { + if (!this.initializedPromise.isResolved) + return; + + var file = this.file; + file[name] = val; + var database = JSON.stringify(this.database); + +//#if B2G +// asyncStorage.setItem('database', database); +//#endif + +//#if FIREFOX || MOZCENTRAL +// FirefoxCom.requestSync('setDatabase', database); +//#endif + +//#if !(FIREFOX || MOZCENTRAL || B2G) + if (isLocalStorageEnabled) + localStorage.setItem('database', database); +//#endif + }, + + get: function settingsGet(name, defaultValue) { + if (!this.initializedPromise.isResolved) + return defaultValue; + + return this.file[name] || defaultValue; + } + }; + + return Settings; +})(); + +var cache = new Cache(CACHE_SIZE); +var currentPageNumber = 1; + +var PDFFindController = { + startedTextExtraction: false, + + extractTextPromises: [], + + // If active, find results will be highlighted. + active: false, + + // Stores the text for each page. + pageContents: [], + + pageMatches: [], + + // Currently selected match. + selected: { + pageIdx: -1, + matchIdx: -1 + }, + + // Where find algorithm currently is in the document. + offset: { + pageIdx: null, + matchIdx: null + }, + + resumePageIdx: null, + + resumeCallback: null, + + state: null, + + dirtyMatch: false, + + findTimeout: null, + + initialize: function() { + var events = [ + 'find', + 'findagain', + 'findhighlightallchange', + 'findcasesensitivitychange' + ]; + + this.handleEvent = this.handleEvent.bind(this); + + for (var i = 0; i < events.length; i++) { + window.addEventListener(events[i], this.handleEvent); + } + }, + + calcFindMatch: function(pageIndex) { + var pageContent = this.pageContents[pageIndex]; + var query = this.state.query; + var caseSensitive = this.state.caseSensitive; + var queryLen = query.length; + + if (queryLen === 0) { + // Do nothing the matches should be wiped out already. + return; + } + + if (!caseSensitive) { + pageContent = pageContent.toLowerCase(); + query = query.toLowerCase(); + } + + var matches = []; + + var matchIdx = -queryLen; + while (true) { + matchIdx = pageContent.indexOf(query, matchIdx + queryLen); + if (matchIdx === -1) { + break; + } + + matches.push(matchIdx); + } + this.pageMatches[pageIndex] = matches; + this.updatePage(pageIndex); + if (this.resumePageIdx === pageIndex) { + var callback = this.resumeCallback; + this.resumePageIdx = null; + this.resumeCallback = null; + callback(); + } + }, + + extractText: function() { + if (this.startedTextExtraction) { + return; + } + this.startedTextExtraction = true; + + this.pageContents = []; + for (var i = 0, ii = PDFView.pdfDocument.numPages; i < ii; i++) { + this.extractTextPromises.push(new PDFJS.Promise()); + } + + var self = this; + function extractPageText(pageIndex) { + PDFView.pages[pageIndex].getTextContent().then( + function textContentResolved(data) { + // Build the find string. + var bidiTexts = data.bidiTexts; + var str = ''; + + for (var i = 0; i < bidiTexts.length; i++) { + str += bidiTexts[i].str; + } + + // Store the pageContent as a string. + self.pageContents.push(str); + + self.extractTextPromises[pageIndex].resolve(pageIndex); + if ((pageIndex + 1) < PDFView.pages.length) + extractPageText(pageIndex + 1); + } + ); + } + extractPageText(0); + return this.extractTextPromise; + }, + + handleEvent: function(e) { + if (this.state === null || e.type !== 'findagain') { + this.dirtyMatch = true; + } + this.state = e.detail; + this.updateUIState(FindStates.FIND_PENDING); + + this.extractText(); + + clearTimeout(this.findTimeout); + if (e.type === 'find') { + // Only trigger the find action after 250ms of silence. + this.findTimeout = setTimeout(this.nextMatch.bind(this), 250); + } else { + this.nextMatch(); + } + }, + + updatePage: function(idx) { + var page = PDFView.pages[idx]; + + if (this.selected.pageIdx === idx) { + // If the page is selected, scroll the page into view, which triggers + // rendering the page, which adds the textLayer. Once the textLayer is + // build, it will scroll onto the selected match. + page.scrollIntoView(); + } + + if (page.textLayer) { + page.textLayer.updateMatches(); + } + }, + + nextMatch: function() { + var pages = PDFView.pages; + var previous = this.state.findPrevious; + var numPages = PDFView.pages.length; + + this.active = true; + + if (this.dirtyMatch) { + // Need to recalculate the matches, reset everything. + this.dirtyMatch = false; + this.selected.pageIdx = this.selected.matchIdx = -1; + this.offset.pageIdx = previous ? numPages - 1 : 0; + this.offset.matchIdx = null; + this.hadMatch = false; + this.resumeCallback = null; + this.resumePageIdx = null; + this.pageMatches = []; + var self = this; + + for (var i = 0; i < numPages; i++) { + // Wipe out any previous highlighted matches. + this.updatePage(i); + + // As soon as the text is extracted start finding the matches. + this.extractTextPromises[i].onData(function(pageIdx) { + // Use a timeout since all the pages may already be extracted and we + // want to start highlighting before finding all the matches. + setTimeout(function() { + self.calcFindMatch(pageIdx); + }); + }); + } + } + + // If there's no query there's no point in searching. + if (this.state.query === '') { + this.updateUIState(FindStates.FIND_FOUND); + return; + } + + // If we're waiting on a page, we return since we can't do anything else. + if (this.resumeCallback) { + return; + } + + var offset = this.offset; + // If there's already a matchIdx that means we are iterating through a + // page's matches. + if (offset.matchIdx !== null) { + var numPageMatches = this.pageMatches[offset.pageIdx].length; + if ((!previous && offset.matchIdx + 1 < numPageMatches) || + (previous && offset.matchIdx > 0)) { + // The simple case, we just have advance the matchIdx to select the next + // match on the page. + this.hadMatch = true; + offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1; + this.updateMatch(true); + return; + } + // We went beyond the current page's matches, so we advance to the next + // page. + this.advanceOffsetPage(previous); + } + // Start searching through the page. + this.nextPageMatch(); + }, + + nextPageMatch: function() { + if (this.resumePageIdx !== null) + console.error('There can only be one pending page.'); + + var matchesReady = function(matches) { + var offset = this.offset; + var numMatches = matches.length; + var previous = this.state.findPrevious; + if (numMatches) { + // There were matches for the page, so initialize the matchIdx. + this.hadMatch = true; + offset.matchIdx = previous ? numMatches - 1 : 0; + this.updateMatch(true); + } else { + // No matches attempt to search the next page. + this.advanceOffsetPage(previous); + if (offset.wrapped) { + offset.matchIdx = null; + if (!this.hadMatch) { + // No point in wrapping there were no matches. + this.updateMatch(false); + return; + } + } + // Search the next page. + this.nextPageMatch(); + } + }.bind(this); + + var pageIdx = this.offset.pageIdx; + var pageMatches = this.pageMatches; + if (!pageMatches[pageIdx]) { + // The matches aren't ready setup a callback so we can be notified, + // when they are ready. + this.resumeCallback = function() { + matchesReady(pageMatches[pageIdx]); + }; + this.resumePageIdx = pageIdx; + return; + } + // The matches are finished already. + matchesReady(pageMatches[pageIdx]); + }, + + advanceOffsetPage: function(previous) { + var offset = this.offset; + var numPages = this.extractTextPromises.length; + offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1; + offset.matchIdx = null; + if (offset.pageIdx >= numPages || offset.pageIdx < 0) { + offset.pageIdx = previous ? numPages - 1 : 0; + offset.wrapped = true; + return; + } + }, + + updateMatch: function(found) { + var state = FindStates.FIND_NOTFOUND; + var wrapped = this.offset.wrapped; + this.offset.wrapped = false; + if (found) { + var previousPage = this.selected.pageIdx; + this.selected.pageIdx = this.offset.pageIdx; + this.selected.matchIdx = this.offset.matchIdx; + state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND; + // Update the currently selected page to wipe out any selected matches. + if (previousPage !== -1 && previousPage !== this.selected.pageIdx) { + this.updatePage(previousPage); + } + } + this.updateUIState(state, this.state.findPrevious); + if (this.selected.pageIdx !== -1) { + this.updatePage(this.selected.pageIdx, true); + } + }, + + updateUIState: function(state, previous) { + if (PDFView.supportsIntegratedFind) { + FirefoxCom.request('updateFindControlState', + {result: state, findPrevious: previous}); + return; + } + PDFFindBar.updateUIState(state, previous); + } +}; + +var PDFFindBar = { + // TODO: Enable the FindBar *AFTER* the pagesPromise in the load function + // got resolved + + opened: false, + + initialize: function() { + this.bar = document.getElementById('findbar'); + this.toggleButton = document.getElementById('viewFind'); + this.findField = document.getElementById('findInput'); + this.highlightAll = document.getElementById('findHighlightAll'); + this.caseSensitive = document.getElementById('findMatchCase'); + this.findMsg = document.getElementById('findMsg'); + this.findStatusIcon = document.getElementById('findStatusIcon'); + + var self = this; + this.toggleButton.addEventListener('click', function() { + self.toggle(); + }); + + this.findField.addEventListener('input', function() { + self.dispatchEvent(''); + }); + + this.bar.addEventListener('keydown', function(evt) { + switch (evt.keyCode) { + case 13: // Enter + if (evt.target === self.findField) { + self.dispatchEvent('again', evt.shiftKey); + } + break; + case 27: // Escape + self.close(); + break; + } + }); + + document.getElementById('findPrevious').addEventListener('click', + function() { self.dispatchEvent('again', true); } + ); + + document.getElementById('findNext').addEventListener('click', function() { + self.dispatchEvent('again', false); + }); + + this.highlightAll.addEventListener('click', function() { + self.dispatchEvent('highlightallchange'); + }); + + this.caseSensitive.addEventListener('click', function() { + self.dispatchEvent('casesensitivitychange'); + }); + }, + + dispatchEvent: function(aType, aFindPrevious) { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('find' + aType, true, true, { + query: this.findField.value, + caseSensitive: this.caseSensitive.checked, + highlightAll: this.highlightAll.checked, + findPrevious: aFindPrevious + }); + return window.dispatchEvent(event); + }, + + updateUIState: function(state, previous) { + var notFound = false; + var findMsg = ''; + var status = ''; + + switch (state) { + case FindStates.FIND_FOUND: + break; + + case FindStates.FIND_PENDING: + status = 'pending'; + break; + + case FindStates.FIND_NOTFOUND: + findMsg = mozL10n.get('find_not_found', null, 'Phrase not found'); + notFound = true; + break; + + case FindStates.FIND_WRAPPED: + if (previous) { + findMsg = mozL10n.get('find_reached_top', null, + 'Reached top of document, continued from bottom'); + } else { + findMsg = mozL10n.get('find_reached_bottom', null, + 'Reached end of document, continued from top'); + } + break; + } + + if (notFound) { + this.findField.classList.add('notFound'); + } else { + this.findField.classList.remove('notFound'); + } + + this.findField.setAttribute('data-status', status); + this.findMsg.textContent = findMsg; + }, + + open: function() { + if (this.opened) return; + + this.opened = true; + this.toggleButton.classList.add('toggled'); + this.bar.classList.remove('hidden'); + this.findField.select(); + this.findField.focus(); + }, + + close: function() { + if (!this.opened) return; + + this.opened = false; + this.toggleButton.classList.remove('toggled'); + this.bar.classList.add('hidden'); + + PDFFindController.active = false; + }, + + toggle: function() { + if (this.opened) { + this.close(); + } else { + this.open(); + } + } +}; + +var PDFView = { + pages: [], + thumbnails: [], + currentScale: UNKNOWN_SCALE, + currentScaleValue: null, + initialBookmark: document.location.hash.substring(1), + startedTextExtraction: false, + pageText: [], + container: null, + thumbnailContainer: null, + initialized: false, + fellback: false, + pdfDocument: null, + sidebarOpen: false, + pageViewScroll: null, + thumbnailViewScroll: null, + isFullscreen: false, + previousScale: null, + pageRotation: 0, + mouseScrollTimeStamp: 0, + mouseScrollDelta: 0, + lastScroll: 0, + previousPageNumber: 1, + + // called once when the document is loaded + initialize: function pdfViewInitialize() { + var self = this; + var container = this.container = document.getElementById('viewerContainer'); + this.pageViewScroll = {}; + this.watchScroll(container, this.pageViewScroll, updateViewarea); + + var thumbnailContainer = this.thumbnailContainer = + document.getElementById('thumbnailView'); + this.thumbnailViewScroll = {}; + this.watchScroll(thumbnailContainer, this.thumbnailViewScroll, + this.renderHighestPriority.bind(this)); + + PDFFindBar.initialize(); + PDFFindController.initialize(); + + this.initialized = true; + container.addEventListener('scroll', function() { + self.lastScroll = Date.now(); + }, false); + }, + + getPage: function pdfViewGetPage(n) { + return this.pdfDocument.getPage(n); + }, + + // Helper function to keep track whether a div was scrolled up or down and + // then call a callback. + watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) { + state.down = true; + state.lastY = viewAreaElement.scrollTop; + viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) { + var currentY = viewAreaElement.scrollTop; + var lastY = state.lastY; + if (currentY > lastY) + state.down = true; + else if (currentY < lastY) + state.down = false; + // else do nothing and use previous value + state.lastY = currentY; + callback(); + }, true); + }, + + setScale: function pdfViewSetScale(val, resetAutoSettings, noScroll) { + if (val == this.currentScale) + return; + + var pages = this.pages; + for (var i = 0; i < pages.length; i++) + pages[i].update(val * CSS_UNITS); + + if (!noScroll && this.currentScale != val) + this.pages[this.page - 1].scrollIntoView(); + this.currentScale = val; + + var event = document.createEvent('UIEvents'); + event.initUIEvent('scalechange', false, false, window, 0); + event.scale = val; + event.resetAutoSettings = resetAutoSettings; + window.dispatchEvent(event); + }, + + parseScale: function pdfViewParseScale(value, resetAutoSettings, noScroll) { + if ('custom' == value) + return; + + var scale = parseFloat(value); + this.currentScaleValue = value; + if (scale) { + this.setScale(scale, true, noScroll); + return; + } + + var container = this.container; + var currentPage = this.pages[this.page - 1]; + if (!currentPage) { + return; + } + + var pageWidthScale = (container.clientWidth - SCROLLBAR_PADDING) / + currentPage.width * currentPage.scale / CSS_UNITS; + var pageHeightScale = (container.clientHeight - VERTICAL_PADDING) / + currentPage.height * currentPage.scale / CSS_UNITS; + switch (value) { + case 'page-actual': + scale = 1; + break; + case 'page-width': + scale = pageWidthScale; + break; + case 'page-height': + scale = pageHeightScale; + break; + case 'page-fit': + scale = Math.min(pageWidthScale, pageHeightScale); + break; + case 'auto': + scale = Math.min(1.0, pageWidthScale); + break; + } + this.setScale(scale, resetAutoSettings, noScroll); + + selectScaleOption(value); + }, + + zoomIn: function pdfViewZoomIn() { + var newScale = (this.currentScale * DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.ceil(newScale * 10) / 10; + newScale = Math.min(MAX_SCALE, newScale); + this.parseScale(newScale, true); + }, + + zoomOut: function pdfViewZoomOut() { + var newScale = (this.currentScale / DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.floor(newScale * 10) / 10; + newScale = Math.max(MIN_SCALE, newScale); + this.parseScale(newScale, true); + }, + + set page(val) { + var pages = this.pages; + var input = document.getElementById('pageNumber'); + var event = document.createEvent('UIEvents'); + event.initUIEvent('pagechange', false, false, window, 0); + + if (!(0 < val && val <= pages.length)) { + this.previousPageNumber = val; + event.pageNumber = this.page; + window.dispatchEvent(event); + return; + } + + pages[val - 1].updateStats(); + this.previousPageNumber = currentPageNumber; + currentPageNumber = val; + event.pageNumber = val; + window.dispatchEvent(event); + + // checking if the this.page was called from the updateViewarea function: + // avoiding the creation of two "set page" method (internal and public) + if (updateViewarea.inProgress) + return; + + // Avoid scrolling the first page during loading + if (this.loading && val == 1) + return; + + pages[val - 1].scrollIntoView(); + }, + + get page() { + return currentPageNumber; + }, + + get supportsPrinting() { + var canvas = document.createElement('canvas'); + var value = 'mozPrintCallback' in canvas; + // shadow + Object.defineProperty(this, 'supportsPrinting', { value: value, + enumerable: true, + configurable: true, + writable: false }); + return value; + }, + + get supportsFullscreen() { + var doc = document.documentElement; + var support = doc.requestFullscreen || doc.mozRequestFullScreen || + doc.webkitRequestFullScreen; + + // Disable fullscreen button if we're in an iframe + if (!!window.frameElement) + support = false; + + Object.defineProperty(this, 'supportsFullScreen', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + + get supportsIntegratedFind() { + var support = false; +//#if !(FIREFOX || MOZCENTRAL) +//#else +// support = FirefoxCom.requestSync('supportsIntegratedFind'); +//#endif + Object.defineProperty(this, 'supportsIntegratedFind', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + + get supportsDocumentFonts() { + var support = true; +//#if !(FIREFOX || MOZCENTRAL) +//#else +// support = FirefoxCom.requestSync('supportsDocumentFonts'); +//#endif + Object.defineProperty(this, 'supportsDocumentFonts', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + + get isHorizontalScrollbarEnabled() { + var div = document.getElementById('viewerContainer'); + return div.scrollWidth > div.clientWidth; + }, + + initPassiveLoading: function pdfViewInitPassiveLoading() { + if (!PDFView.loadingBar) { + PDFView.loadingBar = new ProgressBar('#loadingBar', {}); + } + + window.addEventListener('message', function window_message(e) { + var args = e.data; + + if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) + return; + switch (args.pdfjsLoadAction) { + case 'progress': + PDFView.progress(args.loaded / args.total); + break; + case 'complete': + if (!args.data) { + PDFView.error(mozL10n.get('loading_error', null, + 'An error occurred while loading the PDF.'), e); + break; + } + PDFView.open(args.data, 0); + break; + } + }); + FirefoxCom.requestSync('initPassiveLoading', null); + }, + + setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { + this.url = url; + try { + this.setTitle(decodeURIComponent(getFileName(url)) || url); + } catch (e) { + // decodeURIComponent may throw URIError, + // fall back to using the unprocessed url in that case + this.setTitle(url); + } + }, + + setTitle: function pdfViewSetTitle(title) { + document.title = title; +//#if B2G +// document.getElementById('activityTitle').textContent = title; +//#endif + }, + + open: function pdfViewOpen(url, scale, password) { + var parameters = {password: password}; + if (typeof url === 'string') { // URL + this.setTitleUsingUrl(url); + parameters.url = url; + } else if (url && 'byteLength' in url) { // ArrayBuffer + parameters.data = url; + } + + if (!PDFView.loadingBar) { + PDFView.loadingBar = new ProgressBar('#loadingBar', {}); + } + + this.pdfDocument = null; + var self = this; + self.loading = true; + PDFJS.getDocument(parameters).then( + function getDocumentCallback(pdfDocument) { + self.load(pdfDocument, scale); + self.loading = false; + }, + function getDocumentError(message, exception) { + if (exception && exception.name === 'PasswordException') { + if (exception.code === 'needpassword') { + var promptString = mozL10n.get('request_password', null, + 'PDF is protected by a password:'); + password = prompt(promptString); + if (password && password.length > 0) { + return PDFView.open(url, scale, password); + } + } + } + + var loadingErrorMessage = mozL10n.get('loading_error', null, + 'An error occurred while loading the PDF.'); + + if (exception && exception.name === 'InvalidPDFException') { + // change error message also for other builds + var loadingErrorMessage = mozL10n.get('invalid_file_error', null, + 'Invalid or corrupted PDF file.'); +//#if B2G +// window.alert(loadingErrorMessage); +// return window.close(); +//#endif + } + + if (exception && exception.name === 'MissingPDFException') { + // special message for missing PDF's + var loadingErrorMessage = mozL10n.get('missing_file_error', null, + 'Missing PDF file.'); + +//#if B2G +// window.alert(loadingErrorMessage); +// return window.close(); +//#endif + } + + var loadingIndicator = document.getElementById('loading'); + loadingIndicator.textContent = mozL10n.get('loading_error_indicator', + null, 'Error'); + var moreInfo = { + message: message + }; + self.error(loadingErrorMessage, moreInfo); + self.loading = false; + }, + function getDocumentProgress(progressData) { + self.progress(progressData.loaded / progressData.total); + } + ); + }, + + download: function pdfViewDownload() { + function noData() { + FirefoxCom.request('download', { originalUrl: url }); + } + var url = this.url.split('#')[0]; +//#if !(FIREFOX || MOZCENTRAL) + url += '#pdfjs.action=download'; + window.open(url, '_parent'); +//#else +// // Document isn't ready just try to download with the url. +// if (!this.pdfDocument) { +// noData(); +// return; +// } +// this.pdfDocument.getData().then( +// function getDataSuccess(data) { +// var blob = PDFJS.createBlob(data.buffer, 'application/pdf'); +// var blobUrl = window.URL.createObjectURL(blob); +// +// FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url }, +// function response(err) { +// if (err) { +// // This error won't really be helpful because it's likely the +// // fallback won't work either (or is already open). +// PDFView.error('PDF failed to download.'); +// } +// window.URL.revokeObjectURL(blobUrl); +// } +// ); +// }, +// noData // Error occurred try downloading with just the url. +// ); +//#endif + }, + + fallback: function pdfViewFallback() { +//#if !(FIREFOX || MOZCENTRAL) +// return; +//#else +// // Only trigger the fallback once so we don't spam the user with messages +// // for one PDF. +// if (this.fellback) +// return; +// this.fellback = true; +// var url = this.url.split('#')[0]; +// FirefoxCom.request('fallback', url, function response(download) { +// if (!download) +// return; +// PDFView.download(); +// }); +//#endif + }, + + navigateTo: function pdfViewNavigateTo(dest) { + if (typeof dest === 'string') + dest = this.destinations[dest]; + if (!(dest instanceof Array)) + return; // invalid destination + // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..> + var destRef = dest[0]; + var pageNumber = destRef instanceof Object ? + this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); + if (pageNumber > this.pages.length) + pageNumber = this.pages.length; + if (pageNumber) { + this.page = pageNumber; + var currentPage = this.pages[pageNumber - 1]; + if (!this.isFullscreen) { // Avoid breaking fullscreen mode. + currentPage.scrollIntoView(dest); + } + } + }, + + getDestinationHash: function pdfViewGetDestinationHash(dest) { + if (typeof dest === 'string') + return PDFView.getAnchorUrl('#' + escape(dest)); + if (dest instanceof Array) { + var destRef = dest[0]; // see navigateTo method for dest format + var pageNumber = destRef instanceof Object ? + this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : + (destRef + 1); + if (pageNumber) { + var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber); + var destKind = dest[1]; + if (typeof destKind === 'object' && 'name' in destKind && + destKind.name == 'XYZ') { + var scale = (dest[4] || this.currentScale); + pdfOpenParams += '&zoom=' + (scale * 100); + if (dest[2] || dest[3]) { + pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0); + } + } + return pdfOpenParams; + } + } + return ''; + }, + + /** + * For the firefox extension we prefix the full url on anchor links so they + * don't come up as resource:// urls and so open in new tab/window works. + * @param {String} anchor The anchor hash include the #. + */ + getAnchorUrl: function getAnchorUrl(anchor) { +//#if !(FIREFOX || MOZCENTRAL) + return anchor; +//#else +// return this.url.split('#')[0] + anchor; +//#endif + }, + + /** + * Returns scale factor for the canvas. It makes sense for the HiDPI displays. + * @return {Object} The object with horizontal (sx) and vertical (sy) + scales. The scaled property is set to false if scaling is + not required, true otherwise. + */ + getOutputScale: function pdfViewGetOutputDPI() { + var pixelRatio = 'devicePixelRatio' in window ? window.devicePixelRatio : 1; + return { + sx: pixelRatio, + sy: pixelRatio, + scaled: pixelRatio != 1 + }; + }, + + /** + * Show the error box. + * @param {String} message A message that is human readable. + * @param {Object} moreInfo (optional) Further information about the error + * that is more technical. Should have a 'message' + * and optionally a 'stack' property. + */ + error: function pdfViewError(message, moreInfo) { + var moreInfoText = mozL10n.get('error_version_info', + {version: PDFJS.version || '?', build: PDFJS.build || '?'}, + 'PDF.js v{{version}} (build: {{build}})') + '\n'; + if (moreInfo) { + moreInfoText += + mozL10n.get('error_message', {message: moreInfo.message}, + 'Message: {{message}}'); + if (moreInfo.stack) { + moreInfoText += '\n' + + mozL10n.get('error_stack', {stack: moreInfo.stack}, + 'Stack: {{stack}}'); + } else { + if (moreInfo.filename) { + moreInfoText += '\n' + + mozL10n.get('error_file', {file: moreInfo.filename}, + 'File: {{file}}'); + } + if (moreInfo.lineNumber) { + moreInfoText += '\n' + + mozL10n.get('error_line', {line: moreInfo.lineNumber}, + 'Line: {{line}}'); + } + } + } + + var loadingBox = document.getElementById('loadingBox'); + loadingBox.setAttribute('hidden', 'true'); + +//#if !(FIREFOX || MOZCENTRAL) + var errorWrapper = document.getElementById('errorWrapper'); + errorWrapper.removeAttribute('hidden'); + + var errorMessage = document.getElementById('errorMessage'); + errorMessage.textContent = message; + + var closeButton = document.getElementById('errorClose'); + closeButton.onclick = function() { + errorWrapper.setAttribute('hidden', 'true'); + }; + + var errorMoreInfo = document.getElementById('errorMoreInfo'); + var moreInfoButton = document.getElementById('errorShowMore'); + var lessInfoButton = document.getElementById('errorShowLess'); + moreInfoButton.onclick = function() { + errorMoreInfo.removeAttribute('hidden'); + moreInfoButton.setAttribute('hidden', 'true'); + lessInfoButton.removeAttribute('hidden'); + }; + lessInfoButton.onclick = function() { + errorMoreInfo.setAttribute('hidden', 'true'); + moreInfoButton.removeAttribute('hidden'); + lessInfoButton.setAttribute('hidden', 'true'); + }; + moreInfoButton.removeAttribute('hidden'); + lessInfoButton.setAttribute('hidden', 'true'); + errorMoreInfo.value = moreInfoText; + + errorMoreInfo.rows = moreInfoText.split('\n').length - 1; +//#else +// console.error(message + '\n' + moreInfoText); +// this.fallback(); +//#endif + }, + + progress: function pdfViewProgress(level) { + var percent = Math.round(level * 100); + PDFView.loadingBar.percent = percent; + }, + + load: function pdfViewLoad(pdfDocument, scale) { + function bindOnAfterDraw(pageView, thumbnailView) { + // when page is painted, using the image as thumbnail base + pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { + thumbnailView.setImage(pageView.canvas); + }; + } + + this.pdfDocument = pdfDocument; + + var errorWrapper = document.getElementById('errorWrapper'); + errorWrapper.setAttribute('hidden', 'true'); + + var loadingBox = document.getElementById('loadingBox'); + loadingBox.setAttribute('hidden', 'true'); + var loadingIndicator = document.getElementById('loading'); + loadingIndicator.textContent = ''; + + var thumbsView = document.getElementById('thumbnailView'); + thumbsView.parentNode.scrollTop = 0; + + while (thumbsView.hasChildNodes()) + thumbsView.removeChild(thumbsView.lastChild); + + if ('_loadingInterval' in thumbsView) + clearInterval(thumbsView._loadingInterval); + + var container = document.getElementById('viewer'); + while (container.hasChildNodes()) + container.removeChild(container.lastChild); + + var pagesCount = pdfDocument.numPages; + var id = pdfDocument.fingerprint; + document.getElementById('numPages').textContent = + mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}'); + document.getElementById('pageNumber').max = pagesCount; + + PDFView.documentFingerprint = id; + var store = PDFView.store = new Settings(id); + + this.pageRotation = 0; + + var pages = this.pages = []; + this.pageText = []; + this.startedTextExtraction = false; + var pagesRefMap = this.pagesRefMap = {}; + var thumbnails = this.thumbnails = []; + + var pagesPromise = new PDFJS.Promise(); + var self = this; + + var firstPagePromise = pdfDocument.getPage(1); + + // Fetch a single page so we can get a viewport that will be the default + // viewport for all pages + firstPagePromise.then(function(pdfPage) { + var viewport = pdfPage.getViewport(scale || 1.0); + var pagePromises = []; + for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { + var viewportClone = viewport.clone(); + var pageView = new PageView(container, pageNum, scale, + self.navigateTo.bind(self), + viewportClone); + var thumbnailView = new ThumbnailView(thumbsView, pageNum, + viewportClone); + bindOnAfterDraw(pageView, thumbnailView); + pages.push(pageView); + thumbnails.push(thumbnailView); + } + + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('documentload', true, true, {}); + window.dispatchEvent(event); + + for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { + var pagePromise = pdfDocument.getPage(pageNum); + pagePromise.then(function(pdfPage) { + var pageNum = pdfPage.pageNumber; + var pageView = pages[pageNum - 1]; + if (!pageView.pdfPage) { + // The pdfPage might already be set if we've already entered + // pageView.draw() + pageView.setPdfPage(pdfPage); + } + var thumbnailView = thumbnails[pageNum - 1]; + if (!thumbnailView.pdfPage) { + thumbnailView.setPdfPage(pdfPage); + } + + var pageRef = pdfPage.ref; + var refStr = pageRef.num + ' ' + pageRef.gen + ' R'; + pagesRefMap[refStr] = pdfPage.pageNumber; + }); + pagePromises.push(pagePromise); + } + + PDFJS.Promise.all(pagePromises).then(function(pages) { + pagesPromise.resolve(pages); + }); + }); + + var storePromise = store.initializedPromise; + PDFJS.Promise.all([firstPagePromise, storePromise]).then(function() { + var storedHash = null; + if (store.get('exists', false)) { + var pageNum = store.get('page', '1'); + var zoom = store.get('zoom', PDFView.currentScale); + var left = store.get('scrollLeft', '0'); + var top = store.get('scrollTop', '0'); + + storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' + + left + ',' + top; + } + self.setInitialView(storedHash, scale); + }); + + pagesPromise.then(function() { + if (PDFView.supportsPrinting) { + pdfDocument.getJavaScript().then(function(javaScript) { + if (javaScript.length) { + console.warn('Warning: JavaScript is not supported'); + PDFView.fallback(); + } + // Hack to support auto printing. + var regex = /\bprint\s*\(/g; + for (var i = 0, ii = javaScript.length; i < ii; i++) { + var js = javaScript[i]; + if (js && regex.test(js)) { + setTimeout(function() { + window.print(); + }); + return; + } + } + }); + } + }); + + var destinationsPromise = pdfDocument.getDestinations(); + destinationsPromise.then(function(destinations) { + self.destinations = destinations; + }); + + // outline depends on destinations and pagesRefMap + var promises = [pagesPromise, destinationsPromise, + PDFView.animationStartedPromise]; + PDFJS.Promise.all(promises).then(function() { + pdfDocument.getOutline().then(function(outline) { + self.outline = new DocumentOutlineView(outline); + }); + + // Make all navigation keys work on document load, + // unless the viewer is embedded in another page. + if (window.parent.location === window.location) { + PDFView.container.focus(); + } + }); + + pdfDocument.getMetadata().then(function(data) { + var info = data.info, metadata = data.metadata; + self.documentInfo = info; + self.metadata = metadata; + + // Provides some basic debug information + console.log('PDF ' + pdfDocument.fingerprint + ' [' + + info.PDFFormatVersion + ' ' + (info.Producer || '-') + + ' / ' + (info.Creator || '-') + ']' + + (PDFJS.version ? ' (PDF.js: ' + PDFJS.version + ')' : '')); + + var pdfTitle; + if (metadata) { + if (metadata.has('dc:title')) + pdfTitle = metadata.get('dc:title'); + } + + if (!pdfTitle && info && info['Title']) + pdfTitle = info['Title']; + + if (pdfTitle) + self.setTitle(pdfTitle + ' - ' + document.title); + + if (info.IsAcroFormPresent) { + console.warn('Warning: AcroForm/XFA is not supported'); + PDFView.fallback(); + } + }); + }, + + setInitialView: function pdfViewSetInitialView(storedHash, scale) { + // Reset the current scale, as otherwise the page's scale might not get + // updated if the zoom level stayed the same. + this.currentScale = 0; + this.currentScaleValue = null; + if (this.initialBookmark) { + this.setHash(this.initialBookmark); + this.initialBookmark = null; + } + else if (storedHash) + this.setHash(storedHash); + else if (scale) { + this.parseScale(scale, true); + this.page = 1; + } + + if (PDFView.currentScale === UNKNOWN_SCALE) { + // Scale was not initialized: invalid bookmark or scale was not specified. + // Setting the default one. + this.parseScale(DEFAULT_SCALE, true); + } + }, + + renderHighestPriority: function pdfViewRenderHighestPriority() { + // Pages have a higher priority than thumbnails, so check them first. + var visiblePages = this.getVisiblePages(); + var pageView = this.getHighestPriority(visiblePages, this.pages, + this.pageViewScroll.down); + if (pageView) { + this.renderView(pageView, 'page'); + return; + } + // No pages needed rendering so check thumbnails. + if (this.sidebarOpen) { + var visibleThumbs = this.getVisibleThumbs(); + var thumbView = this.getHighestPriority(visibleThumbs, + this.thumbnails, + this.thumbnailViewScroll.down); + if (thumbView) + this.renderView(thumbView, 'thumbnail'); + } + }, + + getHighestPriority: function pdfViewGetHighestPriority(visible, views, + scrolledDown) { + // The state has changed figure out which page has the highest priority to + // render next (if any). + // Priority: + // 1 visible pages + // 2 if last scrolled down page after the visible pages + // 2 if last scrolled up page before the visible pages + var visibleViews = visible.views; + + var numVisible = visibleViews.length; + if (numVisible === 0) { + return false; + } + for (var i = 0; i < numVisible; ++i) { + var view = visibleViews[i].view; + if (!this.isViewFinished(view)) + return view; + } + + // All the visible views have rendered, try to render next/previous pages. + if (scrolledDown) { + var nextPageIndex = visible.last.id; + // ID's start at 1 so no need to add 1. + if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) + return views[nextPageIndex]; + } else { + var previousPageIndex = visible.first.id - 2; + if (views[previousPageIndex] && + !this.isViewFinished(views[previousPageIndex])) + return views[previousPageIndex]; + } + // Everything that needs to be rendered has been. + return false; + }, + + isViewFinished: function pdfViewNeedsRendering(view) { + return view.renderingState === RenderingStates.FINISHED; + }, + + // Render a page or thumbnail view. This calls the appropriate function based + // on the views state. If the view is already rendered it will return false. + renderView: function pdfViewRender(view, type) { + var state = view.renderingState; + switch (state) { + case RenderingStates.FINISHED: + return false; + case RenderingStates.PAUSED: + PDFView.highestPriorityPage = type + view.id; + view.resume(); + break; + case RenderingStates.RUNNING: + PDFView.highestPriorityPage = type + view.id; + break; + case RenderingStates.INITIAL: + PDFView.highestPriorityPage = type + view.id; + view.draw(this.renderHighestPriority.bind(this)); + break; + } + return true; + }, + + setHash: function pdfViewSetHash(hash) { + if (!hash) + return; + + if (hash.indexOf('=') >= 0) { + var params = PDFView.parseQueryString(hash); + // borrowing syntax from "Parameters for Opening PDF Files" + if ('nameddest' in params) { + PDFView.navigateTo(params.nameddest); + return; + } + if ('page' in params) { + var pageNumber = (params.page | 0) || 1; + if ('zoom' in params) { + var zoomArgs = params.zoom.split(','); // scale,left,top + // building destination array + + // If the zoom value, it has to get divided by 100. If it is a string, + // it should stay as it is. + var zoomArg = zoomArgs[0]; + var zoomArgNumber = parseFloat(zoomArg); + if (zoomArgNumber) + zoomArg = zoomArgNumber / 100; + + var dest = [null, {name: 'XYZ'}, + zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null, + zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null, + zoomArg]; + var currentPage = this.pages[pageNumber - 1]; + currentPage.scrollIntoView(dest); + } else { + this.page = pageNumber; // simple page + } + } + if ('pagemode' in params) { + var toggle = document.getElementById('sidebarToggle'); + if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks') { + if (!this.sidebarOpen) { + toggle.click(); + } + this.switchSidebarView(params.pagemode === 'thumbs' ? + 'thumbs' : 'outline'); + } else if (params.pagemode === 'none' && this.sidebarOpen) { + toggle.click(); + } + } + } else if (/^\d+$/.test(hash)) // page number + this.page = hash; + else // named destination + PDFView.navigateTo(unescape(hash)); + }, + + switchSidebarView: function pdfViewSwitchSidebarView(view) { + var thumbsView = document.getElementById('thumbnailView'); + var outlineView = document.getElementById('outlineView'); + + var thumbsButton = document.getElementById('viewThumbnail'); + var outlineButton = document.getElementById('viewOutline'); + + switch (view) { + case 'thumbs': + var wasOutlineViewVisible = thumbsView.classList.contains('hidden'); + + thumbsButton.classList.add('toggled'); + outlineButton.classList.remove('toggled'); + thumbsView.classList.remove('hidden'); + outlineView.classList.add('hidden'); + + PDFView.renderHighestPriority(); + + if (wasOutlineViewVisible) { + // Ensure that the thumbnail of the current page is visible + // when switching from the outline view. + scrollIntoView(document.getElementById('thumbnailContainer' + + this.page)); + } + break; + + case 'outline': + thumbsButton.classList.remove('toggled'); + outlineButton.classList.add('toggled'); + thumbsView.classList.add('hidden'); + outlineView.classList.remove('hidden'); + + if (outlineButton.getAttribute('disabled')) + return; + break; + } + }, + + getVisiblePages: function pdfViewGetVisiblePages() { + if (!this.isFullscreen) { + return this.getVisibleElements(this.container, this.pages, true); + } else { + // The algorithm in getVisibleElements is broken in fullscreen mode. + var visible = [], page = this.page; + var currentPage = this.pages[page - 1]; + visible.push({ id: currentPage.id, view: currentPage }); + + return { first: currentPage, last: currentPage, views: visible}; + } + }, + + getVisibleThumbs: function pdfViewGetVisibleThumbs() { + return this.getVisibleElements(this.thumbnailContainer, this.thumbnails); + }, + + // Generic helper to find out what elements are visible within a scroll pane. + getVisibleElements: function pdfViewGetVisibleElements( + scrollEl, views, sortByVisibility) { + var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight; + var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth; + + var visible = [], view; + var currentHeight, viewHeight, hiddenHeight, percentHeight; + var currentWidth, viewWidth; + for (var i = 0, ii = views.length; i < ii; ++i) { + view = views[i]; + currentHeight = view.el.offsetTop + view.el.clientTop; + viewHeight = view.el.clientHeight; + if ((currentHeight + viewHeight) < top) { + continue; + } + if (currentHeight > bottom) { + break; + } + currentWidth = view.el.offsetLeft + view.el.clientLeft; + viewWidth = view.el.clientWidth; + if ((currentWidth + viewWidth) < left || currentWidth > right) { + continue; + } + hiddenHeight = Math.max(0, top - currentHeight) + + Math.max(0, currentHeight + viewHeight - bottom); + percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0; + + visible.push({ id: view.id, y: currentHeight, + view: view, percent: percentHeight }); + } + + var first = visible[0]; + var last = visible[visible.length - 1]; + + if (sortByVisibility) { + visible.sort(function(a, b) { + var pc = a.percent - b.percent; + if (Math.abs(pc) > 0.001) { + return -pc; + } + return a.id - b.id; // ensure stability + }); + } + return {first: first, last: last, views: visible}; + }, + + // Helper function to parse query string (e.g. ?param1=value&parm2=...). + parseQueryString: function pdfViewParseQueryString(query) { + var parts = query.split('&'); + var params = {}; + for (var i = 0, ii = parts.length; i < parts.length; ++i) { + var param = parts[i].split('='); + var key = param[0]; + var value = param.length > 1 ? param[1] : null; + params[unescape(key)] = unescape(value); + } + return params; + }, + + beforePrint: function pdfViewSetupBeforePrint() { + if (!this.supportsPrinting) { + var printMessage = mozL10n.get('printing_not_supported', null, + 'Warning: Printing is not fully supported by this browser.'); + this.error(printMessage); + return; + } + + var alertNotReady = false; + if (!this.pages.length) { + alertNotReady = true; + } else { + for (var i = 0, ii = this.pages.length; i < ii; ++i) { + if (!this.pages[i].pdfPage) { + alertNotReady = true; + break; + } + } + } + if (alertNotReady) { + var notReadyMessage = mozL10n.get('printing_not_ready', null, + 'Warning: The PDF is not fully loaded for printing.'); + window.alert(notReadyMessage); + return; + } + + var body = document.querySelector('body'); + body.setAttribute('data-mozPrintCallback', true); + for (var i = 0, ii = this.pages.length; i < ii; ++i) { + this.pages[i].beforePrint(); + } + }, + + afterPrint: function pdfViewSetupAfterPrint() { + var div = document.getElementById('printContainer'); + while (div.hasChildNodes()) + div.removeChild(div.lastChild); + }, + + fullscreen: function pdfViewFullscreen() { + var isFullscreen = document.fullscreenElement || document.mozFullScreen || + document.webkitIsFullScreen; + + if (isFullscreen) { + return false; + } + + var wrapper = document.getElementById('viewerContainer'); + if (document.documentElement.requestFullscreen) { + wrapper.requestFullscreen(); + } else if (document.documentElement.mozRequestFullScreen) { + wrapper.mozRequestFullScreen(); + } else if (document.documentElement.webkitRequestFullScreen) { + wrapper.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); + } else { + return false; + } + + this.isFullscreen = true; + var currentPage = this.pages[this.page - 1]; + this.previousScale = this.currentScaleValue; + this.parseScale('page-fit', true); + + // Wait for fullscreen to take effect + setTimeout(function() { + currentPage.scrollIntoView(); + }, 0); + + this.showPresentationControls(); + return true; + }, + + exitFullscreen: function pdfViewExitFullscreen() { + this.isFullscreen = false; + this.parseScale(this.previousScale); + this.page = this.page; + this.clearMouseScrollState(); + this.hidePresentationControls(); + + // Ensure that the thumbnail of the current page is visible + // when exiting fullscreen mode. + scrollIntoView(document.getElementById('thumbnailContainer' + this.page)); + }, + + showPresentationControls: function pdfViewShowPresentationControls() { + var DELAY_BEFORE_HIDING_CONTROLS = 3000; + var wrapper = document.getElementById('viewerContainer'); + if (this.presentationControlsTimeout) { + clearTimeout(this.presentationControlsTimeout); + } else { + wrapper.classList.add('presentationControls'); + } + this.presentationControlsTimeout = setTimeout(function hideControls() { + wrapper.classList.remove('presentationControls'); + delete PDFView.presentationControlsTimeout; + }, DELAY_BEFORE_HIDING_CONTROLS); + }, + + hidePresentationControls: function pdfViewShowPresentationControls() { + if (!this.presentationControlsTimeout) { + return; + } + clearTimeout(this.presentationControlsTimeout); + delete this.presentationControlsTimeout; + + var wrapper = document.getElementById('viewerContainer'); + wrapper.classList.remove('presentationControls'); + }, + + rotatePages: function pdfViewPageRotation(delta) { + + this.pageRotation = (this.pageRotation + 360 + delta) % 360; + + for (var i = 0, l = this.pages.length; i < l; i++) { + var page = this.pages[i]; + page.update(page.scale, this.pageRotation); + } + + for (var i = 0, l = this.thumbnails.length; i < l; i++) { + var thumb = this.thumbnails[i]; + thumb.update(this.pageRotation); + } + + this.parseScale(this.currentScaleValue, true); + + this.renderHighestPriority(); + + var currentPage = this.pages[this.page - 1]; + if (!currentPage) { + return; + } + + // Wait for fullscreen to take effect + setTimeout(function() { + currentPage.scrollIntoView(); + }, 0); + }, + + /** + * This function flips the page in presentation mode if the user scrolls up + * or down with large enough motion and prevents page flipping too often. + * + * @this {PDFView} + * @param {number} mouseScrollDelta The delta value from the mouse event. + */ + mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) { + var MOUSE_SCROLL_COOLDOWN_TIME = 50; + + var currentTime = (new Date()).getTime(); + var storedTime = this.mouseScrollTimeStamp; + + // In case one page has already been flipped there is a cooldown time + // which has to expire before next page can be scrolled on to. + if (currentTime > storedTime && + currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) + return; + + // In case the user decides to scroll to the opposite direction than before + // clear the accumulated delta. + if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) || + (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) + this.clearMouseScrollState(); + + this.mouseScrollDelta += mouseScrollDelta; + + var PAGE_FLIP_THRESHOLD = 120; + if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) { + + var PageFlipDirection = { + UP: -1, + DOWN: 1 + }; + + // In fullscreen mode scroll one page at a time. + var pageFlipDirection = (this.mouseScrollDelta > 0) ? + PageFlipDirection.UP : + PageFlipDirection.DOWN; + this.clearMouseScrollState(); + var currentPage = this.page; + + // In case we are already on the first or the last page there is no need + // to do anything. + if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) || + (currentPage == this.pages.length && + pageFlipDirection == PageFlipDirection.DOWN)) + return; + + this.page += pageFlipDirection; + this.mouseScrollTimeStamp = currentTime; + } + }, + + /** + * This function clears the member attributes used with mouse scrolling in + * presentation mode. + * + * @this {PDFView} + */ + clearMouseScrollState: function pdfViewClearMouseScrollState() { + this.mouseScrollTimeStamp = 0; + this.mouseScrollDelta = 0; + } +}; + +var PageView = function pageView(container, id, scale, + navigateTo, defaultViewport) { + this.id = id; + + this.rotation = 0; + this.scale = scale || 1.0; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotate; + + this.renderingState = RenderingStates.INITIAL; + this.resume = null; + + this.textContent = null; + this.textLayer = null; + + var anchor = document.createElement('a'); + anchor.name = '' + this.id; + + var div = this.el = document.createElement('div'); + div.id = 'pageContainer' + this.id; + div.className = 'page'; + div.style.width = Math.floor(this.viewport.width) + 'px'; + div.style.height = Math.floor(this.viewport.height) + 'px'; + + container.appendChild(anchor); + container.appendChild(div); + + this.setPdfPage = function pageViewSetPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + this.viewport = pdfPage.getViewport(this.scale); + this.stats = pdfPage.stats; + this.update(); + }; + + this.destroy = function pageViewDestroy() { + this.update(); + if (this.pdfPage) { + this.pdfPage.destroy(); + } + }; + + this.update = function pageViewUpdate(scale, rotation) { + this.renderingState = RenderingStates.INITIAL; + this.resume = null; + + if (typeof rotation !== 'undefined') { + this.rotation = rotation; + } + + this.scale = scale || this.scale; + + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: this.scale, + rotation: totalRotation + }); + + div.style.width = Math.floor(this.viewport.width) + 'px'; + div.style.height = Math.floor(this.viewport.height) + 'px'; + + while (div.hasChildNodes()) + div.removeChild(div.lastChild); + div.removeAttribute('data-loaded'); + + delete this.canvas; + + this.loadingIconDiv = document.createElement('div'); + this.loadingIconDiv.className = 'loadingIcon'; + div.appendChild(this.loadingIconDiv); + }; + + Object.defineProperty(this, 'width', { + get: function PageView_getWidth() { + return this.viewport.width; + }, + enumerable: true + }); + + Object.defineProperty(this, 'height', { + get: function PageView_getHeight() { + return this.viewport.height; + }, + enumerable: true + }); + + function setupAnnotations(pdfPage, viewport) { + function bindLink(link, dest) { + link.href = PDFView.getDestinationHash(dest); + link.onclick = function pageViewSetupLinksOnclick() { + if (dest) + PDFView.navigateTo(dest); + return false; + }; + } + function createElementWithStyle(tagName, item, rect) { + if (!rect) { + rect = viewport.convertToViewportRectangle(item.rect); + rect = PDFJS.Util.normalizeRect(rect); + } + var element = document.createElement(tagName); + element.style.left = Math.floor(rect[0]) + 'px'; + element.style.top = Math.floor(rect[1]) + 'px'; + element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; + element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; + return element; + } + function createTextAnnotation(item) { + var container = document.createElement('section'); + container.className = 'annotText'; + + var rect = viewport.convertToViewportRectangle(item.rect); + rect = PDFJS.Util.normalizeRect(rect); + // sanity check because of OOo-generated PDFs + if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) { + rect[3] = rect[1] + ANNOT_MIN_SIZE; + } + if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) { + rect[2] = rect[0] + (rect[3] - rect[1]); // make it square + } + var image = createElementWithStyle('img', item, rect); + var iconName = item.name; + image.src = IMAGE_DIR + 'annotation-' + + iconName.toLowerCase() + '.svg'; + image.alt = mozL10n.get('text_annotation_type', {type: iconName}, + '[{{type}} Annotation]'); + var content = document.createElement('div'); + content.setAttribute('hidden', true); + var title = document.createElement('h1'); + var text = document.createElement('p'); + content.style.left = Math.floor(rect[2]) + 'px'; + content.style.top = Math.floor(rect[1]) + 'px'; + title.textContent = item.title; + + if (!item.content && !item.title) { + content.setAttribute('hidden', true); + } else { + var e = document.createElement('span'); + var lines = item.content.split(/(?:\r\n?|\n)/); + for (var i = 0, ii = lines.length; i < ii; ++i) { + var line = lines[i]; + e.appendChild(document.createTextNode(line)); + if (i < (ii - 1)) + e.appendChild(document.createElement('br')); + } + text.appendChild(e); + image.addEventListener('mouseover', function annotationImageOver() { + content.removeAttribute('hidden'); + }, false); + + image.addEventListener('mouseout', function annotationImageOut() { + content.setAttribute('hidden', true); + }, false); + } + + content.appendChild(title); + content.appendChild(text); + container.appendChild(image); + container.appendChild(content); + + return container; + } + + pdfPage.getAnnotations().then(function(items) { + for (var i = 0; i < items.length; i++) { + var item = items[i]; + switch (item.type) { + case 'Link': + var link = createElementWithStyle('a', item); + link.href = item.url || ''; + if (!item.url) + bindLink(link, ('dest' in item) ? item.dest : null); + div.appendChild(link); + break; + case 'Text': + var textAnnotation = createTextAnnotation(item); + if (textAnnotation) + div.appendChild(textAnnotation); + break; + } + } + }); + } + + this.getPagePoint = function pageViewGetPagePoint(x, y) { + return this.viewport.convertToPdfPoint(x, y); + }; + + this.scrollIntoView = function pageViewScrollIntoView(dest) { + if (!dest) { + scrollIntoView(div); + return; + } + + var x = 0, y = 0; + var width = 0, height = 0, widthScale, heightScale; + var scale = 0; + switch (dest[1].name) { + case 'XYZ': + x = dest[2]; + y = dest[3]; + scale = dest[4]; + // If x and/or y coordinates are not supplied, default to + // _top_ left of the page (not the obvious bottom left, + // since aligning the bottom of the intended page with the + // top of the window is rarely helpful). + x = x !== null ? x : 0; + y = y !== null ? y : this.height / this.scale; + break; + case 'Fit': + case 'FitB': + scale = 'page-fit'; + break; + case 'FitH': + case 'FitBH': + y = dest[2]; + scale = 'page-width'; + break; + case 'FitV': + case 'FitBV': + x = dest[2]; + scale = 'page-height'; + break; + case 'FitR': + x = dest[2]; + y = dest[3]; + width = dest[4] - x; + height = dest[5] - y; + widthScale = (this.container.clientWidth - SCROLLBAR_PADDING) / + width / CSS_UNITS; + heightScale = (this.container.clientHeight - SCROLLBAR_PADDING) / + height / CSS_UNITS; + scale = Math.min(widthScale, heightScale); + break; + default: + return; + } + + if (scale && scale !== PDFView.currentScale) + PDFView.parseScale(scale, true, true); + else if (PDFView.currentScale === UNKNOWN_SCALE) + PDFView.parseScale(DEFAULT_SCALE, true, true); + + var boundingRect = [ + this.viewport.convertToViewportPoint(x, y), + this.viewport.convertToViewportPoint(x + width, y + height) + ]; + setTimeout(function pageViewScrollIntoViewRelayout() { + // letting page to re-layout before scrolling + var scale = PDFView.currentScale; + var x = Math.min(boundingRect[0][0], boundingRect[1][0]); + var y = Math.min(boundingRect[0][1], boundingRect[1][1]); + var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]); + var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]); + + scrollIntoView(div, {left: x, top: y, width: width, height: height}); + }, 0); + }; + + this.getTextContent = function pageviewGetTextContent() { + if (!this.textContent) { + this.textContent = this.pdfPage.getTextContent(); + } + return this.textContent; + }; + + this.draw = function pageviewDraw(callback) { + var pdfPage = this.pdfPage; + + if (!pdfPage) { + var promise = PDFView.getPage(this.id); + promise.then(function(pdfPage) { + this.setPdfPage(pdfPage); + this.draw(callback); + }.bind(this)); + return; + } + + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); + } + + this.renderingState = RenderingStates.RUNNING; + + var canvas = document.createElement('canvas'); + canvas.id = 'page' + this.id; + div.appendChild(canvas); + this.canvas = canvas; + + var scale = this.scale, viewport = this.viewport; + var outputScale = PDFView.getOutputScale(); + canvas.width = Math.floor(viewport.width) * outputScale.sx; + canvas.height = Math.floor(viewport.height) * outputScale.sy; + + var textLayerDiv = null; + if (!PDFJS.disableTextLayer) { + textLayerDiv = document.createElement('div'); + textLayerDiv.className = 'textLayer'; + textLayerDiv.style.width = canvas.width + 'px'; + textLayerDiv.style.height = canvas.height + 'px'; + div.appendChild(textLayerDiv); + } + var textLayer = this.textLayer = + textLayerDiv ? new TextLayerBuilder(textLayerDiv, this.id - 1) : null; + + if (outputScale.scaled) { + var cssScale = 'scale(' + (1 / outputScale.sx) + ', ' + + (1 / outputScale.sy) + ')'; + CustomStyle.setProp('transform' , canvas, cssScale); + CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); + if (textLayerDiv) { + CustomStyle.setProp('transform' , textLayerDiv, cssScale); + CustomStyle.setProp('transformOrigin' , textLayerDiv, '0% 0%'); + } + } + + var ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + // TODO(mack): use data attributes to store these + ctx._scaleX = outputScale.sx; + ctx._scaleY = outputScale.sy; + if (outputScale.scaled) { + ctx.scale(outputScale.sx, outputScale.sy); + } +//#if (FIREFOX || MOZCENTRAL) +// // Checking if document fonts are used only once +// var checkIfDocumentFontsUsed = !PDFView.pdfDocument.embeddedFontsUsed; +//#endif + + // Rendering area + + var self = this; + var renderingWasReset = false; + function pageViewDrawCallback(error) { + if (renderingWasReset) { + return; + } + + self.renderingState = RenderingStates.FINISHED; + + if (self.loadingIconDiv) { + div.removeChild(self.loadingIconDiv); + delete self.loadingIconDiv; + } + +//#if (FIREFOX || MOZCENTRAL) +// if (checkIfDocumentFontsUsed && PDFView.pdfDocument.embeddedFontsUsed && +// !PDFView.supportsDocumentFonts) { +// console.error(mozL10n.get('web_fonts_disabled', null, +// 'Web fonts are disabled: unable to use embedded PDF fonts.')); +// PDFView.fallback(); +// } +//#endif + if (error) { + PDFView.error(mozL10n.get('rendering_error', null, + 'An error occurred while rendering the page.'), error); + } + + self.stats = pdfPage.stats; + self.updateStats(); + if (self.onAfterDraw) + self.onAfterDraw(); + + cache.push(self); + + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('pagerender', true, true, { + pageNumber: pdfPage.pageNumber + }); + div.dispatchEvent(event); + + callback(); + } + + var renderContext = { + canvasContext: ctx, + viewport: this.viewport, + textLayer: textLayer, + continueCallback: function pdfViewcContinueCallback(cont) { + if (self.renderingState === RenderingStates.INITIAL) { + // The page update() was called, we just need to abort any rendering. + renderingWasReset = true; + return; + } + + if (PDFView.highestPriorityPage !== 'page' + self.id) { + self.renderingState = RenderingStates.PAUSED; + self.resume = function resumeCallback() { + self.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + } + }; + this.pdfPage.render(renderContext).then( + function pdfPageRenderCallback() { + pageViewDrawCallback(null); + }, + function pdfPageRenderError(error) { + pageViewDrawCallback(error); + } + ); + + if (textLayer) { + this.getTextContent().then( + function textContentResolved(textContent) { + textLayer.setTextContent(textContent); + } + ); + } + + setupAnnotations(this.pdfPage, this.viewport); + div.setAttribute('data-loaded', true); + }; + + this.beforePrint = function pageViewBeforePrint() { + var pdfPage = this.pdfPage; + + var viewport = pdfPage.getViewport(1); + // Use the same hack we use for high dpi displays for printing to get better + // output until bug 811002 is fixed in FF. + var PRINT_OUTPUT_SCALE = 2; + var canvas = this.canvas = document.createElement('canvas'); + canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; + canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; + canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt'; + canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt'; + var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' + + (1 / PRINT_OUTPUT_SCALE) + ')'; + CustomStyle.setProp('transform' , canvas, cssScale); + CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); + + var printContainer = document.getElementById('printContainer'); + printContainer.appendChild(canvas); + + var self = this; + canvas.mozPrintCallback = function(obj) { + var ctx = obj.context; + + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE); + + var renderContext = { + canvasContext: ctx, + viewport: viewport + }; + + pdfPage.render(renderContext).then(function() { + // Tell the printEngine that rendering this canvas/page has finished. + obj.done(); + self.pdfPage.destroy(); + }, function(error) { + console.error(error); + // Tell the printEngine that rendering this canvas/page has failed. + // This will make the print proces stop. + if ('abort' in obj) + obj.abort(); + else + obj.done(); + self.pdfPage.destroy(); + }); + }; + }; + + this.updateStats = function pageViewUpdateStats() { + if (!this.stats) { + return; + } + + if (PDFJS.pdfBug && Stats.enabled) { + var stats = this.stats; + Stats.add(this.id, stats); + } + }; +}; + +var ThumbnailView = function thumbnailView(container, id, defaultViewport) { + var anchor = document.createElement('a'); + anchor.href = PDFView.getAnchorUrl('#page=' + id); + anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); + anchor.onclick = function stopNavigation() { + PDFView.page = id; + return false; + }; + + + this.pdfPage = undefined; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotate; + + this.rotation = 0; + this.pageWidth = this.viewport.width; + this.pageHeight = this.viewport.height; + this.pageRatio = this.pageWidth / this.pageHeight; + this.id = id; + + this.canvasWidth = 98; + this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; + this.scale = (this.canvasWidth / this.pageWidth); + + var div = this.el = document.createElement('div'); + div.id = 'thumbnailContainer' + id; + div.className = 'thumbnail'; + + if (id === 1) { + // Highlight the thumbnail of the first page when no page number is + // specified (or exists in cache) when the document is loaded. + div.classList.add('selected'); + } + + var ring = document.createElement('div'); + ring.className = 'thumbnailSelectionRing'; + ring.style.width = this.canvasWidth + 'px'; + ring.style.height = this.canvasHeight + 'px'; + + div.appendChild(ring); + anchor.appendChild(div); + container.appendChild(anchor); + + this.hasImage = false; + this.renderingState = RenderingStates.INITIAL; + + this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + this.viewport = pdfPage.getViewport(1); + this.update(); + }; + + this.update = function thumbnailViewUpdate(rot) { + if (!this.pdfPage) { + return; + } + + if (rot !== undefined) { + this.rotation = rot; + } + + var totalRotation = (this.rotation + this.pdfPage.rotate) % 360; + this.viewport = this.viewport.clone({ + scale: 1, + rotation: totalRotation + }); + this.pageWidth = this.viewport.width; + this.pageHeight = this.viewport.height; + this.pageRatio = this.pageWidth / this.pageHeight; + + this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; + this.scale = (this.canvasWidth / this.pageWidth); + + div.removeAttribute('data-loaded'); + ring.textContent = ''; + ring.style.width = this.canvasWidth + 'px'; + ring.style.height = this.canvasHeight + 'px'; + + this.hasImage = false; + this.renderingState = RenderingStates.INITIAL; + this.resume = null; + }; + + this.getPageDrawContext = function thumbnailViewGetPageDrawContext() { + var canvas = document.createElement('canvas'); + canvas.id = 'thumbnail' + id; + + canvas.width = this.canvasWidth; + canvas.height = this.canvasHeight; + canvas.className = 'thumbnailImage'; + canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas', + {page: id}, 'Thumbnail of Page {{page}}')); + + div.setAttribute('data-loaded', true); + + ring.appendChild(canvas); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); + ctx.restore(); + return ctx; + }; + + this.drawingRequired = function thumbnailViewDrawingRequired() { + return !this.hasImage; + }; + + this.draw = function thumbnailViewDraw(callback) { + if (!this.pdfPage) { + var promise = PDFView.getPage(this.id); + promise.then(function(pdfPage) { + this.setPdfPage(pdfPage); + this.draw(callback); + }.bind(this)); + return; + } + + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); + } + + this.renderingState = RenderingStates.RUNNING; + if (this.hasImage) { + callback(); + return; + } + + var self = this; + var ctx = this.getPageDrawContext(); + var drawViewport = this.viewport.clone({ scale: this.scale }); + var renderContext = { + canvasContext: ctx, + viewport: drawViewport, + continueCallback: function(cont) { + if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) { + self.renderingState = RenderingStates.PAUSED; + self.resume = function() { + self.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + } + }; + this.pdfPage.render(renderContext).then( + function pdfPageRenderCallback() { + self.renderingState = RenderingStates.FINISHED; + callback(); + }, + function pdfPageRenderError(error) { + self.renderingState = RenderingStates.FINISHED; + callback(); + } + ); + this.hasImage = true; + }; + + this.setImage = function thumbnailViewSetImage(img) { + if (this.hasImage || !img) + return; + this.renderingState = RenderingStates.FINISHED; + var ctx = this.getPageDrawContext(); + ctx.drawImage(img, 0, 0, img.width, img.height, + 0, 0, ctx.canvas.width, ctx.canvas.height); + + this.hasImage = true; + }; +}; + +var DocumentOutlineView = function documentOutlineView(outline) { + var outlineView = document.getElementById('outlineView'); + while (outlineView.firstChild) + outlineView.removeChild(outlineView.firstChild); + + function bindItemLink(domObj, item) { + domObj.href = PDFView.getDestinationHash(item.dest); + domObj.onclick = function documentOutlineViewOnclick(e) { + PDFView.navigateTo(item.dest); + return false; + }; + } + + if (!outline) { + var noOutline = document.createElement('div'); + noOutline.classList.add('noOutline'); + noOutline.textContent = mozL10n.get('no_outline', null, + 'No Outline Available'); + outlineView.appendChild(noOutline); + return; + } + + var queue = [{parent: outlineView, items: outline}]; + while (queue.length > 0) { + var levelData = queue.shift(); + var i, n = levelData.items.length; + for (i = 0; i < n; i++) { + var item = levelData.items[i]; + var div = document.createElement('div'); + div.className = 'outlineItem'; + var a = document.createElement('a'); + bindItemLink(a, item); + a.textContent = item.title; + div.appendChild(a); + + if (item.items.length > 0) { + var itemsDiv = document.createElement('div'); + itemsDiv.className = 'outlineItems'; + div.appendChild(itemsDiv); + queue.push({parent: itemsDiv, items: item.items}); + } + + levelData.parent.appendChild(div); + } + } +}; + +// optimised CSS custom property getter/setter +var CustomStyle = (function CustomStyleClosure() { + + // As noted on: http://www.zachstronaut.com/posts/2009/02/17/ + // animate-css-transforms-firefox-webkit.html + // in some versions of IE9 it is critical that ms appear in this list + // before Moz + var prefixes = ['ms', 'Moz', 'Webkit', 'O']; + var _cache = { }; + + function CustomStyle() { + } + + CustomStyle.getProp = function get(propName, element) { + // check cache only when no element is given + if (arguments.length == 1 && typeof _cache[propName] == 'string') { + return _cache[propName]; + } + + element = element || document.documentElement; + var style = element.style, prefixed, uPropName; + + // test standard property first + if (typeof style[propName] == 'string') { + return (_cache[propName] = propName); + } + + // capitalize + uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); + + // test vendor specific properties + for (var i = 0, l = prefixes.length; i < l; i++) { + prefixed = prefixes[i] + uPropName; + if (typeof style[prefixed] == 'string') { + return (_cache[propName] = prefixed); + } + } + + //if all fails then set to undefined + return (_cache[propName] = 'undefined'); + }; + + CustomStyle.setProp = function set(propName, element, str) { + var prop = this.getProp(propName); + if (prop != 'undefined') + element.style[prop] = str; + }; + + return CustomStyle; +})(); + +var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) { + var textLayerFrag = document.createDocumentFragment(); + + this.textLayerDiv = textLayerDiv; + this.layoutDone = false; + this.divContentDone = false; + this.pageIdx = pageIdx; + this.matches = []; + + this.beginLayout = function textLayerBuilderBeginLayout() { + this.textDivs = []; + this.textLayerQueue = []; + this.renderingDone = false; + }; + + this.endLayout = function textLayerBuilderEndLayout() { + this.layoutDone = true; + this.insertDivContent(); + }; + + this.renderLayer = function textLayerBuilderRenderLayer() { + var self = this; + var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; + var textLayerDiv = this.textLayerDiv; + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + // No point in rendering so many divs as it'd make the browser unusable + // even after the divs are rendered + var MAX_TEXT_DIVS_TO_RENDER = 100000; + if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) + return; + + for (var i = 0, ii = textDivs.length; i < ii; i++) { + var textDiv = textDivs[i]; + if ('isWhitespace' in textDiv.dataset) { + continue; + } + textLayerFrag.appendChild(textDiv); + + ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily; + var width = ctx.measureText(textDiv.textContent).width; + + if (width > 0) { + var textScale = textDiv.dataset.canvasWidth / width; + + var transform = 'scale(' + textScale + ', 1)'; + if (bidiTexts[i].dir === 'ttb') { + transform = 'rotate(90deg) ' + transform; + } + CustomStyle.setProp('transform' , textDiv, transform); + CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); + + textLayerDiv.appendChild(textDiv); + } + } + + this.renderingDone = true; + this.updateMatches(); + + textLayerDiv.appendChild(textLayerFrag); + }; + + this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() { + // Schedule renderLayout() if user has been scrolling, otherwise + // run it right away + var RENDER_DELAY = 200; // in ms + var self = this; + if (Date.now() - PDFView.lastScroll > RENDER_DELAY) { + // Render right away + this.renderLayer(); + } else { + // Schedule + if (this.renderTimer) + clearTimeout(this.renderTimer); + this.renderTimer = setTimeout(function() { + self.setupRenderLayoutTimer(); + }, RENDER_DELAY); + } + }; + + this.appendText = function textLayerBuilderAppendText(geom) { + var textDiv = document.createElement('div'); + + // vScale and hScale already contain the scaling to pixel units + var fontHeight = geom.fontSize * Math.abs(geom.vScale); + textDiv.dataset.canvasWidth = geom.canvasWidth * geom.hScale; + textDiv.dataset.fontName = geom.fontName; + + textDiv.style.fontSize = fontHeight + 'px'; + textDiv.style.fontFamily = geom.fontFamily; + textDiv.style.left = geom.x + 'px'; + textDiv.style.top = (geom.y - fontHeight) + 'px'; + + // The content of the div is set in the `setTextContent` function. + + this.textDivs.push(textDiv); + }; + + this.insertDivContent = function textLayerUpdateTextContent() { + // Only set the content of the divs once layout has finished, the content + // for the divs is available and content is not yet set on the divs. + if (!this.layoutDone || this.divContentDone || !this.textContent) + return; + + this.divContentDone = true; + + var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; + + for (var i = 0; i < bidiTexts.length; i++) { + var bidiText = bidiTexts[i]; + var textDiv = textDivs[i]; + if (!/\S/.test(bidiText.str)) { + textDiv.dataset.isWhitespace = true; + continue; + } + + textDiv.textContent = bidiText.str; + // bidiText.dir may be 'ttb' for vertical texts. + textDiv.dir = bidiText.dir === 'rtl' ? 'rtl' : 'ltr'; + } + + this.setupRenderLayoutTimer(); + }; + + this.setTextContent = function textLayerBuilderSetTextContent(textContent) { + this.textContent = textContent; + this.insertDivContent(); + }; + + this.convertMatches = function textLayerBuilderConvertMatches(matches) { + var i = 0; + var iIndex = 0; + var bidiTexts = this.textContent.bidiTexts; + var end = bidiTexts.length - 1; + var queryLen = PDFFindController.state.query.length; + + var lastDivIdx = -1; + var pos; + + var ret = []; + + // Loop over all the matches. + for (var m = 0; m < matches.length; m++) { + var matchIdx = matches[m]; + // # Calculate the begin position. + + // Loop over the divIdxs. + while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { + iIndex += bidiTexts[i].str.length; + i++; + } + + // TODO: Do proper handling here if something goes wrong. + if (i == bidiTexts.length) { + console.error('Could not find matching mapping'); + } + + var match = { + begin: { + divIdx: i, + offset: matchIdx - iIndex + } + }; + + // # Calculate the end position. + matchIdx += queryLen; + + // Somewhat same array as above, but use a > instead of >= to get the end + // position right. + while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { + iIndex += bidiTexts[i].str.length; + i++; + } + + match.end = { + divIdx: i, + offset: matchIdx - iIndex + }; + ret.push(match); + } + + return ret; + }; + + this.renderMatches = function textLayerBuilder_renderMatches(matches) { + // Early exit if there is nothing to render. + if (matches.length === 0) { + return; + } + + var bidiTexts = this.textContent.bidiTexts; + var textDivs = this.textDivs; + var prevEnd = null; + var isSelectedPage = this.pageIdx === PDFFindController.selected.pageIdx; + var selectedMatchIdx = PDFFindController.selected.matchIdx; + var highlightAll = PDFFindController.state.highlightAll; + + var infty = { + divIdx: -1, + offset: undefined + }; + + function beginText(begin, className) { + var divIdx = begin.divIdx; + var div = textDivs[divIdx]; + div.textContent = ''; + + var content = bidiTexts[divIdx].str.substring(0, begin.offset); + var node = document.createTextNode(content); + if (className) { + var isSelected = isSelectedPage && + divIdx === selectedMatchIdx; + var span = document.createElement('span'); + span.className = className + (isSelected ? ' selected' : ''); + span.appendChild(node); + div.appendChild(span); + return; + } + div.appendChild(node); + } + + function appendText(from, to, className) { + var divIdx = from.divIdx; + var div = textDivs[divIdx]; + + var content = bidiTexts[divIdx].str.substring(from.offset, to.offset); + var node = document.createTextNode(content); + if (className) { + var span = document.createElement('span'); + span.className = className; + span.appendChild(node); + div.appendChild(span); + return; + } + div.appendChild(node); + } + + function highlightDiv(divIdx, className) { + textDivs[divIdx].className = className; + } + + var i0 = selectedMatchIdx, i1 = i0 + 1, i; + + if (highlightAll) { + i0 = 0; + i1 = matches.length; + } else if (!isSelectedPage) { + // Not highlighting all and this isn't the selected page, so do nothing. + return; + } + + for (i = i0; i < i1; i++) { + var match = matches[i]; + var begin = match.begin; + var end = match.end; + + var isSelected = isSelectedPage && i === selectedMatchIdx; + var highlightSuffix = (isSelected ? ' selected' : ''); + if (isSelected) + scrollIntoView(textDivs[begin.divIdx], {top: -50}); + + // Match inside new div. + if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { + // If there was a previous div, then add the text at the end + if (prevEnd !== null) { + appendText(prevEnd, infty); + } + // clears the divs and set the content until the begin point. + beginText(begin); + } else { + appendText(prevEnd, begin); + } + + if (begin.divIdx === end.divIdx) { + appendText(begin, end, 'highlight' + highlightSuffix); + } else { + appendText(begin, infty, 'highlight begin' + highlightSuffix); + for (var n = begin.divIdx + 1; n < end.divIdx; n++) { + highlightDiv(n, 'highlight middle' + highlightSuffix); + } + beginText(end, 'highlight end' + highlightSuffix); + } + prevEnd = end; + } + + if (prevEnd) { + appendText(prevEnd, infty); + } + }; + + this.updateMatches = function textLayerUpdateMatches() { + // Only show matches, once all rendering is done. + if (!this.renderingDone) + return; + + // Clear out all matches. + var matches = this.matches; + var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; + var clearedUntilDivIdx = -1; + + // Clear out all current matches. + for (var i = 0; i < matches.length; i++) { + var match = matches[i]; + var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); + for (var n = begin; n <= match.end.divIdx; n++) { + var div = textDivs[n]; + div.textContent = bidiTexts[n].str; + div.className = ''; + } + clearedUntilDivIdx = match.end.divIdx + 1; + } + + if (!PDFFindController.active) + return; + + // Convert the matches on the page controller into the match format used + // for the textLayer. + this.matches = matches = + this.convertMatches(PDFFindController.pageMatches[this.pageIdx] || []); + + this.renderMatches(this.matches); + }; +}; + +document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) { + PDFView.initialize(); + var params = PDFView.parseQueryString(document.location.search.substring(1)); + +//#if !(FIREFOX || MOZCENTRAL) + var file = params.file || DEFAULT_URL; +//#else +//var file = window.location.toString() +//#endif + +//#if !(FIREFOX || MOZCENTRAL) + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + document.getElementById('openFile').setAttribute('hidden', 'true'); + } else { + document.getElementById('fileInput').value = null; + } +//#else +//document.getElementById('openFile').setAttribute('hidden', 'true'); +//#endif + + // Special debugging flags in the hash section of the URL. + var hash = document.location.hash.substring(1); + var hashParams = PDFView.parseQueryString(hash); + + if ('disableWorker' in hashParams) + PDFJS.disableWorker = (hashParams['disableWorker'] === 'true'); + +//#if !(FIREFOX || MOZCENTRAL) + var locale = navigator.language; + if ('locale' in hashParams) + locale = hashParams['locale']; + mozL10n.setLanguage(locale); +//#endif + + if ('textLayer' in hashParams) { + switch (hashParams['textLayer']) { + case 'off': + PDFJS.disableTextLayer = true; + break; + case 'visible': + case 'shadow': + case 'hover': + var viewer = document.getElementById('viewer'); + viewer.classList.add('textLayer-' + hashParams['textLayer']); + break; + } + } + +//#if !(FIREFOX || MOZCENTRAL) + if ('pdfBug' in hashParams) { +//#else +//if ('pdfBug' in hashParams && FirefoxCom.requestSync('pdfBugEnabled')) { +//#endif + PDFJS.pdfBug = true; + var pdfBug = hashParams['pdfBug']; + var enabled = pdfBug.split(','); + PDFBug.enable(enabled); + PDFBug.init(); + } + + if (!PDFView.supportsPrinting) { + document.getElementById('print').classList.add('hidden'); + } + + if (!PDFView.supportsFullscreen) { + document.getElementById('fullscreen').classList.add('hidden'); + } + + if (PDFView.supportsIntegratedFind) { + document.querySelector('#viewFind').classList.add('hidden'); + } + + // Listen for warnings to trigger the fallback UI. Errors should be caught + // and call PDFView.error() so we don't need to listen for those. + PDFJS.LogManager.addLogger({ + warn: function() { + PDFView.fallback(); + } + }); + + var mainContainer = document.getElementById('mainContainer'); + var outerContainer = document.getElementById('outerContainer'); + mainContainer.addEventListener('transitionend', function(e) { + if (e.target == mainContainer) { + var event = document.createEvent('UIEvents'); + event.initUIEvent('resize', false, false, window, 0); + window.dispatchEvent(event); + outerContainer.classList.remove('sidebarMoving'); + } + }, true); + + document.getElementById('sidebarToggle').addEventListener('click', + function() { + this.classList.toggle('toggled'); + outerContainer.classList.add('sidebarMoving'); + outerContainer.classList.toggle('sidebarOpen'); + PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen'); + PDFView.renderHighestPriority(); + }); + + document.getElementById('viewThumbnail').addEventListener('click', + function() { + PDFView.switchSidebarView('thumbs'); + }); + + document.getElementById('viewOutline').addEventListener('click', + function() { + PDFView.switchSidebarView('outline'); + }); + + document.getElementById('previous').addEventListener('click', + function() { + PDFView.page--; + }); + + document.getElementById('next').addEventListener('click', + function() { + PDFView.page++; + }); + + document.querySelector('.zoomIn').addEventListener('click', + function() { + PDFView.zoomIn(); + }); + + document.querySelector('.zoomOut').addEventListener('click', + function() { + PDFView.zoomOut(); + }); + + document.getElementById('fullscreen').addEventListener('click', + function() { + PDFView.fullscreen(); + }); + + document.getElementById('openFile').addEventListener('click', + function() { + document.getElementById('fileInput').click(); + }); + + document.getElementById('print').addEventListener('click', + function() { + window.print(); + }); + + document.getElementById('download').addEventListener('click', + function() { + PDFView.download(); + }); + + document.getElementById('pageNumber').addEventListener('click', + function() { + this.select(); + }); + + document.getElementById('pageNumber').addEventListener('change', + function() { + // Handle the user inputting a floating point number. + PDFView.page = (this.value | 0); + + if (this.value !== (this.value | 0).toString()) { + this.value = PDFView.page; + } + }); + + document.getElementById('scaleSelect').addEventListener('change', + function() { + PDFView.parseScale(this.value); + }); + + document.getElementById('first_page').addEventListener('click', + function() { + PDFView.page = 1; + }); + + document.getElementById('last_page').addEventListener('click', + function() { + PDFView.page = PDFView.pdfDocument.numPages; + }); + + document.getElementById('page_rotate_ccw').addEventListener('click', + function() { + PDFView.rotatePages(-90); + }); + + document.getElementById('page_rotate_cw').addEventListener('click', + function() { + PDFView.rotatePages(90); + }); + +//#if (FIREFOX || MOZCENTRAL) +//if (FirefoxCom.requestSync('getLoadingType') == 'passive') { +// PDFView.setTitleUsingUrl(file); +// PDFView.initPassiveLoading(); +// return; +//} +//#endif + +//#if !B2G + PDFView.open(file, 0); +//#endif +}, true); + +function updateViewarea() { + + if (!PDFView.initialized) + return; + var visible = PDFView.getVisiblePages(); + var visiblePages = visible.views; + if (visiblePages.length === 0) { + return; + } + + PDFView.renderHighestPriority(); + + var currentId = PDFView.page; + var firstPage = visible.first; + + for (var i = 0, ii = visiblePages.length, stillFullyVisible = false; + i < ii; ++i) { + var page = visiblePages[i]; + + if (page.percent < 100) + break; + + if (page.id === PDFView.page) { + stillFullyVisible = true; + break; + } + } + + if (!stillFullyVisible) { + currentId = visiblePages[0].id; + } + + if (!PDFView.isFullscreen) { + updateViewarea.inProgress = true; // used in "set page" + PDFView.page = currentId; + updateViewarea.inProgress = false; + } + + var currentScale = PDFView.currentScale; + var currentScaleValue = PDFView.currentScaleValue; + var normalizedScaleValue = currentScaleValue == currentScale ? + currentScale * 100 : currentScaleValue; + + var pageNumber = firstPage.id; + var pdfOpenParams = '#page=' + pageNumber; + pdfOpenParams += '&zoom=' + normalizedScaleValue; + var currentPage = PDFView.pages[pageNumber - 1]; + var topLeft = currentPage.getPagePoint(PDFView.container.scrollLeft, + (PDFView.container.scrollTop - firstPage.y)); + pdfOpenParams += ',' + Math.round(topLeft[0]) + ',' + Math.round(topLeft[1]); + + var store = PDFView.store; + store.initializedPromise.then(function() { + store.set('exists', true); + store.set('page', pageNumber); + store.set('zoom', normalizedScaleValue); + store.set('scrollLeft', Math.round(topLeft[0])); + store.set('scrollTop', Math.round(topLeft[1])); + }); + var href = PDFView.getAnchorUrl(pdfOpenParams); + document.getElementById('viewBookmark').href = href; +} + +window.addEventListener('resize', function webViewerResize(evt) { + if (PDFView.initialized && + (document.getElementById('pageWidthOption').selected || + document.getElementById('pageFitOption').selected || + document.getElementById('pageAutoOption').selected)) + PDFView.parseScale(document.getElementById('scaleSelect').value); + updateViewarea(); +}); + +window.addEventListener('hashchange', function webViewerHashchange(evt) { + PDFView.setHash(document.location.hash.substring(1)); +}); + +window.addEventListener('change', function webViewerChange(evt) { + var files = evt.target.files; + if (!files || files.length === 0) + return; + + // Read the local file into a Uint8Array. + var fileReader = new FileReader(); + fileReader.onload = function webViewerChangeFileReaderOnload(evt) { + var buffer = evt.target.result; + var uint8Array = new Uint8Array(buffer); + PDFView.open(uint8Array, 0); + }; + + var file = files[0]; + fileReader.readAsArrayBuffer(file); + PDFView.setTitleUsingUrl(file.name); + + // URL does not reflect proper document location - hiding some icons. + document.getElementById('viewBookmark').setAttribute('hidden', 'true'); + document.getElementById('download').setAttribute('hidden', 'true'); +}, true); + +function selectScaleOption(value) { + var options = document.getElementById('scaleSelect').options; + var predefinedValueFound = false; + for (var i = 0; i < options.length; i++) { + var option = options[i]; + if (option.value != value) { + option.selected = false; + continue; + } + option.selected = true; + predefinedValueFound = true; + } + return predefinedValueFound; +} + +window.addEventListener('localized', function localized(evt) { + document.getElementsByTagName('html')[0].dir = mozL10n.getDirection(); + + // Adjust the width of the zoom box to fit the content. + PDFView.animationStartedPromise.then( + function() { + var container = document.getElementById('scaleSelectContainer'); + var select = document.getElementById('scaleSelect'); + select.setAttribute('style', 'min-width: inherit;'); + var width = select.clientWidth + 8; + select.setAttribute('style', 'min-width: ' + (width + 20) + 'px;'); + container.setAttribute('style', 'min-width: ' + width + 'px; ' + + 'max-width: ' + width + 'px;'); + }); +}, true); + +window.addEventListener('scalechange', function scalechange(evt) { + var customScaleOption = document.getElementById('customScaleOption'); + customScaleOption.selected = false; + + if (!evt.resetAutoSettings && + (document.getElementById('pageWidthOption').selected || + document.getElementById('pageFitOption').selected || + document.getElementById('pageAutoOption').selected)) { + updateViewarea(); + return; + } + + var predefinedValueFound = selectScaleOption('' + evt.scale); + if (!predefinedValueFound) { + customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%'; + customScaleOption.selected = true; + } + + document.getElementById('zoom_out').disabled = (evt.scale === MIN_SCALE); + document.getElementById('zoom_in').disabled = (evt.scale === MAX_SCALE); + + updateViewarea(); +}, true); + +window.addEventListener('pagechange', function pagechange(evt) { + var page = evt.pageNumber; + if (PDFView.previousPageNumber !== page) { + document.getElementById('pageNumber').value = page; + var selected = document.querySelector('.thumbnail.selected'); + if (selected) + selected.classList.remove('selected'); + var thumbnail = document.getElementById('thumbnailContainer' + page); + thumbnail.classList.add('selected'); + var visibleThumbs = PDFView.getVisibleThumbs(); + var numVisibleThumbs = visibleThumbs.views.length; + // If the thumbnail isn't currently visible scroll it into view. + if (numVisibleThumbs > 0) { + var first = visibleThumbs.first.id; + // Account for only one thumbnail being visible. + var last = numVisibleThumbs > 1 ? + visibleThumbs.last.id : first; + if (page <= first || page >= last) + scrollIntoView(thumbnail); + } + + } + document.getElementById('previous').disabled = (page <= 1); + document.getElementById('next').disabled = (page >= PDFView.pages.length); +}, true); + +// Firefox specific event, so that we can prevent browser from zooming +window.addEventListener('DOMMouseScroll', function(evt) { + if (evt.ctrlKey) { + evt.preventDefault(); + + var ticks = evt.detail; + var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn'; + for (var i = 0, length = Math.abs(ticks); i < length; i++) + PDFView[direction](); + } else if (PDFView.isFullscreen) { + var FIREFOX_DELTA_FACTOR = -40; + PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR); + } +}, false); + +window.addEventListener('mousemove', function mousemove(evt) { + if (PDFView.isFullscreen) { + PDFView.showPresentationControls(); + } +}, false); + +window.addEventListener('mousedown', function mousedown(evt) { + if (PDFView.isFullscreen && evt.button === 0) { + // Enable clicking of links in fullscreen mode. + // Note: Only links that point to the currently loaded PDF document works. + var targetHref = evt.target.href; + var internalLink = targetHref && (targetHref.replace(/#.*$/, '') === + window.location.href.replace(/#.*$/, '')); + if (!internalLink) { + // Unless an internal link was clicked, advance a page in fullscreen mode. + evt.preventDefault(); + PDFView.page++; + } + } +}, false); + +window.addEventListener('click', function click(evt) { + if (PDFView.isFullscreen && evt.button === 0) { + // Necessary since preventDefault() in 'mousedown' won't stop + // the event propagation in all circumstances. + evt.preventDefault(); + } +}, false); + +window.addEventListener('keydown', function keydown(evt) { + var handled = false; + var cmd = (evt.ctrlKey ? 1 : 0) | + (evt.altKey ? 2 : 0) | + (evt.shiftKey ? 4 : 0) | + (evt.metaKey ? 8 : 0); + + // First, handle the key bindings that are independent whether an input + // control is selected or not. + if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) { + // either CTRL or META key with optional SHIFT. + switch (evt.keyCode) { + case 70: + if (!PDFView.supportsIntegratedFind) { + PDFFindBar.toggle(); + handled = true; + } + break; + case 61: // FF/Mac '=' + case 107: // FF '+' and '=' + case 187: // Chrome '+' + case 171: // FF with German keyboard + PDFView.zoomIn(); + handled = true; + break; + case 173: // FF/Mac '-' + case 109: // FF '-' + case 189: // Chrome '-' + PDFView.zoomOut(); + handled = true; + break; + case 48: // '0' + case 96: // '0' on Numpad of Swedish keyboard + PDFView.parseScale(DEFAULT_SCALE, true); + handled = false; // keeping it unhandled (to restore page zoom to 100%) + break; + } + } + + // CTRL or META with or without SHIFT. + if (cmd == 1 || cmd == 8 || cmd == 5 || cmd == 12) { + switch (evt.keyCode) { + case 71: // g + if (!PDFView.supportsIntegratedFind) { + PDFFindBar.dispatchEvent('again', cmd == 5 || cmd == 12); + handled = true; + } + break; + } + } + + if (handled) { + evt.preventDefault(); + return; + } + + // Some shortcuts should not get handled if a control/input element + // is selected. + var curElement = document.activeElement; + if (curElement && (curElement.tagName == 'INPUT' || + curElement.tagName == 'SELECT')) { + return; + } + var controlsElement = document.getElementById('toolbar'); + while (curElement) { + if (curElement === controlsElement && !PDFView.isFullscreen) + return; // ignoring if the 'toolbar' element is focused + curElement = curElement.parentNode; + } + + if (cmd === 0) { // no control key pressed at all. + switch (evt.keyCode) { + case 38: // up arrow + case 33: // pg up + case 8: // backspace + if (!PDFView.isFullscreen && PDFView.currentScaleValue !== 'page-fit') { + break; + } + /* in fullscreen mode */ + /* falls through */ + case 37: // left arrow + // horizontal scrolling using arrow keys + if (PDFView.isHorizontalScrollbarEnabled) { + break; + } + /* falls through */ + case 75: // 'k' + case 80: // 'p' + PDFView.page--; + handled = true; + break; + case 27: // esc key + if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) { + PDFFindBar.close(); + handled = true; + } + break; + case 40: // down arrow + case 34: // pg down + case 32: // spacebar + if (!PDFView.isFullscreen && PDFView.currentScaleValue !== 'page-fit') { + break; + } + /* falls through */ + case 39: // right arrow + // horizontal scrolling using arrow keys + if (PDFView.isHorizontalScrollbarEnabled) { + break; + } + /* falls through */ + case 74: // 'j' + case 78: // 'n' + PDFView.page++; + handled = true; + break; + + case 36: // home + if (PDFView.isFullscreen) { + PDFView.page = 1; + handled = true; + } + break; + case 35: // end + if (PDFView.isFullscreen) { + PDFView.page = PDFView.pdfDocument.numPages; + handled = true; + } + break; + + case 82: // 'r' + PDFView.rotatePages(90); + break; + } + } + + if (cmd == 4) { // shift-key + switch (evt.keyCode) { + case 82: // 'r' + PDFView.rotatePages(-90); + break; + } + } + + if (handled) { + evt.preventDefault(); + PDFView.clearMouseScrollState(); + } +}); + +window.addEventListener('beforeprint', function beforePrint(evt) { + PDFView.beforePrint(); +}); + +window.addEventListener('afterprint', function afterPrint(evt) { + PDFView.afterPrint(); +}); + +(function fullscreenClosure() { + function fullscreenChange(e) { + var isFullscreen = document.fullscreenElement || document.mozFullScreen || + document.webkitIsFullScreen; + + if (!isFullscreen) { + PDFView.exitFullscreen(); + } + } + + window.addEventListener('fullscreenchange', fullscreenChange, false); + window.addEventListener('mozfullscreenchange', fullscreenChange, false); + window.addEventListener('webkitfullscreenchange', fullscreenChange, false); +})(); + +(function animationStartedClosure() { + // The offsetParent is not set until the pdf.js iframe or object is visible. + // Waiting for first animation. + var requestAnimationFrame = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function startAtOnce(callback) { callback(); }; + PDFView.animationStartedPromise = new PDFJS.Promise(); + requestAnimationFrame(function onAnimationFrame() { + PDFView.animationStartedPromise.resolve(); + }); +})(); + +//#if B2G +//window.navigator.mozSetMessageHandler('activity', function(activity) { +// var url = activity.source.data.url; +// PDFView.open(url); +// var cancelButton = document.getElementById('activityClose'); +// cancelButton.addEventListener('click', function() { +// activity.postResult('close'); +// }); +//}); +//#endif diff --git a/mediagoblin/storage/__init__.py b/mediagoblin/storage/__init__.py index 2db4c37d..bbe134a7 100644 --- a/mediagoblin/storage/__init__.py +++ b/mediagoblin/storage/__init__.py @@ -101,10 +101,20 @@ class StorageInterface(object): def delete_file(self, filepath): """ - Delete or dereference the file at 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. - This might need to delete directories, buckets, whatever, for - cleanliness. (Be sure to avoid race conditions on that though) + :returns: True in case of success, False otherwise. """ # Subclasses should override this method. self.__raise_not_implemented() @@ -160,12 +170,13 @@ class StorageInterface(object): appropriate. """ if self.local_storage: - shutil.copy( - self.get_local_path(filepath), dest_path) + # 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: - dest_file.write(source_file.read()) + # Copy from remote storage in 4M chunks + shutil.copyfileobj(source_file, dest_file, length=4*1048576) def copy_local_to_storage(self, filename, filepath): """ @@ -177,7 +188,8 @@ class StorageInterface(object): """ with self.get_file(filepath, 'wb') as dest_file: with file(filename, 'rb') as source_file: - dest_file.write(source_file.read()) + # Copy to storage system in 4M chunks + shutil.copyfileobj(source_file, dest_file, length=4*1048576) ########### diff --git a/mediagoblin/storage/cloudfiles.py b/mediagoblin/storage/cloudfiles.py index 1b5a6363..b6e57c91 100644 --- a/mediagoblin/storage/cloudfiles.py +++ b/mediagoblin/storage/cloudfiles.py @@ -131,6 +131,43 @@ class CloudFilesStorage(StorageInterface): 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 @@ -160,6 +197,10 @@ class CloudFilesStorageObjectWrapper(): 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)) @@ -169,9 +210,12 @@ class CloudFilesStorageObjectWrapper(): 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 implemented. + Not sure we need anything here. """ pass @@ -188,3 +232,15 @@ class CloudFilesStorageObjectWrapper(): 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 index 00d6335e..3d6e0753 100644 --- a/mediagoblin/storage/filestorage.py +++ b/mediagoblin/storage/filestorage.py @@ -62,10 +62,32 @@ class BasicFileStorage(StorageInterface): return open(self._resolve_filepath(filepath), mode) def delete_file(self, filepath): - # TODO: Also delete unused directories if empty (safely, with - # checks to avoid race conditions). + """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( @@ -87,6 +109,5 @@ class BasicFileStorage(StorageInterface): directory = self._resolve_filepath(filepath[:-1]) if not os.path.exists(directory): os.makedirs(directory) - - shutil.copy( - filename, self.get_local_path(filepath)) + # This uses chunked copying of 16kb buffers (Py2.7): + shutil.copy(filename, self.get_local_path(filepath)) diff --git a/mediagoblin/submit/forms.py b/mediagoblin/submit/forms.py index bd1e904f..e9bd93fd 100644 --- a/mediagoblin/submit/forms.py +++ b/mediagoblin/submit/forms.py @@ -18,7 +18,7 @@ import wtforms from mediagoblin.tools.text import tag_length_validator -from mediagoblin.tools.translate import fake_ugettext_passthrough as _ +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ from mediagoblin.tools.licenses import licenses_as_choices diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py new file mode 100644 index 00000000..7c3b8ab3 --- /dev/null +++ b/mediagoblin/submit/lib.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/>. + +import logging +import uuid +from werkzeug.utils import secure_filename +from werkzeug.datastructures import FileStorage + +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 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 index fbe3c39c..085344fd 100644 --- a/mediagoblin/submit/routing.py +++ b/mediagoblin/submit/routing.py @@ -14,7 +14,7 @@ # You 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.routing import add_route +from mediagoblin.tools.routing import add_route add_route('mediagoblin.submit.start', '/submit/', 'mediagoblin.submit.views:submit_start') diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py index 02026f45..e964ec12 100644 --- a/mediagoblin/submit/views.py +++ b/mediagoblin/submit/views.py @@ -16,30 +16,23 @@ from mediagoblin import messages import mediagoblin.mg_globals as mg_globals -import uuid from os.path import splitext -from celery import registry -import urllib -import urllib2 import logging _log = logging.getLogger(__name__) -from werkzeug.utils import secure_filename -from werkzeug.datastructures import FileStorage -from mediagoblin.db.util import ObjectId 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.processing import mark_entry_failed -from mediagoblin.processing.task import ProcessMedia 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 @require_active_login @@ -47,12 +40,11 @@ def submit_start(request): """ First view for submitting a file. """ - submit_form = submit_forms.SubmitStartForm(request.form) + submit_form = submit_forms.SubmitStartForm(request.form, + license=request.user.license_preference) if request.method == 'POST' and submit_form.validate(): - if not ('file' in request.files - and isinstance(request.files['file'], FileStorage) - and request.files['file'].stream): + if not check_file_field(request, 'file'): submit_form.file.errors.append( _(u'You must provide a file.')) else: @@ -66,101 +58,40 @@ def submit_start(request): # create entry and save in database entry = request.db.MediaEntry() - entry.id = ObjectId() entry.media_type = unicode(media_type) entry.title = ( - unicode(request.form['title']) + unicode(submit_form.title.data) or unicode(splitext(filename)[0])) - entry.description = unicode(request.form.get('description')) + entry.description = unicode(submit_form.description.data) - entry.license = unicode(request.form.get('license', "")) or None + entry.license = unicode(submit_form.license.data) or None - entry.uploader = request.user._id + entry.uploader = request.user.id # Process the user's folksonomy "tags" entry.tags = convert_to_tag_list_of_dicts( - request.form.get('tags')) + submit_form.tags.data) # Generate a slug from the title entry.generate_slug() - # We generate this ourselves so we know what the taks 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()) - - # Now store generate the queueing related filename - queue_filepath = request.app.queue_store.get_unique_filepath( - ['media_entries', - task_id, - secure_filename(filename)]) - - # queue appropriately - queue_file = request.app.queue_store.get_file( - queue_filepath, 'wb') + queue_file = prepare_queue_task(request.app, entry, filename) with queue_file: queue_file.write(request.files['file'].stream.read()) - # Add queued filename to the entry - entry.queued_media_file = queue_filepath - - entry.queued_task_id = task_id - # Save now so we have this data before kicking off processing - entry.save(validate=True) + 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) - process_media = registry.tasks[ProcessMedia.name] - try: - process_media.apply_async( - [unicode(entry._id)], {}, - task_id=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 - - if mg_globals.app_config["push_urls"]: - feed_url = request.urlgen( - 'mediagoblin.user_pages.atom_feed', - qualified=True, - user=request.user.username) - 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 mg_globals.app_config["push_urls"]: - hubrequest = urllib2.Request(huburl, hubdata, hubheaders) - try: - hubresponse = urllib2.urlopen(hubrequest) - except urllib2.HTTPError as exc: - # This is not a big issue, the item will be fetched - # by the PuSH server next time we hit it - _log.warning( - "push url %r gave error %r", huburl, exc.code) - except urllib2.URLError as exc: - _log.warning( - "push url %r is unreachable %r", huburl, exc.reason) - + 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", @@ -183,6 +114,7 @@ def submit_start(request): {'submit_form': submit_form, 'app_config': mg_globals.app_config}) + @require_active_login def add_collection(request, media=None): """ @@ -191,34 +123,30 @@ def add_collection(request, media=None): submit_form = submit_forms.AddCollectionForm(request.form) if request.method == 'POST' and submit_form.validate(): - try: - collection = request.db.Collection() - collection.id = ObjectId() - - collection.title = unicode(request.form['title']) - - collection.description = unicode(request.form.get('description')) - 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: - messages.add_message( - request, messages.ERROR, _('You already have a collection called "%s"!' % collection.title)) - else: - collection.save(validate=True) - - add_message(request, SUCCESS, _('Collection "%s" added!' % collection.title)) + 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() - return redirect(request, "mediagoblin.user_pages.user_home", - user=request.user.username) + add_message(request, SUCCESS, + _('Collection "%s" added!') % collection.title) - except Exception as e: - raise + return redirect(request, "mediagoblin.user_pages.user_home", + user=request.user.username) return render_to_response( request, diff --git a/mediagoblin/templates/mediagoblin/admin/panel.html b/mediagoblin/templates/mediagoblin/admin/panel.html index d3c6c958..1c3c866e 100644 --- a/mediagoblin/templates/mediagoblin/admin/panel.html +++ b/mediagoblin/templates/mediagoblin/admin/panel.html @@ -42,7 +42,7 @@ </tr> {% for media_entry in processing_entries %} <tr> - <td>{{ media_entry._id }}</td> + <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> @@ -72,7 +72,7 @@ </tr> {% for media_entry in failed_entries %} <tr> - <td>{{ media_entry._id }}</td> + <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> @@ -101,10 +101,10 @@ </tr> {% for media_entry in processed_entries %} <tr> - <td>{{ media_entry._id }}</td> + <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>{{ media_entry.created.strftime("%F %R") }}</td> + <td><span title='{{ media_entry.created.strftime("%F %R") }}'>{{ timesince(media_entry.created) }}</span></td> </tr> {% endfor %} </table> diff --git a/mediagoblin/templates/mediagoblin/auth/register.html b/mediagoblin/templates/mediagoblin/auth/register.html index a2505cb9..6dff0207 100644 --- a/mediagoblin/templates/mediagoblin/auth/register.html +++ b/mediagoblin/templates/mediagoblin/auth/register.html @@ -42,4 +42,6 @@ </div> </div> </form> +<!-- Focus the username field by default --> +<script>$(document).ready(function(){$("#username").focus();});</script> {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html index e1fa0191..9c42a756 100644 --- a/mediagoblin/templates/mediagoblin/base.html +++ b/mediagoblin/templates/mediagoblin/base.html @@ -16,7 +16,10 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -#} <!doctype html> -<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"> @@ -27,68 +30,108 @@ href="{{ request.staticdirect('/css/base.css') }}"/> <link rel="shortcut icon" href="{{ request.staticdirect('/images/goblin.ico') }}" /> - <script src="{{ request.staticdirect('/js/extlib/jquery.js') }}"></script> <script type="text/javascript" - src="{{ request.staticdirect('/js/header_dropdown.js') }}"></script> - <!--[if lt IE 9]> - <script src="{{ request.staticdirect('/js/extlib/html5shiv.js') }}"></script> - <![endif]--> + 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> - {% 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 %} + {%- include "mediagoblin/bits/logo.html" -%} {% block mediagoblin_header_title %}{% endblock %} <div class="header_right"> - {% if request.user %} - <a href="{{ request.urlgen('mediagoblin.user_pages.user_home', user= request.user.username) }}">{{ request.user.username }}</a>{% trans %}'s account{% endtrans %} - (<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}log out{% endtrans %}</a>) + {%- if request.user %} {% if request.user and request.user.status == 'active' %} - <a class="button_action" href="{{ request.urlgen('mediagoblin.submit.start') }}">{% trans %}Add media{% endtrans %}</a> + <div class="button_action header_dropdown_down">▼</div> + <div class="button_action header_dropdown_up">▲</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') }}"> - {% trans %}Log in{% 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> + · + <a href="{{ request.urlgen('mediagoblin.edit.account') }}">{%- trans %}Change account settings{% endtrans -%}</a> + · + <a href="{{ request.urlgen('mediagoblin.user_pages.processing_panel', + user=request.user.username) }}"> + {%- trans %}Media processing panel{% endtrans -%} + </a> + · + <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"> - <div class="mediagoblin_content"> + <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> - {% block mediagoblin_footer %} + </div> + {%- block mediagoblin_footer %} <footer> {% trans -%} - Powered by <a href="http://mediagoblin.org">MediaGoblin</a>, a <a href="http://gnu.org/">GNU</a> project. + 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 %} - {% endblock mediagoblin_body %} - </div> + {%- endblock mediagoblin_footer %} + </div> + {%- endblock mediagoblin_body %} + {% include 'mediagoblin/bits/body-end.html' %} </body> </html> diff --git a/mediagoblin/db/sql/__init__.py b/mediagoblin/templates/mediagoblin/bits/above-content.html index 621845ba..bb7b9762 100644 --- a/mediagoblin/db/sql/__init__.py +++ b/mediagoblin/templates/mediagoblin/bits/above-content.html @@ -1,3 +1,4 @@ +{# # GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # @@ -13,3 +14,4 @@ # # You 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-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/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 index ee9d5cb7..3fbea3be 100644 --- a/mediagoblin/templates/mediagoblin/edit/attachments.html +++ b/mediagoblin/templates/mediagoblin/edit/attachments.html @@ -15,7 +15,7 @@ # You 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" %} +{%- extends "mediagoblin/base.html" %} {% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} @@ -28,37 +28,40 @@ {% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.edit.attachments', user= media.get_uploader.username, - media= media._id) }}" + media_id=media.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> <h1> - {% trans media_title=media.title -%} + {%- trans media_title=media.title -%} Editing attachments for {{ media_title }} - {%- endtrans %}</h1> + {%- endtrans -%} + </h1> <div style="text-align: center;" > - <img src="{{ request.app.public_store.file_url( - media.media_files['thumb']) }}" /> + <img src="{{ media.thumb_url }}" /> </div> {% if media.attachment_files|count %} - <h2>Attachments</h2> + <h2>{% trans %}Attachments{% endtrans %}</h2> <ul> - {% for attachment in media.attachment_files %} + {%- for attachment in media.attachment_files %} <li> <a target="_blank" href="{{ request.app.public_store.file_url( attachment['filepath']) }}"> - {{ attachment.name -}} - </a><br /> + {{- attachment.name -}} + </a> </li> - {% endfor %} + {%- endfor %} </ul> {% endif %} - <h2>Add attachment</h2> - {{ wtforms_util.render_divs(form) }} + <h2>{% trans %}Add attachment{% endtrans %}</h2> + {{- wtforms_util.render_divs(form) }} <div class="form_submit_buttons"> - <a href="{{ media.url_for_self(request.urlgen) }}">Cancel</a> - <input type="submit" value="Save changes" class="button_form" /> + <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> 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 index 3e305ae0..9a040095 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit.html +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -29,13 +29,12 @@ <form action="{{ request.urlgen('mediagoblin.edit.edit_media', user= media.get_uploader.username, - media= media._id) }}" + 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="{{ request.app.public_store.file_url( - media.media_files['thumb']) }}" /> + <img src="{{ media.thumb_url }}" /> </div> {{ wtforms_util.render_divs(form) }} <div class="form_submit_buttons"> @@ -46,4 +45,4 @@ </div> </form> -{% endblock %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/edit/edit_account.html b/mediagoblin/templates/mediagoblin/edit/edit_account.html index 9bacb157..7fe2c031 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit_account.html +++ b/mediagoblin/templates/mediagoblin/edit/edit_account.html @@ -15,7 +15,7 @@ # You 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" %} +{%- extends "mediagoblin/base.html" %} {% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} @@ -32,21 +32,31 @@ {% block mediagoblin_content %} - - <form action="{{ request.urlgen('mediagoblin.edit.account') }}?username={{ + <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 %} + {%- endtrans -%} </h1> - {{ wtforms_util.render_divs(form) }} + {{ wtforms_util.render_field_div(form.old_password) }} + {{ wtforms_util.render_field_div(form.new_password) }} + <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> -{% endblock %} + <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_profile.html b/mediagoblin/templates/mediagoblin/edit/edit_profile.html index 2b2fa4fa..163fe186 100644 --- a/mediagoblin/templates/mediagoblin/edit/edit_profile.html +++ b/mediagoblin/templates/mediagoblin/edit/edit_profile.html @@ -27,9 +27,8 @@ {% block mediagoblin_content %} - <form action="{{ request.urlgen('mediagoblin.edit.profile') }}?username={{ - user.username }}" - method="POST" enctype="multipart/form-data"> + <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 -%} diff --git a/mediagoblin/templates/mediagoblin/404.html b/mediagoblin/templates/mediagoblin/error.html index c0fe8b62..c16b650f 100644 --- a/mediagoblin/templates/mediagoblin/404.html +++ b/mediagoblin/templates/mediagoblin/error.html @@ -17,15 +17,12 @@ #} {% extends "mediagoblin/base.html" %} -{% block title %}404 — {{ super() }}{% endblock %} +{% block title %}{{err_code}} — {{ super() }}{% endblock %} {% block mediagoblin_content %} <img class="right_align" src="{{ request.staticdirect('/images/404.png') }}" - alt="{% trans %}Image of 404 goblin stressing out{% endtrans %}" /> - <h1>{% trans %}Oops!{% endtrans %}</h1> - <p>{% trans %}There doesn't seem to be a page at this address. Sorry!{% endtrans %}</p> - <p> - {%- trans %}If you're sure the address is correct, maybe the page you're looking for has been moved or deleted.{% endtrans -%} - </p> + 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/media_displays/image.html b/mediagoblin/templates/mediagoblin/media_displays/image.html index 30c2a90d..158dd67f 100644 --- a/mediagoblin/templates/mediagoblin/media_displays/image.html +++ b/mediagoblin/templates/mediagoblin/media_displays/image.html @@ -18,5 +18,12 @@ {% 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 %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/pdf.html b/mediagoblin/templates/mediagoblin/media_displays/pdf.html new file mode 100644 index 00000000..e946f3ab --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/pdf.html @@ -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/>. +#} + +{% 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 index d72de2ca..b0854c9f 100644 --- a/mediagoblin/templates/mediagoblin/media_displays/video.html +++ b/mediagoblin/templates/mediagoblin/media_displays/video.html @@ -18,44 +18,57 @@ {% extends 'mediagoblin/user_pages/media.html' %} -{% block mediagoblin_head %} +{% block mediagoblin_head -%} {{ super() }} - <script type="text/javascript" - src="{{ request.staticdirect('/js/extlib/video-js/video.js') }}"></script> - <link href="{{ request.staticdirect('/css/vjs-mg-skin.css') }}" rel="stylesheet"> -{% endblock %} + <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 %} - <div class="video-player" style="position: relative;"> - <video class="video-js vjs-mg-skin" - width="{{ media.media_data.width }}" - height="{{ media.media_data.height }}" - controls="controls" - preload="metadata" - data-setup=""> - <source src="{{ request.app.public_store.file_url( - media.media_files['webm_640']) }}" - type="video/webm; codecs="vp8, vorbis"" /> - <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> - </div> + {% 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> + <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 %} - <li><a href="{{ request.app.public_store.file_url( - media.media_files.webm_640) }}">{% trans %}WebM file (640p; VP8/Vorbis){% endtrans %}</a> </ul> {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/root.html b/mediagoblin/templates/mediagoblin/root.html index 99d3269f..529d89ef 100644 --- a/mediagoblin/templates/mediagoblin/root.html +++ b/mediagoblin/templates/mediagoblin/root.html @@ -19,17 +19,15 @@ {% 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 %} {% if request.user %} - {% if request.user.status == 'active' %} - <h1>{% trans %}Actions{% endtrans %}</h1> - <p><a href="{{ request.urlgen('mediagoblin.submit.collection') }}"> - {% trans %}Create new collection{% endtrans %} - </a></p> - <p><a href="{{ request.urlgen('mediagoblin.edit.account') }}"> - {%- trans %}Change account settings{% endtrans -%} - </a></p> - {% endif %} <h1>{% trans %}Explore{% endtrans %}</h1> {% else %} <h1>{% trans %}Hi there, welcome to this MediaGoblin site!{% endtrans %}</h1> @@ -48,4 +46,8 @@ {% endif %} <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/user_pages/collection.html b/mediagoblin/templates/mediagoblin/user_pages/collection.html index 7ea84876..5a7baadd 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/collection.html +++ b/mediagoblin/templates/mediagoblin/user_pages/collection.html @@ -44,7 +44,7 @@ {{ collection_title }} by <a href="{{ user_url }}">{{ username }}</a> {%- endtrans %} </h1> - {% if request.user and (collection.creator == request.user._id or + {% 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, @@ -56,11 +56,11 @@ <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a> {% endif %} - {%- trans collection_description=collection.description -%} <p> - {{ collection_description }} + {% autoescape False %} + {{ collection.description_html }} + {% endautoescape %} </p> - {%- endtrans %} {{ collection_gallery(request, collection_items, pagination) }} diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html index 7499c0cf..694eb979 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html +++ b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html @@ -36,14 +36,15 @@ <p class="delete_checkbox_box"> {{ form.confirm }} - <label for="{{ (form.confirm.name) }}">{{ _(form.confirm.label.text) }}</label> + {{ 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="{{ request.urlgen('mediagoblin.user_pages.user_collection', - collection=collection.slug, - user=request.user.username) }}">{% trans %}Cancel{% endtrans %}</a> + <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> diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html index f56ab5ab..dc31d90f 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html +++ b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html @@ -24,7 +24,7 @@ <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) }}" + collection_item=collection_item.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> <h1> @@ -33,24 +33,24 @@ Really remove {{ media_title }} from {{ collection_title }}? {%- endtrans %} </h1> - + <div style="text-align: center;" > - <img src="{{ request.app.public_store.file_url( - collection_item.get_media_entry.media_files['thumb']) }}" /> + <img src="{{ collection_item.get_media_entry.thumb_url }}" /> </div> - + <br /> - + <p class="delete_checkbox_box"> {{ form.confirm }} - <label for="{{ (form.confirm.name) }}">{{ _(form.confirm.label.text) }}</label> + {{ 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="{{ request.urlgen('mediagoblin.user_pages.user_collection', - collection=collection_item.in_collection.slug, - user=request.user.username) }}">{% trans %}Cancel{% endtrans %}</a> + <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> 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 %} — {{ 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/gallery.html b/mediagoblin/templates/mediagoblin/user_pages/gallery.html index e234914f..f23bb156 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/gallery.html +++ b/mediagoblin/templates/mediagoblin/user_pages/gallery.html @@ -34,16 +34,28 @@ {% 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 media - {%- endtrans %} + {% 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" %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index ac15dd2f..92c01c48 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -15,7 +15,7 @@ # You 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" %} +{%- extends "mediagoblin/base.html" %} {% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} {% from "mediagoblin/utils/pagination.html" import render_pagination %} @@ -30,15 +30,7 @@ <script type="text/javascript" src="{{ request.staticdirect('/js/keyboard_navigation.js') }}"></script> - {% if app_config['geolocation_map_visible'] %} - <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> - {% endif %} + {% template_hook("media_head") %} {% endblock mediagoblin_head %} {% block mediagoblin_content %} @@ -51,11 +43,11 @@ {%- endtrans -%} </p> {% include "mediagoblin/utils/prev_next.html" %} - <div class="media_pane"> + <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(media.media_files)) %} + 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! #} @@ -79,35 +71,34 @@ {{ media.title }} </h2> {% if request.user and - (media.uploader == request.user._id or + (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= media._id) %} + 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= media._id) %} + 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 %} - <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> + {% 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', + <form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment', user= media.get_uploader.username, - media=media._id) }}" method="POST" id="form_comment"> - <p> - {% trans %}You can use <a href="http://daringfireball.net/projects/markdown/basics">Markdown</a> for formatting.{% endtrans %} - </p> + 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" /> @@ -115,95 +106,111 @@ </div> </form> {% endif %} + <ul style="list-style:none"> {% for comment in comments %} {% set comment_author = comment.get_author %} - {% if pagination.active_id == comment._id %} - <div class="comment_wrapper comment_active" id="comment-{{ comment._id }}"> - <a name="comment" id="comment"></a> - {% else %} - <div class="comment_wrapper" id="comment-{{ comment._id }}"> - {% endif %} - <div class="comment_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) }}"> - {{ comment_author.username -}} + user=comment_author.username) }}" + class="comment_authorlink"> + {{- comment_author.username -}} </a> - {% trans %}at{% endtrans %} <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"> - {{ comment.created.strftime("%I:%M%p %Y-%m-%d") }} - </a>: + 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 %} + {% autoescape False -%} {{ comment.content_html }} - {% endautoescape %} + {%- endautoescape %} </div> - </div> + </li> {% endfor %} + </ul> {{ render_pagination(request, pagination, media.url_for_self(request.urlgen)) }} {% endif %} </div> <div class="media_sidebar"> - {% trans date=media.created.strftime("%Y-%m-%d") -%} - <h3>Added on</h3> - <p>{{ date }}</p> - {%- endtrans %} + <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> + + {% 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 %} + {% if media.tags %} {% include "mediagoblin/utils/tags.html" %} {% endif %} - {% if media.collections %} - {% include "mediagoblin/utils/collections.html" %} - {% endif %} + {% include "mediagoblin/utils/collections.html" %} {% include "mediagoblin/utils/license.html" %} - {% include "mediagoblin/utils/geolocation_map.html" %} - {% include "mediagoblin/utils/exif.html" %} - - {% if media.attachment_files|count %} + + {%- if media.attachment_files|count %} <h3>{% trans %}Attachments{% endtrans %}</h3> <ul> - {% for attachment in media.attachment_files %} + {%- for attachment in media.attachment_files %} <li> <a href="{{ request.app.public_store.file_url(attachment.filepath) }}"> - {{ attachment.name }} + {{- attachment.name -}} </a> </li> - {% endfor %} + {%- endfor %} </ul> - {% endif %} - {% if app_config['allow_attachments'] + {%- endif %} + {%- if app_config['allow_attachments'] and request.user - and (media.uploader == request.user._id + and (media.uploader == request.user.id or request.user.is_admin) %} - {% if not media.attachment_files|count %} + {%- if not media.attachment_files|count %} <h3>{% trans %}Attachments{% endtrans %}</h3> - {% endif %} + {%- endif %} <p> <a href="{{ request.urlgen('mediagoblin.edit.attachments', user=media.get_uploader.username, - media=media._id) }}">{% trans %}Add attachment{% endtrans %}</a> + media_id=media.id) }}"> + {%- trans %}Add attachment{% endtrans -%} + </a> </p> - {% endif %} + {%- endif %} - {% if request.user %} - <p> - <a type="submit" href="{{ request.urlgen('mediagoblin.user_pages.media_collect', - user=media.get_uploader.username, - media=media._id) }}" class="button_action button_collect" > - </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 index a56c9cd3..b4c9671c 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media_collect.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media_collect.html @@ -24,58 +24,43 @@ src="{{ request.staticdirect('/js/collection_form_show.js') }}"></script> {% endblock %} -{% block mediagoblin_content %} +{% block title -%} + {% trans media_title=media.title -%} + Add “{{ media_title }}†to a collection + {%- endtrans %} — {{ super() }} +{%- endblock %} +{% block mediagoblin_content %} <form action="{{ request.urlgen('mediagoblin.user_pages.media_collect', user=media.get_uploader.username, - media=media._id) }}" + media_id=media.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> <h1> - {%- trans title=media.title -%} - Add {{ title }} to collection - {%- endtrans %} + {%- trans media_title=media.title -%} + Add “{{ media_title }}†to a collection + {%- endtrans -%} </h1> - + <div style="text-align: center;" > - <img src="{{ request.app.public_store.file_url( - media.media_files['thumb']) }}" /> + <img src="{{ media.thumb_url }}" /> </div> - + <br /> - - <p class="form_field_label"> - <label for="{{ (form.collection.name) }}">{{ _(form.collection.label.text) }}</label> - </p> + + {{- 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> - <h3>{% trans %}Add a new collection{% endtrans %}</h3> - - <p class="form_field_label"> - <label for="{{ (form.collection_title.name) }}">{{ _(form.collection_title.label.text) }}</label> - </p> - <div class="form_field_input"> - {{ form.collection_title }} - </div> - <p class="form_field_label"> - <label for="{{ (form.collection_description.name) }}">{{ _(form.collection_description.label.text) }}</label> - </p> - <div class="form_field_input"> - {{ form.collection_description }} - </div> - - </div> - <p class="form_field_label"> - <label for="{{ (form.note.name) }}">{{ _(form.note.label.text) }}</label> - </p> - <div class="form_field_input"> - {{ form.note }} + {{- 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 :) #} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html index a3459206..1d7dcc17 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html @@ -23,7 +23,7 @@ <form action="{{ request.urlgen('mediagoblin.user_pages.media_confirm_delete', user=media.get_uploader.username, - media=media._id) }}" + media_id=media.id) }}" method="POST" enctype="multipart/form-data"> <div class="form_box"> <h1> @@ -31,17 +31,16 @@ Really delete {{ title }}? {%- endtrans %} </h1> - + <div style="text-align: center;" > - <img src="{{ request.app.public_store.file_url( - media.media_files['thumb']) }}" /> + <img src="{{ media.thumb_url }}" /> </div> - + <br /> - + <p class="delete_checkbox_box"> {{ form.confirm }} - <label for="{{ (form.confirm.name) }}">{{ _(form.confirm.label.text) }}</label> + {{ wtforms_util.render_label(form.confirm) }} </p> <div class="form_submit_buttons"> diff --git a/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html index e673902b..2a449d45 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html +++ b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html @@ -41,7 +41,7 @@ </tr> {% for media_entry in processing_entries %} <tr> - <td>{{ media_entry._id }}</td> + <td>{{ media_entry.id }}</td> <td>{{ media_entry.title }}</td> <td>{{ media_entry.created.strftime("%F %R") }}</td> {% if media_entry.transcoding_progress %} @@ -69,7 +69,7 @@ </tr> {% for media_entry in failed_entries %} <tr> - <td>{{ media_entry._id }}</td> + <td>{{ media_entry.id }}</td> <td>{{ media_entry.title }}</td> <td>{{ media_entry.created.strftime("%F %R") }}</td> {% if media_entry.get_fail_exception() %} @@ -97,7 +97,7 @@ </tr> {% for entry in processed_entries %} <tr> - <td>{{ entry._id }}</td> + <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> diff --git a/mediagoblin/templates/mediagoblin/user_pages/user.html b/mediagoblin/templates/mediagoblin/user_pages/user.html index eb3bde48..71acd66c 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/user.html +++ b/mediagoblin/templates/mediagoblin/user_pages/user.html @@ -90,14 +90,13 @@ </h1> {% if not user.url and not user.bio %} - {% if request.user and (request.user._id == user._id) %} + {% 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') }}?username={{ - user.username }}" - class="button_action"> + <a href="{{ request.urlgen('mediagoblin.edit.profile', + user=user.username) }}" class="button_action"> {%- trans %}Edit profile{% endtrans -%} </a> {% else %} @@ -112,19 +111,25 @@ <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') }}?username={{ - user.username }}"> + (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=2) }} + pagination_base_url=user_gallery_url, col_number=3) }} {% include "mediagoblin/utils/object_gallery.html" %} <div class="clear"></div> <p> @@ -139,7 +144,7 @@ {% include "mediagoblin/utils/feed_link.html" %} </div> {% else %} - {% if request.user and (request.user._id == user._id) %} + {% if request.user and (request.user.id == user.id) %} <div class="profile_showcase empty_space"> <p> {% trans -%} diff --git a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html index a8742c74..dcc59244 100644 --- a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html +++ b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html @@ -20,37 +20,34 @@ {% macro media_grid(request, collection_items, col_number=5) %} <table class="thumb_gallery"> - {% for row in gridify_cursor(collection_items, col_number) %} + {% 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="collection_thumbnail thumb_entry + <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="{{ request.app.public_store.file_url( - media_entry.media_files['thumb']) }}" /> + <img src="{{ media_entry.thumb_url }}" /> </a> - + {% if item.note %} - {%- trans note=item.note -%} - <br /> - <a href="{{ entry_url }}">{{ note }}</a> - {%- endtrans -%} + <a href="{{ entry_url }}">{{ item.note }}</a> {% endif %} {% if request.user and - (item.in_collection.creator == request.user._id or + (item.in_collection.creator == request.user.id or request.user.is_admin) %} - {%- trans remove_url=request.urlgen( + {%- 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) -%} - <br /><a href="{{ remove_url }}" class="remove">(remove)</a> - {%- endtrans -%} + <a href="{{ remove_url }}" class="remove"> + {%- trans %}(remove){% endtrans -%} + </a> {% endif %} </td> {% endfor %} diff --git a/mediagoblin/templates/mediagoblin/utils/collections.html b/mediagoblin/templates/mediagoblin/utils/collections.html index 6cb5a342..69738e26 100644 --- a/mediagoblin/templates/mediagoblin/utils/collections.html +++ b/mediagoblin/templates/mediagoblin/utils/collections.html @@ -17,20 +17,28 @@ #} {% block collections_content -%} - <h3>{% trans collected=media.collected %}In collections ({{ collected }}){% endtrans %}</h3> - <p> - {% for collection in media.collections %} - {% if loop.last %} - {# the 'and' should only appear if there is more than one collections #} - {% if media.collections|length > 1 %} + {% if media.collections %} + <h3>{% trans %}Collected in{% endtrans %}</h3> + <p> + {%- for collection in media.collections %} + {%- if not loop.first %} · - {% endif %} - <a href="{{ collection.url_for_self(request.urlgen) }}">{{ collection['title'] }}</a> - {% elif loop.revindex == 2 %} - <a href="{{ collection.url_for_self(request.urlgen) }}">{{ collection['title'] }}</a> - {% else %} - <a href="{{ collection.url_for_self(request.urlgen) }}">{{ collection['title'] }}</a> · - {% endif %} - {% endfor %} - </p> + {%- 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/object_gallery.html b/mediagoblin/templates/mediagoblin/utils/object_gallery.html index bfd19ee6..d328b552 100644 --- a/mediagoblin/templates/mediagoblin/utils/object_gallery.html +++ b/mediagoblin/templates/mediagoblin/utils/object_gallery.html @@ -20,7 +20,7 @@ {% macro media_grid(request, media_entries, col_number=5) %} <table class="thumb_gallery"> - {% for row in gridify_cursor(media_entries, col_number) %} + {% 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 %}"> @@ -30,8 +30,7 @@ {%- if loop.first %} thumb_entry_first {%- elif loop.last %} thumb_entry_last{% endif %}"> <a href="{{ entry_url }}"> - <img src="{{ request.app.public_store.file_url( - entry.media_files['thumb']) }}" /> + <img src="{{ entry.thumb_url }}" /> </a> {% if entry.title %} <a class="thumb_entry_title" href="{{ entry_url }}">{{ entry.title }}</a> @@ -48,7 +47,7 @@ Args: - request: Request - - media_entries: pymongo cursor of media entries + - 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 diff --git a/mediagoblin/templates/mediagoblin/utils/tags.html b/mediagoblin/templates/mediagoblin/utils/tags.html index 0127035c..bb4bd1a5 100644 --- a/mediagoblin/templates/mediagoblin/utils/tags.html +++ b/mediagoblin/templates/mediagoblin/utils/tags.html @@ -26,16 +26,20 @@ · {% endif %} <a href="{{ request.urlgen( - 'mediagoblin.listings.tags_listing', - tag=tag['slug']) }}">{{ tag['name'] }}</a> + '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.listings.tags_listing', - tag=tag['slug']) }}">{{ tag['name'] }}</a> + 'mediagoblin.user_pages.user_tag_gallery', + tag=tag['slug'], + user=media.get_uploader.username) }}">{{ tag['name'] }}</a> {% else %} <a href="{{ request.urlgen( - 'mediagoblin.listings.tags_listing', - tag=tag['slug']) }}">{{ tag['name'] }}</a> · + 'mediagoblin.user_pages.user_tag_gallery', + tag=tag['slug'], + user=media.get_uploader.username) }}">{{ tag['name'] }}</a> + · {% endif %} {% endfor %} </p> diff --git a/mediagoblin/templates/mediagoblin/utils/wtforms.html b/mediagoblin/templates/mediagoblin/utils/wtforms.html index 58ecb8e0..be6976c2 100644 --- a/mediagoblin/templates/mediagoblin/utils/wtforms.html +++ b/mediagoblin/templates/mediagoblin/utils/wtforms.html @@ -16,20 +16,34 @@ # 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) %} - {% if field.label.text -%} - <p class="form_field_label"><label for="{{ field.label.field_id }}">{{ _(field.label.text) }}</label></p> - {%- endif %} + {{- 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> + <p class="form_field_error">{{ error }}</p> {% endfor %} {%- endif %} - {% if field.description -%} - <p class="form_field_description">{{ _(field.description)|safe }}</p> + {%- if field.description %} + <p class="form_field_description">{{ field.description|safe }}</p> {%- endif %} </div> {%- endmacro %} @@ -45,7 +59,7 @@ {% macro render_table(form) -%} {% for field in form %} <tr> - <th>{{ _(field.label.text) }}</th> + <th>{{ field.label.text }}</th> <td> {{field}} {% if field.errors %} diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py index 4e84914a..7a88281e 100644 --- a/mediagoblin/tests/__init__.py +++ b/mediagoblin/tests/__init__.py @@ -18,12 +18,14 @@ import os import shutil from mediagoblin import mg_globals -from mediagoblin.tests.tools import ( - TEST_USER_DEV, suicide_if_bad_celery_environ) +from mediagoblin.tests.tools import TEST_USER_DEV def setup_package(): - suicide_if_bad_celery_environ() + + import warnings + from sqlalchemy.exc import SAWarning + warnings.simplefilter("error", SAWarning) def teardown_package(): diff --git a/mediagoblin/tests/appconfig_context_modified.ini b/mediagoblin/tests/appconfig_context_modified.ini new file mode 100644 index 00000000..e93797df --- /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/test_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/test_user_dev/media/public +base_url = /mgoblin_media/ + +[storage:queuestore] +base_dir = %(here)s/test_user_dev/media/queue + +[celery] +CELERY_ALWAYS_EAGER = true +CELERY_RESULT_DBURI = "sqlite:///%(here)s/test_user_dev/celery.db" +BROKER_HOST = "sqlite:///%(here)s/test_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/test_tests.py b/mediagoblin/tests/conftest.py index 20832ac7..dbb0aa0a 100644 --- a/mediagoblin/tests/test_tests.py +++ b/mediagoblin/tests/conftest.py @@ -1,5 +1,5 @@ # GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# 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 @@ -14,25 +14,28 @@ # You 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 get_test_app +import pytest -from mediagoblin import mg_globals +from mediagoblin.tests import tools +from mediagoblin.tools.testing import _activate_testing -def test_get_test_app_wipes_db(): +@pytest.fixture() +def test_app(request): """ - Make sure we get a fresh database on every wipe :) - """ - get_test_app() - assert mg_globals.database.User.find().count() == 0 + py.test fixture to pass sandboxed mediagoblin applications into tests that + want them. - new_user = mg_globals.database.User() - new_user.username = u'lolcat' - new_user.email = u'lol@cats.example.org' - new_user.pw_hash = u'pretend_this_is_a_hash' - new_user.save() - assert mg_globals.database.User.find().count() == 1 + 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) - get_test_app() - assert mg_globals.database.User.find().count() == 0 +@pytest.fixture() +def pt_fixture_enable_testing(): + """ + py.test fixture to enable testing mode in tools. + """ + _activate_testing() 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/db/sql/fake.py b/mediagoblin/tests/resources.py index 0fd0cc41..f7b3037d 100644 --- a/mediagoblin/db/sql/fake.py +++ b/mediagoblin/tests/resources.py @@ -1,5 +1,5 @@ # GNU MediaGoblin -- federated, autonomous media hosting -# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# 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 @@ -15,31 +15,27 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -This module contains some fake classes and functions to -calm the rest of the code base. Or provide super minimal -implementations. +from pkg_resources import resource_filename -Currently: -- ObjectId "class": It's a function mostly doing - int(init_arg) to convert string primary keys into - integer primary keys. -- InvalidId exception -- DESCENDING "constant" -""" +def resource(filename): + return resource_filename('mediagoblin.tests', 'test_submission/' + filename) -DESCENDING = object() # a unique object for this "constant" +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') -class InvalidId(Exception): - pass +def resource_exif(f): + return resource_filename('mediagoblin.tests', 'test_exif/' + f) -def ObjectId(value=None): - if value is None: - return None - try: - return int(value) - except ValueError: - raise InvalidId("%r is an invalid id" % value) + +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 index 1b84b435..755727f9 100644 --- a/mediagoblin/tests/test_auth.py +++ b/mediagoblin/tests/test_auth.py @@ -17,11 +17,10 @@ import urlparse import datetime -from nose.tools import assert_equal - -from mediagoblin.auth import lib as auth_lib -from mediagoblin.tests.tools import setup_fresh_app, fixture_add_user 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 @@ -39,7 +38,6 @@ def test_bcrypt_check_password(): 'notthepassword', '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO') - # Same thing, but with extra fake salt. assert not auth_lib.bcrypt_check_password( 'notthepassword', @@ -57,7 +55,6 @@ def test_bcrypt_gen_password_hash(): 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( @@ -66,7 +63,6 @@ def test_bcrypt_gen_password_hash(): 'notthepassword', hashed_pw, '3><7R45417') -@setup_fresh_app def test_register_views(test_app): """ Massive test function that all our registration-related views all work. @@ -76,8 +72,7 @@ def test_register_views(test_app): test_app.get('/auth/register/') # Make sure it rendered with the appropriate template - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/auth/register.html') + assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT # Try to register without providing anything, should error # -------------------------------------------------------- @@ -104,10 +99,8 @@ def test_register_views(test_app): 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 6 and 30 characters long.'] + 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() @@ -118,13 +111,11 @@ def test_register_views(test_app): context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] form = context['register_form'] - assert form.username.errors == [ - u'Invalid input.'] - assert form.email.errors == [ - u'Invalid email address.'] + 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 not mg_globals.database.User.find().count() + assert User.query.count() == 0 # Successful register # ------------------- @@ -137,11 +128,8 @@ def test_register_views(test_app): response.follow() ## Did we redirect to the proper page? Use the right template? - assert_equal( - urlparse.urlsplit(response.location)[2], - '/u/happygirl/') - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/user_pages/user.html') + 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( @@ -153,7 +141,7 @@ def test_register_views(test_app): ## 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) + 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 @@ -170,7 +158,7 @@ def test_register_views(test_app): ### user should have these same parameters assert parsed_get_params['userid'] == [ - unicode(new_user._id)] + unicode(new_user.id)] assert parsed_get_params['token'] == [ new_user.verification_key] @@ -178,7 +166,7 @@ def test_register_views(test_app): template.clear_test_template_context() response = test_app.get( "/auth/verify_email/?userid=%s&token=total_bs" % unicode( - new_user._id)) + new_user.id)) response.follow() context = template.TEMPLATE_TEST_CONTEXT[ 'mediagoblin/user_pages/user.html'] @@ -231,11 +219,8 @@ def test_register_views(test_app): response.follow() ## Did we redirect to the proper page? Use the right template? - assert_equal( - urlparse.urlsplit(response.location)[2], - '/auth/login/') - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/auth/login.html') + 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 @@ -253,7 +238,7 @@ def test_register_views(test_app): # 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['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 @@ -264,8 +249,8 @@ def test_register_views(test_app): 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_equal(response.status, '404 Not Found') + 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() @@ -274,14 +259,14 @@ def test_register_views(test_app): new_user.fp_token_expire = datetime.datetime.now() new_user.save() response = test_app.get("%s?%s" % (path, get_params), status=404) - assert_equal(response.status, '404 Not Found') + 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 template.TEMPLATE_TEST_CONTEXT.has_key('mediagoblin/auth/change_fp.html') + 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() @@ -291,8 +276,7 @@ def test_register_views(test_app): 'password': 'iamveryveryhappy', 'token': parsed_get_params['token']}) response.follow() - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/auth/login.html') + 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() @@ -303,14 +287,10 @@ def test_register_views(test_app): # User should be redirected response.follow() - assert_equal( - urlparse.urlsplit(response.location)[2], - '/') - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/root.html') + assert urlparse.urlsplit(response.location)[2] == '/' + assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT -@setup_fresh_app def test_authentication_views(test_app): """ Test logging in and logging out @@ -321,8 +301,7 @@ def test_authentication_views(test_app): # Get login # --------- test_app.get('/auth/login/') - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/auth/login.html') + assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT # Failed login - blank form # ------------------------- @@ -369,7 +348,7 @@ def test_authentication_views(test_app): response = test_app.post( '/auth/login/', { 'username': u'chris', - 'password': 'jam'}) + 'password': 'jam_and_ham'}) context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] assert context['login_failed'] @@ -383,16 +362,13 @@ def test_authentication_views(test_app): # User should be redirected response.follow() - assert_equal( - urlparse.urlsplit(response.location)[2], - '/') - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/root.html') + 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) + assert session['user_id'] == unicode(test_user.id) # Successful logout # ----------------- @@ -401,16 +377,13 @@ def test_authentication_views(test_app): # Should be redirected to index page response.follow() - assert_equal( - urlparse.urlsplit(response.location)[2], - '/') - assert template.TEMPLATE_TEST_CONTEXT.has_key( - 'mediagoblin/root.html') + 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 session.has_key('user_id') == False + assert 'user_id' not in session # User is redirected to custom URL if POST['next'] is set # ------------------------------------------------------- @@ -420,7 +393,4 @@ def test_authentication_views(test_app): 'username': u'chris', 'password': 'toast', 'next' : '/u/chris/'}) - assert_equal( - urlparse.urlsplit(response.location)[2], - '/u/chris/') - + assert urlparse.urlsplit(response.location)[2] == '/u/chris/' diff --git a/mediagoblin/tests/test_cache.py b/mediagoblin/tests/test_cache.py deleted file mode 100644 index 48fa1386..00000000 --- a/mediagoblin/tests/test_cache.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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.tests.tools import setup_fresh_app -from mediagoblin import mg_globals - - -DATA_TO_CACHE = { - 'herp': 'derp', - 'lol': 'cats'} - - -def _get_some_data(key): - """ - Stuid function that makes use of some caching. - """ - some_data_cache = mg_globals.cache.get_cache('sum_data') - if some_data_cache.has_key(key): - return some_data_cache.get(key) - - value = DATA_TO_CACHE.get(key) - some_data_cache.put(key, value) - return value - - -@setup_fresh_app -def test_cache_working(test_app): - some_data_cache = mg_globals.cache.get_cache('sum_data') - assert not some_data_cache.has_key('herp') - assert _get_some_data('herp') == 'derp' - assert some_data_cache.get('herp') == 'derp' - # should get the same value again - assert _get_some_data('herp') == 'derp' - - # now we force-change it, but the function should use the cached - # version - some_data_cache.put('herp', 'pred') - assert _get_some_data('herp') == 'pred' 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 index 7d8c65c1..b13adae6 100644 --- a/mediagoblin/tests/test_config.py +++ b/mediagoblin/tests/test_config.py @@ -36,7 +36,7 @@ def test_read_mediagoblin_config(): assert this_conf['carrotapp']['carrotcake'] == False assert this_conf['carrotapp']['num_carrots'] == 1 - assert not this_conf['carrotapp'].has_key('encouragement_phrase') + assert 'encouragement_phrase' not in this_conf['carrotapp'] assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == True # A good file diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py index ad433fe8..a272caf6 100644 --- a/mediagoblin/tests/test_csrf_middleware.py +++ b/mediagoblin/tests/test_csrf_middleware.py @@ -14,13 +14,10 @@ # You 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 setup_fresh_app from mediagoblin import mg_globals -@setup_fresh_app def test_csrf_cookie_set(test_app): - cookie_name = mg_globals.app_config['csrf_cookie_name'] # get login page @@ -34,7 +31,13 @@ def test_csrf_cookie_set(test_app): assert response.headers.get('Vary', False) == 'Cookie' -@setup_fresh_app +# 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 @@ -44,7 +47,7 @@ def test_csrf_token_must_match(test_app): # construct a request with a cookie, but no form token assert test_app.post('/auth/login/', - headers={'Cookie': str('%s=foo; ' % + headers={'Cookie': str('%s=foo' % mg_globals.app_config['csrf_cookie_name'])}, extra_environ={'gmg.verify_csrf': True}, expect_errors=True).status_int == 403 @@ -52,7 +55,7 @@ def test_csrf_token_must_match(test_app): # 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; ' % + headers={'Cookie': str('%s=foo' % mg_globals.app_config['csrf_cookie_name'])}, extra_environ={'gmg.verify_csrf': True}, expect_errors=True).\ @@ -60,14 +63,12 @@ def test_csrf_token_must_match(test_app): assert test_app.post('/auth/login/', {'csrf_token': 'foo'}, - headers={'Cookie': str('%s=foo; ' % + headers={'Cookie': str('%s=foo' % mg_globals.app_config['csrf_cookie_name'])}, extra_environ={'gmg.verify_csrf': True}).\ status_int == 200 -@setup_fresh_app 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 diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py index 353a7eb9..cda2607f 100644 --- a/mediagoblin/tests/test_edit.py +++ b/mediagoblin/tests/test_edit.py @@ -14,83 +14,128 @@ # You 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 -from mediagoblin.tests.tools import setup_fresh_app, fixture_add_user +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 - -@setup_fresh_app -def test_change_password(test_app): - """Test changing password correctly and incorrectly""" - # set up new user - test_user = fixture_add_user() - - test_app.post( - '/auth/login/', { - 'username': u'chris', - 'password': 'toast'}) - - # test that the password can be changed - # template.clear_test_template_context() - test_app.post( - '/edit/account/', { - 'old_password': 'toast', - 'new_password': '123456', - 'wants_comment_notification': 'y' - }) - - # test_user has to be fetched again in order to have the current values - test_user = mg_globals.database.User.one({'username': u'chris'}) - - assert bcrypt_check_password('123456', test_user.pw_hash) - - # test that the password cannot be changed if the given old_password - # is wrong - # template.clear_test_template_context() - test_app.post( - '/edit/account/', { - 'old_password': 'toast', - 'new_password': '098765', - }) - - test_user = mg_globals.database.User.one({'username': u'chris'}) - - assert not bcrypt_check_password('098765', test_user.pw_hash) - - -@setup_fresh_app -def change_bio_url(test_app): - """Test changing bio and URL""" - # set up new user - test_user = fixture_add_user() - - # test changing the bio and the URL properly - test_app.post( - '/edit/profile/', { - 'bio': u'I love toast!', - 'url': u'http://dustycloud.org/'}) - - test_user = mg_globals.database.User.one({'username': u'chris'}) - - assert test_user.bio == u'I love toast!' - assert test_user.url == u'http://dustycloud.org/' - - # test changing the bio and the URL inproperly - too_long_bio = 150 * 'T' + 150 * 'o' + 150 * 'a' + 150 * 's' + 150* 't' - - test_app.post( - '/edit/profile/', { - # more than 500 characters - 'bio': too_long_bio, - 'url': 'this-is-no-url'}) - - test_user = mg_globals.database.User.one({'username': u'chris'}) - - context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/edit/edit_profile.html'] - form = context['edit_profile_form'] - - assert form.bio.errors == [u'Field must be between 0 and 500 characters long.'] - assert form.url.errors == [u'Improperly formed URL'] - - # test changing the url inproperly +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/account/', { + 'old_password': 'toast', + 'new_password': '123456', + 'wants_comment_notification': 'y' + }) + + # Check for redirect on success + assert res.status_int == 302 + # 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/account/', { + '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 index ed95045c..824de3c2 100644 --- a/mediagoblin/tests/test_exif.py +++ b/mediagoblin/tests/test_exif.py @@ -15,39 +15,20 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import os -import pkg_resources -import Image +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) -GOOD_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'good.jpg')) -EMPTY_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'empty.jpg')) -BAD_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'bad.jpg')) -GPS_JPG = pkg_resources.resource_filename( - 'mediagoblin.tests', - os.path.join( - 'test_exif', - 'has-gps.jpg')) - - def test_exif_extraction(): ''' Test EXIF extraction from a good image @@ -58,10 +39,10 @@ def test_exif_extraction(): gps = get_gps_data(result) # Do we have the result? - assert len(result) == 108 + assert len(result) == 56 # Do we have clean data? - assert len(clean) == 105 + assert len(clean) == 53 # GPS data? assert gps == {} @@ -70,7 +51,7 @@ def test_exif_extraction(): assert useful == { 'EXIF Flash': { 'field_type': 3, - 'printable': 'No', + 'printable': u'Flash did not fire', 'field_offset': 380, 'tag': 37385, 'values': [0], @@ -123,18 +104,7 @@ def test_exif_extraction(): 'field_offset': 708, 'tag': 33437, 'values': [[10, 1]], - 'field_length': 8}, - 'EXIF UserComment': { - 'field_type': 7, - 'printable': 'Joar Wandborg ', - 'field_offset': 26180, - 'tag': 37510, - 'values': [ - 65, 83, 67, 73, 73, 0, 0, 0, 74, 111, 97, 114, 32, 87, - 97, 110, 100, 98, 111, 114, 103, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32], - 'field_length': 44}} + 'field_length': 8}} def test_exif_image_orientation(): diff --git a/mediagoblin/tests/test_globals.py b/mediagoblin/tests/test_globals.py index 98f6a436..fe3088f8 100644 --- a/mediagoblin/tests/test_globals.py +++ b/mediagoblin/tests/test_globals.py @@ -14,32 +14,29 @@ # You 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 nose.tools import assert_raises +import pytest from mediagoblin import mg_globals + class TestGlobals(object): - def setUp(self): - self.old_connection = mg_globals.db_connection + def setup(self): self.old_database = mg_globals.database - def tearDown(self): - mg_globals.db_connection = self.old_connection + def teardown(self): mg_globals.database = self.old_database def test_setup_globals(self): mg_globals.setup_globals( - db_connection='my favorite db_connection!', database='my favorite database!', public_store='my favorite public_store!', queue_store='my favorite queue_store!') - assert mg_globals.db_connection == 'my favorite db_connection!' 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!' - assert_raises( + pytest.raises( AssertionError, mg_globals.setup_globals, - no_such_global_foo = "Dummy") + no_such_global_foo="Dummy") diff --git a/mediagoblin/tests/test_http_callback.py b/mediagoblin/tests/test_http_callback.py index d769af1e..a0511af7 100644 --- a/mediagoblin/tests/test_http_callback.py +++ b/mediagoblin/tests/test_http_callback.py @@ -16,32 +16,35 @@ 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 get_test_app, fixture_add_user +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): - def setUp(self): - self.app = get_test_app() + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + self.db = mg_globals.database - self.user_password = 'secret' - self.user = fixture_add_user('call_back', self.user_password) + self.user_password = u'secret' + self.user = fixture_add_user(u'call_back', self.user_password) self.login() def login(self): - self.app.post('/auth/login/', { + 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.app.get('/oauth/access_token', { + response = self.test_app.get('/oauth/access_token', { 'code': code, 'client_id': client_id, 'client_secret': client_secret}) @@ -52,9 +55,8 @@ class TestHTTPCallback(object): def test_callback(self): ''' Test processing HTTP callback ''' - self.oauth = oauth.TestOAuth() - self.oauth.setUp() + self.oauth.setup(self.test_app) redirect, client_id = self.oauth.test_4_authorize_confidential_client() @@ -69,7 +71,7 @@ class TestHTTPCallback(object): callback_url = 'https://foo.example?secrettestmediagoblinparam' - res = self.app.post('/api/submit?client_id={0}&access_token={1}\ + self.test_app.post('/api/submit?client_id={0}&access_token={1}\ &client_secret={2}'.format( client_id, access_token, diff --git a/mediagoblin/tests/test_messages.py b/mediagoblin/tests/test_messages.py index d3b84828..22f9e800 100644 --- a/mediagoblin/tests/test_messages.py +++ b/mediagoblin/tests/test_messages.py @@ -14,12 +14,10 @@ # You 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.messages import fetch_messages, add_message -from mediagoblin.tests.tools import setup_fresh_app +from mediagoblin import messages from mediagoblin.tools import template -@setup_fresh_app def test_messages(test_app): """ Added messages should show up in the request.session, @@ -30,15 +28,23 @@ def test_messages(test_app): 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 - add_message(request, 'herp_derp', 'First!') + messages.add_message(request, 'herp_derp', 'First!') test_msg_queue = [{'text': 'First!', 'level': 'herp_derp'}] - assert request.session['messages'] == test_msg_queue - + + # 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 fetch_messages(request) == test_msg_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 index cde61a70..2e876812 100644 --- a/mediagoblin/tests/test_mgoblin_app.ini +++ b/mediagoblin/tests/test_mgoblin_app.ini @@ -12,9 +12,7 @@ tags_max_length = 50 # So we can start to test attachments: allow_attachments = True -# Celery shouldn't be set up by the application as it's setup via -# mediagoblin.init.celery.from_celery -celery_setup_elsewhere = true +media_types = mediagoblin.media_types.image, mediagoblin.media_types.pdf [storage:publicstore] base_dir = %(here)s/test_user_dev/media/public @@ -23,10 +21,6 @@ base_url = /mgoblin_media/ [storage:queuestore] base_dir = %(here)s/test_user_dev/media/queue -[beaker.cache] -data_dir = %(here)s/test_user_dev/beaker/cache/data -lock_dir = %(here)s/test_user_dev/beaker/cache/lock - [celery] CELERY_ALWAYS_EAGER = true CELERY_RESULT_DBURI = "sqlite:///%(here)s/test_user_dev/celery.db" @@ -35,3 +29,5 @@ BROKER_HOST = "sqlite:///%(here)s/test_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 index 94ae5a51..755d863f 100644 --- a/mediagoblin/tests/test_misc.py +++ b/mediagoblin/tests/test_misc.py @@ -14,13 +14,78 @@ # You 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 nose.tools import assert_equal +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 -from mediagoblin.tests.tools import setup_fresh_app - -@setup_fresh_app def test_404_for_non_existent(test_app): - assert_equal(test_app.get('/does-not-exist/', - expect_errors=True).status_int, - 404) + 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 index db4e226a..ea3bd798 100644 --- a/mediagoblin/tests/test_oauth.py +++ b/mediagoblin/tests/test_oauth.py @@ -17,37 +17,40 @@ 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 get_test_app, fixture_add_user +from mediagoblin.tests.tools import fixture_add_user _log = logging.getLogger(__name__) class TestOAuth(object): - def setUp(self): - self.app = get_test_app() + @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 = '4cc355_70k3N' - self.user = fixture_add_user('joauth', self.user_password) + self.user_password = u'4cc355_70k3N' + self.user = fixture_add_user(u'joauth', self.user_password) self.login() def login(self): - self.app.post( - '/auth/login/', { - 'username': self.user.username, - 'password': self.user_password}) + 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.app.post( + redirect_uri=''): + return self.test_app.post( '/oauth/client/register', { 'name': name, 'description': description, @@ -59,42 +62,46 @@ class TestOAuth(object): def test_1_public_client_registration_without_redirect_uri(self): ''' Test 'public' OAuth client registration without any redirect uri ''' - response = self.register_client('OMGOMGOMG', 'public', - 'OMGOMG Apache License v2') + 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 == 'OMGOMGOMG').first() + self.db.OAuthClient.name == u'OMGOMGOMG').first() assert response.status_int == 200 # Should display an error - assert ctx['form'].redirect_uri.errors + 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 ''' - self.login() - self.register_client('OMGOMG', 'public', 'OMG!', - 'http://foo.example') + uri = 'http://foo.example' + self.register_client( + u'OMGOMG', 'public', 'OMG!', uri) client = self.db.OAuthClient.query.filter( - self.db.OAuthClient.name == 'OMGOMG').first() + 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('GMOGMO', 'confidential', 'NO GMO!') + response = self.register_client( + u'GMOGMO', 'confidential', 'NO GMO!') assert response.status_int == 302 client = self.db.OAuthClient.query.filter( - self.db.OAuthClient.name == 'GMOGMO').first() + self.db.OAuthClient.name == u'GMOGMO').first() # Client should have been registered assert client @@ -108,9 +115,9 @@ class TestOAuth(object): client_identifier = client.identifier redirect_uri = 'https://foo.example' - response = self.app.get('/oauth/authorize', { + response = self.test_app.get('/oauth/authorize', { 'client_id': client.identifier, - 'scope': 'admin', + 'scope': 'all', 'redirect_uri': redirect_uri}) # User-agent should NOT be redirected @@ -121,7 +128,7 @@ class TestOAuth(object): form = ctx['form'] # Short for client authorization post reponse - capr = self.app.post( + capr = self.test_app.post( '/oauth/client/authorize', { 'client_id': form.client_id.data, 'allow': 'Allow', @@ -136,6 +143,7 @@ class TestOAuth(object): 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): @@ -147,7 +155,7 @@ class TestOAuth(object): client = self.db.OAuthClient.query.filter( self.db.OAuthClient.identifier == unicode(client_id)).first() - token_res = self.app.get('/oauth/access_token?client_id={0}&\ + 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 @@ -161,6 +169,11 @@ code={1}&client_secret={2}'.format(client_id, code, client.secret)) 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() @@ -170,7 +183,7 @@ code={1}&client_secret={2}'.format(client_id, code, client.secret)) client = self.db.OAuthClient.query.filter( self.db.OAuthClient.identifier == unicode(client_id)).first() - token_res = self.app.get('/oauth/access_token?\ + token_res = self.test_app.get('/oauth/access_token?\ code={0}&client_secret={1}'.format(code, client.secret)) assert token_res.status_int == 200 @@ -180,4 +193,30 @@ code={0}&client_secret={1}'.format(code, client.secret)) assert 'error' in token_data assert not 'access_token' in token_data assert token_data['error'] == 'invalid_request' - assert token_data['error_description'] == 'Missing client_id in 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 index d7c18642..91ecbb84 100644 --- a/mediagoblin/tests/test_paste.ini +++ b/mediagoblin/tests/test_paste.ini @@ -9,8 +9,7 @@ use = egg:Paste#urlmap [app:mediagoblin] use = egg:mediagoblin#app -filter-with = beaker -config = %(here)s/test_mgoblin_app.ini +config = %(here)s/mediagoblin.ini [app:publicstore_serve] use = egg:Paste#static @@ -20,14 +19,6 @@ document_root = %(here)s/test_user_dev/media/public use = egg:Paste#static document_root = %(here)s/mediagoblin/static/ -[filter:beaker] -use = egg:Beaker#beaker_session -cache_dir = %(here)s/test_user_dev/beaker -beaker.session.key = mediagoblin -# beaker.session.secret = somesupersecret -beaker.session.data_dir = %(here)s/test_user_dev/beaker/sessions/data -beaker.session.lock_dir = %(here)s/test_user_dev/beaker/sessions/lock - [celery] CELERY_ALWAYS_EAGER = true 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..18f95057 --- /dev/null +++ b/mediagoblin/tests/test_piwigo.py @@ -0,0 +1,69 @@ +# 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="ok">0</rsp>' + + resp = self.do_post("pwg.session.login", + {"username": self.username, "password": "wrong"}) + assert resp.body == XML_PREFIX + '<rsp stat="ok">0</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 index 315a95da..73ad235e 100644 --- a/mediagoblin/tests/test_pluginapi.py +++ b/mediagoblin/tests/test_pluginapi.py @@ -15,11 +15,17 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. 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.tools import pluginapi -from nose.tools import eq_ +from mediagoblin.tests.tools import get_app def with_cleanup(*modules_to_delete): @@ -97,7 +103,7 @@ def test_no_plugins(): setup_plugins() # Make sure we didn't load anything. - eq_(len(pman.plugins), 0) + assert len(pman.plugins) == 0 @with_cleanup('mediagoblin.plugins.sampleplugin') @@ -117,14 +123,14 @@ def test_one_plugin(): setup_plugins() # Make sure we only found one plugin - eq_(len(pman.plugins), 1) + assert len(pman.plugins) == 1 # Make sure the plugin is the one we think it is. - eq_(pman.plugins[0], 'mediagoblin.plugins.sampleplugin') + assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin' # Make sure there was one hook registered - eq_(len(pman.hooks), 1) + assert len(pman.hooks) == 1 # Make sure _setup_plugin_called was called once import mediagoblin.plugins.sampleplugin - eq_(mediagoblin.plugins.sampleplugin._setup_plugin_called, 1) + assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1 @with_cleanup('mediagoblin.plugins.sampleplugin') @@ -145,14 +151,14 @@ def test_same_plugin_twice(): setup_plugins() # Make sure we only found one plugin - eq_(len(pman.plugins), 1) + assert len(pman.plugins) == 1 # Make sure the plugin is the one we think it is. - eq_(pman.plugins[0], 'mediagoblin.plugins.sampleplugin') + assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin' # Make sure there was one hook registered - eq_(len(pman.hooks), 1) + assert len(pman.hooks) == 1 # Make sure _setup_plugin_called was called once import mediagoblin.plugins.sampleplugin - eq_(mediagoblin.plugins.sampleplugin._setup_plugin_called, 1) + assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1 @with_cleanup() @@ -172,4 +178,183 @@ def test_disabled_plugin(): setup_plugins() # Make sure we didn't load the plugin - eq_(len(pman.plugins), 0) + 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""" diff --git a/mediagoblin/tests/test_processing.py b/mediagoblin/tests/test_processing.py index fe8489aa..591add96 100644 --- a/mediagoblin/tests/test_processing.py +++ b/mediagoblin/tests/test_processing.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -from nose.tools import assert_equal - from mediagoblin import processing class TestProcessing(object): @@ -10,7 +8,7 @@ class TestProcessing(object): result = builder.fill(format) if output is None: return result - assert_equal(output, result) + assert output == result def test_easy_filename_fill(self): self.run_fill('/home/user/foo.TXT', '{basename}bar{ext}', 'foobar.txt') 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 index e3b55634..2fc4c043 100644 --- a/mediagoblin/tests/test_sql_migrations.py +++ b/mediagoblin/tests/test_sql_migrations.py @@ -25,8 +25,8 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import select, insert from migrate import changeset -from mediagoblin.db.sql.base import GMGTableBase -from mediagoblin.db.sql.util import MigrationManager, RegisterMigration +from mediagoblin.db.base import GMGTableBase +from mediagoblin.db.migration_tools import MigrationManager, RegisterMigration from mediagoblin.tools.common import CollectingPrinter @@ -322,6 +322,28 @@ def creature_power_hitpower_to_float(db_conn): 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 @@ -660,7 +682,7 @@ def test_set1_to_set3(): u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), printer) - assert migration_manager.latest_migration == 7 + assert migration_manager.latest_migration == 8 assert migration_manager.database_current_migration == 0 # Migrate @@ -679,14 +701,15 @@ def test_set1_to_set3(): + 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 == 7 - assert migration_manager.database_current_migration == 7 + assert migration_manager.latest_migration == 8 + assert migration_manager.database_current_migration == 8 # Check all things in database match expected diff --git a/mediagoblin/tests/test_storage.py b/mediagoblin/tests/test_storage.py index 6fc2e57c..f6f1d18f 100644 --- a/mediagoblin/tests/test_storage.py +++ b/mediagoblin/tests/test_storage.py @@ -18,7 +18,7 @@ import os import tempfile -from nose.tools import assert_raises +import pytest from werkzeug.utils import secure_filename from mediagoblin import storage @@ -41,10 +41,8 @@ def test_clean_listy_filepath(): assert storage.clean_listy_filepath( ['../../../etc/', 'passwd']) == expected - assert_raises( - storage.InvalidFilepath, - storage.clean_listy_filepath, - ['../../', 'linooks.jpg']) + with pytest.raises(storage.InvalidFilepath): + storage.clean_listy_filepath(['../../', 'linooks.jpg']) class FakeStorageSystem(): @@ -80,7 +78,8 @@ def test_storage_system_from_config(): 'mediagoblin.tests.test_storage:FakeStorageSystem'}) assert this_storage.foobie == 'eiboof' assert this_storage.blech == 'hcelb' - assert this_storage.__class__ is FakeStorageSystem + assert unicode(this_storage.__class__) == \ + u'mediagoblin.tests.test_storage.FakeStorageSystem' ########################## @@ -88,7 +87,7 @@ def test_storage_system_from_config(): ########################## def get_tmp_filestorage(mount_url=None, fake_remote=False): - tmpdir = tempfile.mkdtemp() + tmpdir = tempfile.mkdtemp(prefix="test_gmg_storage") if fake_remote: this_storage = FakeRemoteStorage(tmpdir, mount_url) else: @@ -96,6 +95,14 @@ def get_tmp_filestorage(mount_url=None, fake_remote=False): 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() @@ -107,11 +114,13 @@ def test_basic_storage__resolve_filepath(): assert result == os.path.join( tmpdir, 'etc/passwd') - assert_raises( + 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() @@ -125,6 +134,9 @@ def test_basic_storage_file_exists(): 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() @@ -145,6 +157,9 @@ def test_basic_storage_get_unique_filepath(): 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() @@ -181,6 +196,11 @@ def test_basic_storage_get_file(): 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() @@ -195,19 +215,24 @@ def test_basic_storage_delete_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() - assert_raises( + 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/') @@ -215,6 +240,7 @@ def test_basic_storage_url_for_file(): ['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( @@ -223,6 +249,7 @@ def test_basic_storage_url_for_file(): ['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(): @@ -236,10 +263,13 @@ def test_basic_storage_get_local_path(): 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(): @@ -254,9 +284,14 @@ def test_basic_storage_copy_locally(): 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() @@ -266,10 +301,15 @@ def _test_copy_local_to_storage_works(tmpdir, this_storage): 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() diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py index b7b0e574..162b2d19 100644 --- a/mediagoblin/tests/test_submission.py +++ b/mediagoblin/tests/test_submission.py @@ -20,26 +20,17 @@ sys.setdefaultencoding('utf-8') import urlparse import os +import pytest -from nose.tools import assert_equal, assert_true -from pkg_resources import resource_filename - -from mediagoblin.tests.tools import get_test_app, \ - fixture_add_user +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 - -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') +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) @@ -49,8 +40,9 @@ REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request'] class TestSubmission: - def setUp(self): - self.test_app = get_test_app() + @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') @@ -86,27 +78,27 @@ class TestSubmission: def check_comments(self, request, media_id, count): comments = request.db.MediaComment.find({'media_entry': media_id}) - assert_equal(count, len(list(comments))) + assert count == len(list(comments)) def test_missing_fields(self): # Test blank form # --------------- response, form = self.do_post({}, *FORM_CONTEXT) - assert_equal(form.file.errors, [u'You must provide a file.']) + 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_equal(form.file.errors, [u'You must provide a file.']) + assert form.file.errors == [u'You must provide a file.'] def check_url(self, response, path): - assert_equal(urlparse.urlsplit(response.location)[2], 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_true('mediagoblin/user_pages/user.html' in context) + 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(' ', '-')) @@ -121,10 +113,18 @@ class TestSubmission: 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 = request.db.MediaEntry.find(find_data) + media = MediaEntry.find(find_data) if count is not None: - assert_equal(media.count(), count) + assert media.count() == count if count == 0: return return media[0] @@ -132,11 +132,11 @@ class TestSubmission: def test_tags(self): # Good tag string # -------- - response, request = self.do_post({'title': u'Balanced Goblin', + 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'}, 1) + 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' @@ -145,14 +145,14 @@ class TestSubmission: # Test tags that are too long # --------------- - response, form = self.do_post({'title': u'Balanced Goblin', + response, form = self.do_post({'title': u'Balanced Goblin 2', 'tags': BAD_TAG_STRING}, *FORM_CONTEXT, **self.upload_data(GOOD_JPG)) - assert_equal(form.tags.errors, [ + assert form.tags.errors == [ u'Tags must be shorter than 50 characters. ' \ 'Tags that are too long: ' \ - 'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu']) + 'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu'] def test_delete(self): response, request = self.do_post({'title': u'Balanced Goblin'}, @@ -161,11 +161,23 @@ class TestSubmission: 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=media_id) + 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) @@ -174,7 +186,7 @@ class TestSubmission: # --------------------------------------------------- delete_url = request.urlgen( 'mediagoblin.user_pages.media_confirm_delete', - user=self.test_user.username, media=media_id) + 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) @@ -184,7 +196,7 @@ class TestSubmission: # --------------------------------------------------- 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_media(request, {'id': media_id}, 0) self.check_comments(request, media_id, 0) def test_evil_file(self): @@ -193,10 +205,24 @@ class TestSubmission: response, form = self.do_post({'title': u'Malicious Upload 1'}, *FORM_CONTEXT, **self.upload_data(EVIL_FILE)) - assert_equal(len(form.file.errors), 1) + 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 @@ -227,8 +253,8 @@ class TestSubmission: **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_equal(entry.state, 'failed') - assert_equal(entry.fail_error, u'mediagoblin.processing:BadMediaFail') + assert entry.state == 'failed' + assert entry.fail_error == u'mediagoblin.processing:BadMediaFail' def test_evil_jpg(self): # Test non-supported file with .jpg extension @@ -240,7 +266,15 @@ class TestSubmission: # ------------------------------------------- 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)) @@ -250,12 +284,11 @@ class TestSubmission: ('medium', 'bigblue.medium.png'), ('thumb', 'bigblue.thumbnail.png')): # Does the processed image have a good filename? - filename = resource_filename( - 'mediagoblin.tests', - os.path.join('test_user_dev/media/public', - *media.media_files.get(key, []))) - assert_true(filename.endswith('_' + basename)) + 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_true(last_size > size) + assert last_size > size last_size = size diff --git a/mediagoblin/tests/test_submission/evil b/mediagoblin/tests/test_submission/evil Binary files differindex 775da664..2c850e29 100755 --- a/mediagoblin/tests/test_submission/evil +++ b/mediagoblin/tests/test_submission/evil diff --git a/mediagoblin/tests/test_submission/evil.jpg b/mediagoblin/tests/test_submission/evil.jpg Binary files differindex 775da664..2c850e29 100755 --- a/mediagoblin/tests/test_submission/evil.jpg +++ b/mediagoblin/tests/test_submission/evil.jpg diff --git a/mediagoblin/tests/test_submission/evil.png b/mediagoblin/tests/test_submission/evil.png Binary files differindex 775da664..2c850e29 100755 --- a/mediagoblin/tests/test_submission/evil.png +++ b/mediagoblin/tests/test_submission/evil.png diff --git a/mediagoblin/tests/test_submission/good.pdf b/mediagoblin/tests/test_submission/good.pdf Binary files differnew file mode 100644 index 00000000..ab5db006 --- /dev/null +++ b/mediagoblin/tests/test_submission/good.pdf diff --git a/mediagoblin/tests/test_tags.py b/mediagoblin/tests/test_tags.py index bc657660..e25cc283 100644 --- a/mediagoblin/tests/test_tags.py +++ b/mediagoblin/tests/test_tags.py @@ -14,10 +14,8 @@ # You 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 setup_fresh_app from mediagoblin.tools import text -@setup_fresh_app def test_list_of_dicts_conversion(test_app): """ When the user adds tags to a media entry, the string from the form is 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 index 452090e1..bc14f528 100644 --- a/mediagoblin/tests/test_util.py +++ b/mediagoblin/tests/test_util.py @@ -70,13 +70,13 @@ I hope you like unit tests JUST AS MUCH AS I DO!""" I hope you like unit tests JUST AS MUCH AS I DO!""" def test_slugify(): - assert url.slugify('a walk in the park') == 'a-walk-in-the-park' - assert url.slugify('A Walk in the Park') == 'a-walk-in-the-park' - assert url.slugify('a walk in the park') == 'a-walk-in-the-park' - assert url.slugify('a walk in-the-park') == 'a-walk-in-the-park' - assert url.slugify('a w@lk in the park?') == 'a-w-lk-in-the-park' - assert url.slugify(u'a walk in the par\u0107') == 'a-walk-in-the-parc' - assert url.slugify(u'\u00E0\u0042\u00E7\u010F\u00EB\u0066') == 'abcdef' + 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(): """ @@ -104,6 +104,28 @@ def test_locale_to_lower_lower(): 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( diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py index 04a74653..6695618b 100644 --- a/mediagoblin/tests/test_workbench.py +++ b/mediagoblin/tests/test_workbench.py @@ -18,29 +18,37 @@ import os import tempfile -from mediagoblin import workbench -from mediagoblin.tests.test_storage import get_tmp_filestorage +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): + def setup(self): + self.workbench_base = tempfile.mkdtemp(prefix='gmg_workbench_testing') self.workbench_manager = workbench.WorkbenchManager( - os.path.join(tempfile.gettempdir(), u'mgoblin_workbench_testing')) + 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_workbench() + 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_workbench() + 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_self() + this_workbench.destroy() def test_destroy_workbench(self): # kill a workbench - this_workbench = self.workbench_manager.create_workbench() + this_workbench = self.workbench_manager.create() tmpfile_name = this_workbench.joinpath('temp.txt') tmpfile = file(tmpfile_name, 'w') with tmpfile: @@ -49,14 +57,14 @@ class TestWorkbench(object): assert os.path.exists(tmpfile_name) wb_dir = this_workbench.dir - this_workbench.destroy_self() + 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_workbench() - + this_workbench = self.workbench_manager.create() + # Write a brand new file filepath = ['dir1', 'dir2', 'ourfile.txt'] @@ -67,6 +75,8 @@ class TestWorkbench(object): 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) @@ -78,7 +88,7 @@ class TestWorkbench(object): 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') @@ -91,3 +101,22 @@ class TestWorkbench(object): 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/db/mongo/__init__.py b/mediagoblin/tests/testplugins/__init__.py index 621845ba..621845ba 100644 --- a/mediagoblin/db/mongo/__init__.py +++ b/mediagoblin/tests/testplugins/__init__.py 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/init/celery/from_tests.py b/mediagoblin/tests/testplugins/modify_context/views.py index 3149e1ba..701ec6f9 100644 --- a/mediagoblin/init/celery/from_tests.py +++ b/mediagoblin/tests/testplugins/modify_context/views.py @@ -14,20 +14,20 @@ # You 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.tools.response import render_to_response -from mediagoblin.tests.tools import TEST_APP_CONFIG -from mediagoblin.init.celery.from_celery import setup_self +def specific(request): + return render_to_response( + request, + 'contextplugin/specific.html', + {"something": "orother", + "doubleme": "happy"}) -OUR_MODULENAME = __name__ -CELERY_SETUP = False - -if os.environ.get('CELERY_CONFIG_MODULE') == OUR_MODULENAME: - if CELERY_SETUP: - pass - else: - setup_self(check_environ_for_conf=False, module_name=OUR_MODULENAME, - default_conf_file=TEST_APP_CONFIG) - CELERY_SETUP = True +def general(request): + return render_to_response( + request, + 'contextplugin/general.html', + {"lol": "cats", + "doubleme": "joy"}) diff --git a/mediagoblin/meddleware/noop.py b/mediagoblin/tests/testplugins/pluginspec/__init__.py index 98477706..76ca2b1f 100644 --- a/mediagoblin/meddleware/noop.py +++ b/mediagoblin/tests/testplugins/pluginspec/__init__.py @@ -14,14 +14,9 @@ # You 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 -from mediagoblin.meddleware import BaseMeddleware - - -class NoOpMeddleware(BaseMeddleware): - - def process_request(self, request, controller): - pass - - def process_response(self, request, response): - 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/tools.py b/mediagoblin/tests/tools.py index d3369831..794ed940 100644 --- a/mediagoblin/tests/tools.py +++ b/mediagoblin/tests/tools.py @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +import sys import os import pkg_resources import shutil @@ -25,14 +26,13 @@ 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.open import setup_connection_and_db_from_config -from mediagoblin.db.sql.base import Session +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 -from mediagoblin.init.celery import setup_celery_app MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__' @@ -42,19 +42,9 @@ TEST_APP_CONFIG = pkg_resources.resource_filename( 'mediagoblin.tests', 'test_mgoblin_app.ini') TEST_USER_DEV = pkg_resources.resource_filename( 'mediagoblin.tests', 'test_user_dev') -MGOBLIN_APP = None -USER_DEV_DIRECTORIES_TO_SETUP = [ - 'media/public', 'media/queue', - 'beaker/sessions/data', 'beaker/sessions/lock'] -BAD_CELERY_MESSAGE = """\ -Sorry, you *absolutely* must run nosetests with the -mediagoblin.init.celery.from_tests module. Like so: -$ CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_tests ./bin/nosetests""" - - -class BadCeleryEnviron(Exception): pass +USER_DEV_DIRECTORIES_TO_SETUP = ['media/public', 'media/queue'] class TestingMeddleware(BaseMeddleware): @@ -78,7 +68,7 @@ class TestingMeddleware(BaseMeddleware): def process_response(self, request, response): # All following tests should be for html only! - if response.content_type != "text/html": + if getattr(response, 'content_type', None) != "text/html": # Get out early return @@ -96,41 +86,40 @@ class TestingMeddleware(BaseMeddleware): return -def suicide_if_bad_celery_environ(): - if not os.environ.get('CELERY_CONFIG_MODULE') == \ - 'mediagoblin.init.celery.from_tests': - raise BadCeleryEnviron(BAD_CELERY_MESSAGE) - - -def get_test_app(dump_old_app=True): - suicide_if_bad_celery_environ() +def get_app(request, paste_config=None, mgoblin_config=None): + """Create a MediaGoblin app for testing. - # Make sure we've turned on testing - testing._activate_testing() - - # Leave this imported as it sets up celery. - from mediagoblin.init.celery import from_tests + 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 - global MGOBLIN_APP + # 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('test_user_dev').strpath - # Just return the old app if that exists and it's okay to set up - # and return - if MGOBLIN_APP and not dump_old_app: - return MGOBLIN_APP + 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() - # Remove and reinstall user_dev directories - if os.path.exists(TEST_USER_DEV): - shutil.rmtree(TEST_USER_DEV) - + # install user_dev directories for directory in USER_DEV_DIRECTORIES_TO_SETUP: - full_dir = os.path.join(TEST_USER_DEV, directory) + full_dir = os.path.join(user_dev_dir, directory) os.makedirs(full_dir) # Get app config - global_config, validation_result = read_mediagoblin_config(TEST_APP_CONFIG) + global_config, validation_result = read_mediagoblin_config(new_mgoblin_config) app_config = global_config['mediagoblin'] # Run database setup/migrations @@ -138,10 +127,7 @@ def get_test_app(dump_old_app=True): # setup app and return test_app = loadapp( - 'config:' + TEST_SERVER_CONFIG) - - # Re-setup celery - setup_celery_app(app_config, global_config) + 'config:' + new_paste_config) # Insert the TestingMeddleware, which can do some # sanity checks on every request/response. @@ -150,26 +136,10 @@ def get_test_app(dump_old_app=True): mg_globals.app.meddleware.insert(0, TestingMeddleware(mg_globals.app)) app = TestApp(test_app) - MGOBLIN_APP = app return app -def setup_fresh_app(func): - """ - Decorator to setup a fresh test application for this function. - - Cleans out test buckets and passes in a new, fresh test_app. - """ - @wraps(func) - def wrapper(*args, **kwargs): - test_app = get_test_app() - testing.clear_test_buckets() - return func(test_app, *args, **kwargs) - - return wrapper - - def install_fixtures_simple(db, fixtures): """ Very simply install fixtures in the database @@ -184,27 +154,30 @@ 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. + 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', + {'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']}) + 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='toast', +def fixture_add_user(username=u'chris', password=u'toast', active_user=True): - test_user = mg_globals.database.User() + # 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: @@ -216,10 +189,46 @@ def fixture_add_user(username=u'chris', password='toast', test_user.save() # Reload - test_user = mg_globals.database.User.find_one({'username': username}) + test_user = User.query.filter_by(username=username).first() # ... and detach from session: - from mediagoblin.db.sql.base import 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/assets/css/airy.css b/mediagoblin/themes/airy/assets/css/airy.css index c63345b2..c4bea5cb 100644 --- a/mediagoblin/themes/airy/assets/css/airy.css +++ b/mediagoblin/themes/airy/assets/css/airy.css @@ -33,6 +33,12 @@ header { margin-right: auto; } +@media screen and (max-width: 940px) { + header { + width: 100%; + } +} + footer { border-top: 1px solid #E4E4E4; } diff --git a/mediagoblin/themes/airy/templates/mediagoblin/base.html b/mediagoblin/themes/airy/templates/mediagoblin/base.html deleted file mode 100644 index db500199..00000000 --- a/mediagoblin/themes/airy/templates/mediagoblin/base.html +++ /dev/null @@ -1,94 +0,0 @@ -{# -# 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> - <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 src="{{ request.staticdirect('/js/extlib/jquery.js') }}"></script> - <script type="text/javascript" - src="{{ request.staticdirect('/js/header_dropdown.js') }}"></script> - <!--[if lt IE 9]> - <script src="{{ request.staticdirect('/js/extlib/html5shiv.js') }}"></script> - <![endif]--> - - {% include "mediagoblin/extra_head.html" %} - - {% block mediagoblin_head %} - {% endblock mediagoblin_head %} - </head> - <body> - {% block mediagoblin_body %} - {% block mediagoblin_header %} - <header> - {% 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 %} - {% block mediagoblin_header_title %}{% endblock %} - <div class="header_right"> - {% if request.user %} - <a href="{{ request.urlgen('mediagoblin.user_pages.user_home', user= request.user.username) }}">{{ request.user.username }}</a>{% trans %}'s account{% endtrans %} - (<a href="{{ request.urlgen('mediagoblin.auth.logout') }}">{% trans %}log out{% endtrans %}</a>) - {% if request.user and request.user.status == 'active' %} - <a class="button_action" href="{{ request.urlgen('mediagoblin.submit.start') }}">{% trans %}Add media{% endtrans %}</a> - {% 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> - {% endif %} - {% else %} - <a href="{{ request.urlgen('mediagoblin.auth.login') }}"> - {% trans %}Log in{% endtrans %}</a> - {% endif %} - </div> - <div class="clear"></div> - </header> - {% endblock %} - <div class="container"> - <div class="mediagoblin_content"> - {% include "mediagoblin/utils/messages.html" %} - {% block mediagoblin_content %} - {% endblock mediagoblin_content %} - </div> - {% block mediagoblin_footer %} - <footer> - {% trans -%} - Powered by <a href="http://mediagoblin.org">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 %} - {% endblock mediagoblin_body %} - </div> - </body> -</html> diff --git a/mediagoblin/gmg_commands/mongosql.py b/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html index dd53f575..c8500159 100644 --- a/mediagoblin/gmg_commands/mongosql.py +++ b/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html @@ -1,3 +1,4 @@ +{# # GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2012 MediaGoblin contributors. See AUTHORS. # @@ -13,16 +14,12 @@ # # You 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 mongosql_parser_setup(subparser): - pass - - -def mongosql(args): - # First, make sure our mongo migrations are up to date... - from mediagoblin.gmg_commands.migrate import run_migrate - run_migrate(args.conf_file) - - from mediagoblin.db.sql.convert import run_conversion - run_conversion(args.conf_file) +{% 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/tools/common.py b/mediagoblin/tools/common.py index c9f9d032..34586611 100644 --- a/mediagoblin/tools/common.py +++ b/mediagoblin/tools/common.py @@ -16,7 +16,6 @@ import sys -DISPLAY_IMAGE_FETCHING_ORDER = [u'medium', u'original', u'thumb'] global TESTS_ENABLED TESTS_ENABLED = False 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 index 543484c9..d0f9d0a6 100644 --- a/mediagoblin/tools/exif.py +++ b/mediagoblin/tools/exif.py @@ -14,7 +14,11 @@ # You 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.extlib.EXIF import process_file, Ratio +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 _ @@ -46,7 +50,10 @@ def exif_fix_image_orientation(im, exif_tags): Translate any EXIF orientation to raw orientation Cons: - - REDUCES IMAGE QUALITY by recompressig it + - 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 @@ -58,7 +65,7 @@ def exif_fix_image_orientation(im, exif_tags): 6: 270, 8: 90} orientation = exif_tags['Image Orientation'].values[0] - if orientation in rotation_map.keys(): + if orientation in rotation_map: im = im.rotate( rotation_map[orientation]) @@ -69,16 +76,12 @@ def extract_exif(filename): """ Returns EXIF tags found in file at ``filename`` """ - exif_tags = {} - try: - image = open(filename) - exif_tags = process_file(image) + with file(filename) as image: + return process_file(image, details=False) except IOError: raise BadMediaFail(_('Could not read the image file.')) - return exif_tags - def clean_exif(exif): ''' @@ -92,13 +95,8 @@ def clean_exif(exif): 'JPEGThumbnail', 'Thumbnail JPEGInterchangeFormat'] - clean_exif = {} - - for key, value in exif.items(): - if not key in disabled_tags: - clean_exif[key] = _ifd_tag_to_dict(value) - - return clean_exif + 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): @@ -119,13 +117,8 @@ def _ifd_tag_to_dict(tag): data['printable'] = tag.printable.decode('utf8', 'replace') if type(tag.values) == list: - data['values'] = [] - for val in tag.values: - if isinstance(val, Ratio): - data['values'].append( - _ratio_to_list(val)) - else: - data['values'].append(val) + 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 @@ -141,12 +134,7 @@ def _ratio_to_list(ratio): def get_useful(tags): - useful = {} - for key, tag in tags.items(): - if key in USEFUL_TAGS: - useful[key] = tag - - return useful + return dict((key, tag) for (key, tag) in tags.iteritems() if key in USEFUL_TAGS) def get_gps_data(tags): @@ -163,7 +151,7 @@ def get_gps_data(tags): 'latitude': tags['GPS GPSLatitude'], 'longitude': tags['GPS GPSLongitude']} - for key, dat in dms_data.items(): + for key, dat in dms_data.iteritems(): gps_data[key] = ( lambda v: float(v[0].num) / float(v[0].den) \ diff --git a/mediagoblin/tools/files.py b/mediagoblin/tools/files.py index fd38f05e..848c86f2 100644 --- a/mediagoblin/tools/files.py +++ b/mediagoblin/tools/files.py @@ -37,7 +37,7 @@ def delete_media_files(media): mg_globals.public_store.delete_file( attachment['filepath']) except OSError: - no_such_files.append("/".join(attachment)) + 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 index 48cb442b..a964980e 100644 --- a/mediagoblin/tools/licenses.py +++ b/mediagoblin/tools/licenses.py @@ -14,42 +14,56 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -SORTED_SUPPORTED_LICENSES = [ - ("", - {"name": "No license specified", - "abbreviation": "All rights reserved"}), - ("http://creativecommons.org/licenses/by/3.0/", - {"name": "Creative Commons Attribution Unported 3.0", - "abbreviation": "CC BY 3.0"}), - ("http://creativecommons.org/licenses/by-sa/3.0/", - {"name": "Creative Commons Attribution-ShareAlike Unported 3.0", - "abbreviation": "CC BY-SA 3.0"}), - ("http://creativecommons.org/licenses/by-nd/3.0/", - {"name": "Creative Commons Attribution-NoDerivs 3.0 Unported", - "abbreviation": "CC BY-ND 3.0"}), - ("http://creativecommons.org/licenses/by-nc/3.0/", - {"name": "Creative Commons Attribution-NonCommercial Unported 3.0", - "abbreviation": "CC BY-NC 3.0"}), - ("http://creativecommons.org/licenses/by-nc-sa/3.0/", - {"name": "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported", - "abbreviation": "CC BY-NC-SA 3.0"}), - ("http://creativecommons.org/licenses/by-nc-nd/3.0/", - {"name": "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported", - "abbreviation": "CC BY-NC-ND 3.0"}), - ("http://creativecommons.org/publicdomain/zero/1.0/", - {"name": "Creative Commons CC0 1.0 Universal", - "abbreviation": "CC0 1.0"}), - ("http://creativecommons.org/publicdomain/mark/1.0/", - {"name": "Public Domain", - "abbreviation": "Public Domain"})] - -SUPPORTED_LICENSES = dict(SORTED_SUPPORTED_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/"), + ] -def licenses_as_choices(): - license_list = [] +# 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) - for uri, data in SORTED_SUPPORTED_LICENSES: - license_list.append((uri, data["abbreviation"])) - return license_list +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 index 8639ba0c..4fa02ce5 100644 --- a/mediagoblin/tools/mail.py +++ b/mediagoblin/tools/mail.py @@ -122,3 +122,16 @@ def send_email(from_addr, to_addrs, subject, message_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 diff --git a/mediagoblin/tools/pagination.py b/mediagoblin/tools/pagination.py index 50e59070..d0f08c94 100644 --- a/mediagoblin/tools/pagination.py +++ b/mediagoblin/tools/pagination.py @@ -25,7 +25,7 @@ PAGINATION_DEFAULT_PER_PAGE = 30 class Pagination(object): """ - Pagination class for mongodb queries. + Pagination class for database queries. Initialization through __init__(self, cursor, page=1, per_page=2), get actual data slice through __call__(). @@ -40,8 +40,8 @@ class Pagination(object): - page: requested page - per_page: number of objects per page - cursor: db cursor - - jump_to_id: ObjectId, sets the page to the page containing the - object with _id == jump_to_id. + - 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 @@ -53,7 +53,7 @@ class Pagination(object): cursor = copy.copy(self.cursor) for (doc, increment) in izip(cursor, count(0)): - if doc._id == jump_to_id: + if doc.id == jump_to_id: self.page = 1 + int(floor(increment / self.per_page)) self.active_id = jump_to_id @@ -63,8 +63,11 @@ class Pagination(object): """ Returns slice of objects for the requested page """ - return self.cursor.skip( - (self.page - 1) * self.per_page).limit(self.per_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): diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py index 1752dfc8..3f98aa8a 100644 --- a/mediagoblin/tools/pluginapi.py +++ b/mediagoblin/tools/pluginapi.py @@ -83,6 +83,9 @@ class PluginManager(object): # list of registered template paths "template_paths": set(), + # list of template hooks + "template_hooks": {}, + # list of registered routes "routes": [], } @@ -131,6 +134,18 @@ class PluginManager(object): 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 @@ -145,16 +160,14 @@ def register_routes(routes): Example passing in a single route: - >>> from routes import Route - >>> register_routes(Route('about-view', '/about', - ... controller=about_view_handler)) + >>> register_routes(('about-view', '/about', + ... 'mediagoblin.views:about_view_handler')) Example passing in a list of routes: - >>> from routes import Route >>> register_routes([ - ... Route('contact-view', '/contact', controller=contact_handler), - ... Route('about-view', '/about', controller=about_handler) + ... ('contact-view', '/contact', 'mediagoblin.views:contact_handler'), + ... ('about-view', '/about', 'mediagoblin.views:about_handler') ... ]) @@ -210,3 +223,145 @@ def get_config(key): 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 index cff4cb9d..2abe6452 100644 --- a/mediagoblin/tools/processing.py +++ b/mediagoblin/tools/processing.py @@ -21,8 +21,6 @@ import traceback from urllib2 import urlopen, Request, HTTPError from urllib import urlencode -from mediagoblin.tools.common import TESTS_ENABLED - _log = logging.getLogger(__name__) TESTS_CALLBACKS = {} diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py index ae372c92..ee342eae 100644 --- a/mediagoblin/tools/request.py +++ b/mediagoblin/tools/request.py @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import logging -from mediagoblin.db.util import ObjectId, InvalidId +from mediagoblin.db.models import User _log = logging.getLogger(__name__) @@ -25,21 +25,14 @@ def setup_user_in_request(request): Examine a request and tack on a request.user parameter if that's appropriate. """ - if not request.session.has_key('user_id'): + if 'user_id' not in request.session: request.user = None return - try: - oid = ObjectId(request.session['user_id']) - except InvalidId: - user = None - else: - user = request.db.User.find_one({'_id': oid}) + request.user = User.query.get(request.session['user_id']) - if not user: + 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.invalidate() - - request.user = user + request.session.delete() diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py index a54c32fb..aaf31d0b 100644 --- a/mediagoblin/tools/response.py +++ b/mediagoblin/tools/response.py @@ -14,8 +14,15 @@ # You 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 webob import Response, exc +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): @@ -25,23 +32,77 @@ def render_to_response(request, template, context, status=200): status=status) -def render_404(request): - """ - Render a 404. +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 render_to_response( - request, 'mediagoblin/404.html', {}, status=404) + 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): - """Returns a HTTPFound(), takes a request and then urlgen params""" + """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) + - querystring = None - if kwargs.get('querystring'): - querystring = kwargs.get('querystring') - del kwargs['querystring'] +def redirect_obj(request, obj): + """Redirect to the page for the given object. - return exc.HTTPFound( - location=''.join([ - request.urlgen(*args, **kwargs), - querystring if querystring else ''])) + 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/template.py b/mediagoblin/tools/template.py index 158d5321..3d651a6e 100644 --- a/mediagoblin/tools/template.py +++ b/mediagoblin/tools/template.py @@ -14,16 +14,25 @@ # You 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 math import ceil + 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 setup_gettext +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 = {} @@ -34,11 +43,11 @@ def get_jinja_env(template_loader, locale): (In the future we may have another system for providing theming; for now this is good enough.) """ - setup_gettext(locale) + set_thread_locale(locale) # If we have a jinja environment set up with this locale, just # return that one. - if SETUP_JINJA_ENVS.has_key(locale): + if locale in SETUP_JINJA_ENVS: return SETUP_JINJA_ENVS[locale] # jinja2.StrictUndefined will give exceptions on references @@ -46,7 +55,9 @@ def get_jinja_env(template_loader, locale): template_env = jinja2.Environment( loader=template_loader, autoescape=True, undefined=jinja2.StrictUndefined, - extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape']) + extensions=[ + 'jinja2.ext.i18n', 'jinja2.ext.autoescape', + TemplateHookExtension]) template_env.install_gettext_callables( mg_globals.thread_scope.translations.ugettext, @@ -57,10 +68,20 @@ def get_jinja_env(template_loader, locale): # ... 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['gridify_list'] = gridify_list - template_env.globals['gridify_cursor'] = gridify_cursor 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 @@ -85,6 +106,20 @@ def render_template(request, template_path, context): 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: @@ -98,30 +133,28 @@ def clear_test_template_context(): TEMPLATE_TEST_CONTEXT = {} -def gridify_list(this_list, num_cols=5): - """ - Generates a list of lists where each sub-list's length depends on - the number of columns in the list +class TemplateHookExtension(Extension): """ - grid = [] + Easily loop through a bunch of templates from a template hook. - # Figure out how many rows we should have - num_rows = int(ceil(float(len(this_list)) / num_cols)) + Use: + {% template_hook("comment_extras") %} - for row_num in range(num_rows): - slice_min = row_num * num_cols - slice_max = (row_num + 1) * num_cols - - row = this_list[slice_min:slice_max] + ... will include all templates hooked into the comment_extras section. + """ - grid.append(row) + tags = set(["template_hook"]) - return grid + 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)) -def gridify_cursor(this_cursor, num_cols=5): - """ - Generates a list of lists where each sub-list's length depends on - the number of columns in the list - """ - return gridify_list(list(this_cursor), num_cols) + return includes diff --git a/mediagoblin/tools/text.py b/mediagoblin/tools/text.py index ea231244..96df49d2 100644 --- a/mediagoblin/tools/text.py +++ b/mediagoblin/tools/text.py @@ -38,13 +38,12 @@ HTML_CLEANER = Cleaner( 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 + remove_unknown_tags=False, # can't be used with allow_tags safe_attrs_only=True, - add_nofollow=True, # for now + add_nofollow=True, # for now host_whitelist=(), whitelist_tags=set([])) -TAGS_DELIMITER=','; def clean_html(html): # clean_html barfs on an empty string @@ -68,14 +67,12 @@ def convert_to_tag_list_of_dicts(tag_string): 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( - TAGS_DELIMITER): - + for tag in stripped_tag_string.split(','): + tag = tag.strip() # Ignore empty or duplicate tags - if tag.strip() and tag.strip() not in [t['name'] for t in taglist]: - - taglist.append({'name': tag.strip(), - 'slug': url.slugify(tag.strip())}) + if tag and tag not in [t['name'] for t in taglist]: + taglist.append({'name': tag, + 'slug': url.slugify(tag)}) return taglist @@ -85,11 +82,10 @@ def media_tags_as_string(media_entry_tags): This is the opposite of convert_to_tag_list_of_dicts """ - media_tag_string = '' + tags_string = '' if media_entry_tags: - media_tag_string = (TAGS_DELIMITER+u' ').join( - [tag['name'] for tag in media_entry_tags]) - return media_tag_string + tags_string = u', '.join([tag['name'] for tag in media_entry_tags]) + return tags_string TOO_LONG_TAG_WARNING = \ @@ -107,7 +103,7 @@ def tag_length_validator(form, field): if too_long_tags: raise wtforms.ValidationError( - TOO_LONG_TAG_WARNING % (mg_globals.app_config['tags_max_length'], \ + TOO_LONG_TAG_WARNING % (mg_globals.app_config['tags_max_length'], ', '.join(too_long_tags))) 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 index 01cabe6a..b20e57d1 100644 --- a/mediagoblin/tools/translate.py +++ b/mediagoblin/tools/translate.py @@ -16,7 +16,9 @@ import gettext import pkg_resources -from babel.localedata import exists + + +from babel import localedata from babel.support import LazyProxy from mediagoblin import mg_globals @@ -25,14 +27,40 @@ 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" + Take a locale, regardless of style, and format it like "en_US" """ if '-' in locale: lang, country = locale.split('-', 1) @@ -57,28 +85,32 @@ def locale_to_lower_lower(locale): def get_locale_from_request(request): """ - Figure out what target language is most appropriate based on the - request + Return most appropriate language based on prefs/request request """ - request_form = request.GET or request.form + request_args = (request.args, request.form)[request.method=='POST'] - if request_form.has_key('lang'): - return locale_to_lower_upper(request_form['lang']) + if 'lang' in request_args: + # User explicitely demanded a language, normalize lower_uppercase + target_lang = locale_to_lower_upper(request_args['lang']) - if 'target_lang' in request.session: + elif 'target_lang' in request.session: + # TODO: Uh, ohh, this is never ever set anywhere? target_lang = request.session['target_lang'] - # Pull the first acceptable language or English else: - # TODO: Internationalization broken - target_lang = 'en' + # 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 locale_to_lower_upper(target_lang) + return target_lang SETUP_GETTEXTS = {} -def setup_gettext(locale): +def get_gettext_translation(locale): """ - Setup the gettext instance based on this 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 @@ -91,15 +123,14 @@ def setup_gettext(locale): else: this_gettext = gettext.translation( 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True) - if exists(locale): + if localedata.exists(locale): SETUP_GETTEXTS[locale] = this_gettext - - mg_globals.thread_scope.translations = this_gettext + return this_gettext -# Force en to be setup before anything else so that -# mg_globals.translations is never None -setup_gettext('en') +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): @@ -112,6 +143,16 @@ def pass_to_ugettext(*args, **kwargs): 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): """ @@ -119,9 +160,12 @@ def lazy_pass_to_ugettext(*args, **kwargs): 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. + 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 LazyProxy(pass_to_ugettext, *args, **kwargs) + return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs) def pass_to_ngettext(*args, **kwargs): @@ -143,7 +187,17 @@ def lazy_pass_to_ngettext(*args, **kwargs): level but you need it to not translate until the time that it's used as a string. """ - return LazyProxy(pass_to_ngettext, *args, **kwargs) + 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): diff --git a/mediagoblin/tools/url.py b/mediagoblin/tools/url.py index 7477173a..d9179f9e 100644 --- a/mediagoblin/tools/url.py +++ b/mediagoblin/tools/url.py @@ -15,10 +15,17 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import re -import translitcodec +# 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 !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+') + +_punct_re = re.compile(r'[\t !"#:$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+') def slugify(text, delim=u'-'): @@ -27,7 +34,11 @@ def slugify(text, delim=u'-'): """ result = [] for word in _punct_re.split(text.lower()): - word = word.encode('translit/long') + 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/workbench.py b/mediagoblin/tools/workbench.py index 2331b551..0bd4096b 100644 --- a/mediagoblin/workbench.py +++ b/mediagoblin/tools/workbench.py @@ -19,10 +19,6 @@ import shutil import tempfile -DEFAULT_WORKBENCH_DIR = os.path.join( - tempfile.gettempdir(), u'mgoblin_workbench') - - # Actual workbench stuff # ---------------------- @@ -119,7 +115,7 @@ class Workbench(object): return full_dest_filename - def destroy_self(self): + def destroy(self): """ Destroy this workbench! Deletes the directory and all its contents! @@ -127,18 +123,33 @@ class Workbench(object): """ # 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 temporary storage space - for during the processing stage. + 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): @@ -146,7 +157,7 @@ class WorkbenchManager(object): if not os.path.exists(self.base_workbench_dir): os.makedirs(self.base_workbench_dir) - def create_workbench(self): + def create(self): """ Create and return the path to a new workbench (directory). """ diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py index 9e8ccf01..9a193680 100644 --- a/mediagoblin/user_pages/forms.py +++ b/mediagoblin/user_pages/forms.py @@ -16,12 +16,15 @@ import wtforms from wtforms.ext.sqlalchemy.fields import QuerySelectField -from mediagoblin.tools.translate import fake_ugettext_passthrough as _ +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ class MediaCommentForm(wtforms.Form): comment_content = wtforms.TextAreaField( - '', - [wtforms.validators.Required()]) + _('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( @@ -32,7 +35,9 @@ class ConfirmCollectionItemRemoveForm(wtforms.Form): _('I am sure I want to remove this item from the collection')) class MediaCollectForm(wtforms.Form): - collection = QuerySelectField(allow_blank=True, blank_text=_('-- Select --'), get_label='title',) + collection = QuerySelectField( + _('Collection'), + allow_blank=True, blank_text=_('-- Select --'), get_label='title',) note = wtforms.TextAreaField( _('Include a note'), [wtforms.validators.Optional()],) diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py index a4be14c2..2f47e4b1 100644 --- a/mediagoblin/user_pages/lib.py +++ b/mediagoblin/user_pages/lib.py @@ -18,6 +18,8 @@ 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): @@ -33,7 +35,7 @@ def send_comment_email(user, comment, media, request): comment_url = request.urlgen( 'mediagoblin.user_pages.media_home.view_comment', - comment=comment._id, + comment=comment.id, user=media.get_uploader.username, media=media.slug_or_id, qualified=True) + '#comment' @@ -55,3 +57,21 @@ def send_comment_email(user, comment, media, request): 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 index 8162e641..9cb665b5 100644 --- a/mediagoblin/user_pages/routing.py +++ b/mediagoblin/user_pages/routing.py @@ -14,7 +14,7 @@ # You 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.routing import add_route +from mediagoblin.tools.routing import add_route add_route('mediagoblin.user_pages.user_home', '/u/<string:user>/', 'mediagoblin.user_pages.views:user_home') @@ -24,11 +24,12 @@ add_route('mediagoblin.user_pages.media_home', 'mediagoblin.user_pages.views:media_home') add_route('mediagoblin.user_pages.media_confirm_delete', - '/u/<string:user>/m/<string: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/<string:media>/comment/add/', + '/u/<string:user>/m/<int:media_id>/comment/add/', 'mediagoblin.user_pages.views:media_post_comment') add_route('mediagoblin.user_pages.user_gallery', @@ -36,17 +37,26 @@ add_route('mediagoblin.user_pages.user_gallery', 'mediagoblin.user_pages.views:user_gallery') add_route('mediagoblin.user_pages.media_home.view_comment', - '/u/<string:user>/m/<string:media>/c/<string: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/<string: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') @@ -73,9 +83,9 @@ add_route('mediagoblin.user_pages.processing_panel', # Stray edit routes add_route('mediagoblin.edit.edit_media', - '/u/<string:user>/m/<string:media>/edit/', + '/u/<string:user>/m/<int:media_id>/edit/', 'mediagoblin.edit.views:edit_media') add_route('mediagoblin.edit.attachments', - '/u/<string:user>/m/<string:media>/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 index c26bd340..738cc054 100644 --- a/mediagoblin/user_pages/views.py +++ b/mediagoblin/user_pages/views.py @@ -14,26 +14,27 @@ # You 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 webob import exc import logging import datetime from mediagoblin import messages, mg_globals -from mediagoblin.db.util import DESCENDING, ObjectId -from mediagoblin.tools.response import render_to_response, render_404, redirect +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.tools.files import delete_media_files from mediagoblin.user_pages import forms as user_forms -from mediagoblin.user_pages.lib import send_comment_email +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) + get_user_collection, get_user_collection_item, active_user_from_url) from werkzeug.contrib.atom import AtomFeed -from mediagoblin.media_types import get_media_manager _log = logging.getLogger(__name__) _log.setLevel(logging.DEBUG) @@ -42,8 +43,10 @@ _log.setLevel(logging.DEBUG) @uses_pagination def user_home(request, page): """'Homepage' of a User()""" - user = request.db.User.find_one({ - 'username': request.matchdict['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': @@ -52,9 +55,9 @@ def user_home(request, page): 'mediagoblin/user_pages/user.html', {'user': user}) - cursor = request.db.MediaEntry.find( - {'uploader': user._id, - 'state': u'processed'}).sort('created', DESCENDING) + cursor = MediaEntry.query.\ + filter_by(uploader = user.id, + state = u'processed').order_by(MediaEntry.created.desc()) pagination = Pagination(page, cursor) media_entries = pagination() @@ -76,30 +79,34 @@ def user_home(request, page): 'pagination': pagination}) +@active_user_from_url @uses_pagination -def user_gallery(request, page): +def user_gallery(request, page, url_user=None): """'Gallery' of a User()""" - user = request.db.User.find_one({ - 'username': request.matchdict['user'], - 'status': u'active'}) - if not user: - return render_404(request) - - cursor = request.db.MediaEntry.find( - {'uploader': user._id, - 'state': u'processed'}).sort('created', DESCENDING) - + 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': user, + {'user': url_user, 'tag': tag, 'media_entries': media_entries, 'pagination': pagination}) @@ -112,12 +119,13 @@ def media_home(request, media, page, **kwargs): """ 'Homepage' of a MediaEntry() """ - if ObjectId(request.matchdict.get('comment')): + 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, - ObjectId(request.matchdict.get('comment'))) + comment_id) else: pagination = Pagination( page, media.get_comments( @@ -128,8 +136,7 @@ def media_home(request, media, page, **kwargs): comment_form = user_forms.MediaCommentForm(request.form) - media_template_name = get_media_manager( - media.media_type)['display_template'] + media_template_name = media.media_manager['display_template'] return render_to_response( request, @@ -141,7 +148,7 @@ def media_home(request, media, page, **kwargs): 'app_config': mg_globals.app_config}) -@get_user_media_entry +@get_media_entry_by_id @require_active_login def media_post_comment(request, media): """ @@ -154,7 +161,13 @@ def media_post_comment(request, media): comment.author = request.user.id comment.content = unicode(request.form['comment_content']) - if not comment.content.strip(): + # 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, @@ -172,107 +185,88 @@ def media_post_comment(request, media): media_uploader.wants_comment_notification): send_comment_email(media_uploader, comment, media, request) - return exc.HTTPFound( - location=media.url_for_self(request.urlgen)) + return redirect_obj(request, media) -@get_user_media_entry +@get_media_entry_by_id @require_active_login def media_collect(request, media): + """Add media to collection submission""" form = user_forms.MediaCollectForm(request.form) - filt = (request.db.Collection.creator == request.user.id) - form.collection.query = request.db.Collection.query.filter( - filt).order_by(request.db.Collection.title) + # 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) - if request.method == 'POST': - if form.validate(): + 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 - collection_item = request.db.CollectionItem() - - # If the user is adding a new collection, use that - if request.form['collection_title']: - collection = request.db.Collection() - collection.id = ObjectId() - - collection.title = ( - unicode(request.form['collection_title'])) - - collection.description = unicode( - request.form.get('collection_description')) - 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: - messages.add_message( - request, messages.ERROR, - _('You already have a collection called "%s"!' - % collection.title)) - - return redirect(request, "mediagoblin.user_pages.media_home", - user=request.user.username, - media=media.id) - - collection.save(validate=True) - - collection_item.collection = collection.id - # Otherwise, use the collection selected from the drop-down - else: - collection = request.db.Collection.find_one({ - '_id': request.form.get('collection')}) - collection_item.collection = collection.id - - # 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')) - # Check whether media already exists in collection - elif request.db.CollectionItem.find_one({ - 'media_entry': media.id, - 'collection': collection_item.collection}): - messages.add_message( - request, messages.ERROR, - _('"%s" already in collection "%s"' - % (media.title, collection.title))) - else: - collection_item.media_entry = media.id - collection_item.author = request.user.id - collection_item.note = unicode(request.form['note']) - collection_item.save(validate=True) - - collection.items = collection.items + 1 - collection.save(validate=True) - - media.collected = media.collected + 1 - media.save() - - messages.add_message( - request, messages.SUCCESS, _('"%s" added to collection "%s"' - % (media.title, collection.title))) - return redirect(request, "mediagoblin.user_pages.media_home", - user=media.get_uploader.username, - media=media.id) - else: - messages.add_message( - request, messages.ERROR, - _('Please check your entries and try again.')) + # 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) - return render_to_response( - request, - 'mediagoblin/user_pages/media_collect.html', - {'media': media, - 'form': form}) + # 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) -@get_user_media_entry + 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): @@ -282,21 +276,7 @@ def media_confirm_delete(request, media): if request.method == 'POST' and form.validate(): if form.confirm.data is True: username = media.get_uploader.username - - # Delete all the associated comments - for comment in media.get_comments(): - comment.delete() - - # Delete all files on the public storage - try: - delete_media_files(media) - except OSError, error: - _log.error('No such files from the user "{1}"' - ' to delete: {0}'.format(str(error), username)) - messages.add_message(request, messages.ERROR, - _('Some of the files with this entry seem' - ' to be missing. Deleting anyway.')) - + # Delete MediaEntry and all related files, comments etc. media.delete() messages.add_message( request, messages.SUCCESS, _('You deleted the media.')) @@ -307,11 +287,10 @@ def media_confirm_delete(request, media): messages.add_message( request, messages.ERROR, _("The media was not deleted because you didn't check that you were sure.")) - return exc.HTTPFound( - location=media.url_for_self(request.urlgen)) + return redirect_obj(request, media) if ((request.user.is_admin and - request.user._id != media.uploader)): + request.user.id != media.uploader)): messages.add_message( request, messages.WARNING, _("You are about to delete another user's media. " @@ -324,37 +303,49 @@ def media_confirm_delete(request, media): 'form': form}) +@active_user_from_url @uses_pagination -def user_collection(request, page): +def user_collection(request, page, url_user=None): """A User-defined Collection""" - user = request.db.User.find_one({ - 'username': request.matchdict['user'], - 'status': u'active'}) - if not user: - return render_404(request) + collection = Collection.query.filter_by( + get_creator=url_user, + slug=request.matchdict['collection']).first() - collection = request.db.Collection.find_one( - {'slug': request.matchdict['collection']}) + if not collection: + return render_404(request) - cursor = request.db.CollectionItem.find( - {'collection': collection.id}) + cursor = collection.get_collection_items() pagination = Pagination(page, cursor) collection_items = pagination() - #if no data is available, return NotFound + # 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': user, + {'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 @@ -382,12 +373,10 @@ def collection_item_confirm_remove(request, collection_item): request, messages.ERROR, _("The item was not removed because you didn't check that you were sure.")) - return redirect(request, "mediagoblin.user_pages.user_collection", - user=username, - collection=collection.slug) + return redirect_obj(request, collection) if ((request.user.is_admin and - request.user._id != collection_item.in_collection.creator)): + 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. " @@ -422,8 +411,8 @@ def collection_confirm_delete(request, collection): item.delete() collection.delete() - messages.add_message( - request, messages.SUCCESS, _('You deleted the collection "%s"' % collection_title)) + messages.add_message(request, messages.SUCCESS, + _('You deleted the collection "%s"') % collection_title) return redirect(request, "mediagoblin.user_pages.user_home", user=username) @@ -432,12 +421,10 @@ def collection_confirm_delete(request, collection): request, messages.ERROR, _("The collection was not deleted because you didn't check that you were sure.")) - return redirect(request, "mediagoblin.user_pages.user_collection", - user=username, - collection=collection.slug) + return redirect_obj(request, collection) if ((request.user.is_admin and - request.user._id != collection.creator)): + request.user.id != collection.creator)): messages.add_message( request, messages.WARNING, _("You are about to delete another user's collection. " @@ -457,18 +444,17 @@ def atom_feed(request): """ generates the atom feed with the newest images """ - - user = request.db.User.find_one({ - 'username': request.matchdict['user'], - 'status': u'active'}) + user = User.query.filter_by( + username = request.matchdict['user'], + status = u'active').first() if not user: return render_404(request) - cursor = request.db.MediaEntry.find({ - 'uploader': user._id, - 'state': u'processed'}) \ - .sort('created', DESCENDING) \ - .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS) + 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) @@ -521,29 +507,28 @@ def collection_atom_feed(request): """ generates the atom feed with the newest images from a collection """ - - user = request.db.User.find_one({ - 'username': request.matchdict['user'], - 'status': u'active'}) + user = User.query.filter_by( + username = request.matchdict['user'], + status = u'active').first() if not user: return render_404(request) - collection = request.db.Collection.find_one({ - 'creator': user.id, - 'slug': request.matchdict['collection']}) + collection = Collection.query.filter_by( + creator=user.id, + slug=request.matchdict['collection']).first() + if not collection: + return render_404(request) - cursor = request.db.CollectionItem.find({ - 'collection': collection._id}) \ - .sort('added', DESCENDING) \ + 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': request.urlgen( - 'mediagoblin.user_pages.user_collection', - qualified=True, user=request.matchdict['user'], collection=collection.slug), + 'href': collection.url_for_self(request.urlgen, qualified=True), 'rel': 'alternate', 'type': 'text/html' }] @@ -555,14 +540,16 @@ def collection_atom_feed(request): 'href': push_url}) feed = AtomFeed( - "MediaGoblin: Feed for %s's collection %s" % (request.matchdict['user'], collection.title), - feed_url=request.url, - id='tag:{host},{year}:collection.user-{user}.title-{title}'.format( - host=request.host, - year=datetime.datetime.today().strftime('%Y'), - user=request.matchdict['user'], - title=collection.title), - links=atomlinks) + "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 @@ -592,44 +579,34 @@ def processing_panel(request): Show to the user what media is still in conversion/processing... and what failed, and why! """ - # Get the user - user = request.db.User.find_one( - {'username': request.matchdict['user'], - 'status': u'active'}) - - # Make sure the user exists and is active - if not user: - return render_404(request) - elif user.status != u'active': - return render_to_response( - request, - 'mediagoblin/user_pages/user.html', - {'user': user}) - - # XXX: Should this be a decorator? + 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? Let's simply redirect to this user's homepage then. + 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=request.matchdict['user']) + user=user.username) # Get media entries which are in-processing - processing_entries = request.db.MediaEntry.find( - {'uploader': user._id, - 'state': u'processing'}).sort('created', DESCENDING) + 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 = request.db.MediaEntry.find( - {'uploader': user._id, - 'state': u'failed'}).sort('created', DESCENDING) - - processed_entries = request.db.MediaEntry.find( - {'uploader': user._id, - 'state': u'processed'}).sort('created', DESCENDING).limit(10) + 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( diff --git a/mediagoblin/views.py b/mediagoblin/views.py index 9d34750b..6acd7e96 100644 --- a/mediagoblin/views.py +++ b/mediagoblin/views.py @@ -15,17 +15,17 @@ # 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.db.util import DESCENDING from mediagoblin.decorators import uses_pagination @uses_pagination def root_view(request, page): - cursor = request.db.MediaEntry.find( - {u'state': u'processed'}).sort('created', DESCENDING) + cursor = MediaEntry.query.filter_by(state=u'processed').\ + order_by(MediaEntry.created.desc()) pagination = Pagination(page, cursor) media_entries = pagination() diff --git a/mediagoblin/webfinger/routing.py b/mediagoblin/webfinger/routing.py index 18f9eb02..eb10509f 100644 --- a/mediagoblin/webfinger/routing.py +++ b/mediagoblin/webfinger/routing.py @@ -14,7 +14,7 @@ # You 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.routing import add_route +from mediagoblin.tools.routing import add_route add_route('mediagoblin.webfinger.host_meta', '/.well-known/host-meta', 'mediagoblin.webfinger.views:host_meta') @@ -17,7 +17,6 @@ use = egg:Paste#urlmap [app:mediagoblin] use = egg:mediagoblin#app -filter-with = beaker config = %(here)s/mediagoblin_local.ini %(here)s/mediagoblin.ini [loggers] @@ -57,14 +56,6 @@ use = egg:Paste#static document_root = %(here)s/user_dev/theme_static/ cache_max_age = 86400 -[filter:beaker] -use = egg:Beaker#beaker_session -cache_dir = %(here)s/user_dev/beaker -beaker.session.key = mediagoblin -# beaker.session.secret = somesupersecret -beaker.session.data_dir = %(here)s/user_dev/beaker/sessions/data -beaker.session.lock_dir = %(here)s/user_dev/beaker/sessions/lock - [filter:errors] use = egg:mediagoblin#errors debug = false diff --git a/runtests.sh b/runtests.sh index 94e77da2..00164a78 100755 --- a/runtests.sh +++ b/runtests.sh @@ -16,16 +16,56 @@ # You 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 [ -f ./bin/nosetests ]; then - echo "Using ./bin/nosetests"; - export NOSETESTS="./bin/nosetests"; -elif which nosetests > /dev/null; then - echo "Using nosetests from \$PATH"; - export NOSETESTS="nosetests"; +basedir="`dirname $0`" +# Directory to seaerch for: +subdir="mediagoblin/tests" +[ '!' -d "$basedir/$subdir" ] && basedir="." +if [ '!' -d "$basedir/$subdir" ] +then + echo "Could not find base directory" >&2 + exit 1 +fi + +if [ -x "$basedir/bin/py.test" ]; then + export PYTEST="$basedir/bin/py.test"; + echo "Using $PYTEST"; +elif which py.test > /dev/null; then + echo "Using py.test from \$PATH"; + export PYTEST="py.test"; else - echo "nosetests not found. X_X"; + echo "py.test not found. X_X"; echo "Please install 'nose'. Exiting."; exit 1 fi -CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_tests $NOSETESTS $@ + +# Look to see if the user has specified a specific directory/file to +# run tests out of. If not we'll need to pass along +# mediagoblin/tests/ later very specifically. Otherwise py.test +# will try to read all directories, and this turns into a mess! + +need_arg=1 +ignore_next=0 +for i in "$@" +do + if [ "$ignore_next" = 1 ] + then + ignore_next=0 + continue + fi + case "$i" in + -n) ignore_next=1;; + -*) ;; + *) need_arg=0; break ;; + esac +done + +if [ "$need_arg" = 1 ] +then + testdir="$basedir/mediagoblin/tests" + set -x + exec "$PYTEST" "$@" "$testdir" --boxed +else + set -x + exec "$PYTEST" "$@" --boxed +fi @@ -43,24 +43,28 @@ setup( install_requires=[ 'setuptools', 'PasteScript', - 'beaker', - 'webob<=1.2a2,>=1.1', 'wtforms', 'py-bcrypt', - 'nose', - 'werkzeug', + 'pytest', + 'pytest-xdist', + 'werkzeug>=0.7', 'celery==2.5.3', 'kombu==2.1.7', 'jinja2', 'sphinx', 'Babel', - 'translitcodec', 'argparse', - 'webtest', + 'webtest<2', 'ConfigObj', 'Markdown', 'sqlalchemy>=0.7.0', 'sqlalchemy-migrate', + 'mock', + 'itsdangerous', + 'pytz', + 'six', + ## This is optional! + # 'translitcodec', ## For now we're expecting that users will install this from ## their package managers. # 'lxml', @@ -94,7 +98,7 @@ setup( classifiers=[ "Development Status :: 3 - Alpha", "Environment :: Web Environment", - "License :: OSI Approved :: GNU Affero General Public License", + "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Operating System :: OS Independent", "Programming Language :: Python", 'Programming Language :: Python :: 2.6', |