diff options
Diffstat (limited to 'mediagoblin')
407 files changed, 70741 insertions, 0 deletions
diff --git a/mediagoblin/__init__.py b/mediagoblin/__init__.py new file mode 100644 index 00000000..88dedd28 --- /dev/null +++ b/mediagoblin/__init__.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin._version import __version__ diff --git a/mediagoblin/_version.py b/mediagoblin/_version.py new file mode 100644 index 00000000..2abc105f --- /dev/null +++ b/mediagoblin/_version.py @@ -0,0 +1,26 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# valid version formats: +# * x.y - final release +# * x.ya1 - alpha 1 +# * x.yb1 - beta 1 +# * x.yrc1 - release candidate 1 +# * x.y.dev - dev + +# see http://www.python.org/dev/peps/pep-0386/ + +__version__ = "0.4.1.dev" diff --git a/mediagoblin/admin/__init__.py b/mediagoblin/admin/__init__.py new file mode 100644 index 00000000..719b56e7 --- /dev/null +++ b/mediagoblin/admin/__init__.py @@ -0,0 +1,16 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + diff --git a/mediagoblin/admin/routing.py b/mediagoblin/admin/routing.py new file mode 100644 index 00000000..29515f12 --- /dev/null +++ b/mediagoblin/admin/routing.py @@ -0,0 +1,20 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +admin_routes = [ + ('mediagoblin.admin.panel', + '/panel', + 'mediagoblin.admin.views:admin_processing_panel')] diff --git a/mediagoblin/admin/views.py b/mediagoblin/admin/views.py new file mode 100644 index 00000000..22ca74a3 --- /dev/null +++ b/mediagoblin/admin/views.py @@ -0,0 +1,48 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from werkzeug.exceptions import Forbidden + +from mediagoblin.db.models import MediaEntry +from mediagoblin.decorators import require_active_login +from mediagoblin.tools.response import render_to_response + +@require_active_login +def admin_processing_panel(request): + ''' + Show the global processing panel for this instance + ''' + # TODO: Why not a "require_admin_login" decorator throwing a 403 exception? + if not request.user.is_admin: + raise Forbidden() + + processing_entries = MediaEntry.query.filter_by(state = u'processing').\ + order_by(MediaEntry.created.desc()) + + # Get media entries which have failed to process + failed_entries = MediaEntry.query.filter_by(state = u'failed').\ + order_by(MediaEntry.created.desc()) + + processed_entries = MediaEntry.query.filter_by(state = u'processed').\ + order_by(MediaEntry.created.desc()).limit(10) + + # Render to response + return render_to_response( + request, + 'mediagoblin/admin/panel.html', + {'processing_entries': processing_entries, + 'failed_entries': failed_entries, + 'processed_entries': processed_entries}) diff --git a/mediagoblin/app.py b/mediagoblin/app.py new file mode 100644 index 00000000..1984ce77 --- /dev/null +++ b/mediagoblin/app.py @@ -0,0 +1,268 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import logging + +from mediagoblin.routing import get_url_map +from mediagoblin.tools.routing import endpoint_to_controller + +from werkzeug.wrappers import Request +from werkzeug.exceptions import HTTPException +from werkzeug.routing import RequestRedirect + +from mediagoblin import meddleware, __version__ +from mediagoblin.tools import common, session, translate, template +from mediagoblin.tools.response import render_http_exception +from mediagoblin.tools.theme import register_themes +from mediagoblin.tools import request as mg_request +from mediagoblin.mg_globals import setup_globals +from mediagoblin.init.celery import setup_celery_from_config +from mediagoblin.init.plugins import setup_plugins +from mediagoblin.init import (get_jinja_loader, get_staticdirector, + setup_global_and_app_config, setup_locales, setup_workbench, setup_database, + setup_storage) +from mediagoblin.tools.pluginapi import PluginManager, hook_transform +from mediagoblin.tools.crypto import setup_crypto + + +_log = logging.getLogger(__name__) + + +class MediaGoblinApp(object): + """ + WSGI application of MediaGoblin + + ... this is the heart of the program! + """ + def __init__(self, config_path, setup_celery=True): + """ + Initialize the application based on a configuration file. + + Arguments: + - config_path: path to the configuration file we're opening. + - setup_celery: whether or not to setup celery during init. + (Note: setting 'celery_setup_elsewhere' also disables + setting up celery.) + """ + _log.info("GNU MediaGoblin %s main server starting", __version__) + _log.debug("Using config file %s", config_path) + ############## + # Setup config + ############## + + # Open and setup the config + global_config, app_config = setup_global_and_app_config(config_path) + + setup_crypto() + + ########################################## + # Setup other connections / useful objects + ########################################## + + # Setup Session Manager, not needed in celery + self.session_manager = session.SessionManager() + + # load all available locales + setup_locales() + + # Set up plugins -- need to do this early so that plugins can + # affect startup. + _log.info("Setting up plugins.") + setup_plugins() + + # Set up the database + self.db = setup_database() + + # Register themes + self.theme_registry, self.current_theme = register_themes(app_config) + + # Get the template environment + self.template_loader = get_jinja_loader( + app_config.get('local_templates'), + self.current_theme, + PluginManager().get_template_paths() + ) + + # Set up storage systems + self.public_store, self.queue_store = setup_storage() + + # set up routing + self.url_map = get_url_map() + + # set up staticdirector tool + self.staticdirector = get_staticdirector(app_config) + + # Setup celery, if appropriate + if setup_celery and not app_config.get('celery_setup_elsewhere'): + if os.environ.get('CELERY_ALWAYS_EAGER', 'false').lower() == 'true': + setup_celery_from_config( + app_config, global_config, + force_celery_always_eager=True) + else: + setup_celery_from_config(app_config, global_config) + + ####################################################### + # Insert appropriate things into mediagoblin.mg_globals + # + # certain properties need to be accessed globally eg from + # validators, etc, which might not access to the request + # object. + ####################################################### + + setup_globals(app=self) + + # Workbench *currently* only used by celery, so this only + # matters in always eager mode :) + setup_workbench() + + # instantiate application meddleware + self.meddleware = [common.import_component(m)(self) + for m in meddleware.ENABLED_MEDDLEWARE] + + def call_backend(self, environ, start_response): + request = Request(environ) + + # Compatibility with django, use request.args preferrably + request.GET = request.args + + ## Routing / controller loading stuff + map_adapter = self.url_map.bind_to_environ(request.environ) + + # By using fcgi, mediagoblin can run under a base path + # like /mediagoblin/. request.path_info contains the + # path inside mediagoblin. If the something needs the + # full path of the current page, that should include + # the basepath. + # Note: urlgen and routes are fine! + request.full_path = environ["SCRIPT_NAME"] + request.path + # python-routes uses SCRIPT_NAME. So let's use that too. + # The other option would be: + # request.full_path = environ["SCRIPT_URL"] + + # Fix up environ for urlgen + # See bug: https://bitbucket.org/bbangert/routes/issue/55/cache_hostinfo-breaks-on-https-off + if environ.get('HTTPS', '').lower() == 'off': + environ.pop('HTTPS') + + ## Attach utilities to the request object + # Do we really want to load this via middleware? Maybe? + session_manager = self.session_manager + request.session = session_manager.load_session_from_cookie(request) + # Attach self as request.app + # Also attach a few utilities from request.app for convenience? + request.app = self + + request.db = self.db + request.staticdirect = self.staticdirector + + request.locale = translate.get_locale_from_request(request) + request.template_env = template.get_jinja_env( + self.template_loader, request.locale) + + def build_proxy(endpoint, **kw): + try: + qualified = kw.pop('qualified') + except KeyError: + qualified = False + + return map_adapter.build( + endpoint, + values=dict(**kw), + force_external=qualified) + + request.urlgen = build_proxy + + mg_request.setup_user_in_request(request) + + request.controller_name = None + try: + found_rule, url_values = map_adapter.match(return_rule=True) + request.matchdict = url_values + except RequestRedirect as response: + # Deal with 301 responses eg due to missing final slash + return response(environ, start_response) + except HTTPException as exc: + # Stop and render exception + return render_http_exception( + request, exc, + exc.get_description(environ))(environ, start_response) + + controller = endpoint_to_controller(found_rule) + # Make a reference to the controller's symbolic name on the request... + # used for lazy context modification + request.controller_name = found_rule.endpoint + + # pass the request through our meddleware classes + try: + for m in self.meddleware: + response = m.process_request(request, controller) + if response is not None: + return response(environ, start_response) + except HTTPException as e: + return render_http_exception( + request, e, + e.get_description(environ))(environ, start_response) + + request.start_response = start_response + + # get the Http response from the controller + try: + response = controller(request) + except HTTPException as e: + response = render_http_exception( + request, e, e.get_description(environ)) + + # pass the response through the meddlewares + try: + for m in self.meddleware[::-1]: + m.process_response(request, response) + except HTTPException as e: + response = render_http_exception( + request, e, e.get_description(environ)) + + session_manager.save_session_to_cookie(request.session, + request, response) + + return response(environ, start_response) + + def __call__(self, environ, start_response): + ## If more errors happen that look like unclean sessions: + # self.db.check_session_clean() + + try: + return self.call_backend(environ, start_response) + finally: + # Reset the sql session, so that the next request + # gets a fresh session + self.db.reset_after_request() + + +def paste_app_factory(global_config, **app_config): + configs = app_config['config'].split() + mediagoblin_config = None + for config in configs: + if os.path.exists(config) and os.access(config, os.R_OK): + mediagoblin_config = config + break + + if not mediagoblin_config: + raise IOError("Usable mediagoblin config not found.") + + mgoblin_app = MediaGoblinApp(mediagoblin_config) + mgoblin_app = hook_transform('wrap_wsgi', mgoblin_app) + + return mgoblin_app diff --git a/mediagoblin/auth/__init__.py b/mediagoblin/auth/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/auth/__init__.py @@ -0,0 +1,15 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/mediagoblin/auth/forms.py b/mediagoblin/auth/forms.py new file mode 100644 index 00000000..0a391d67 --- /dev/null +++ b/mediagoblin/auth/forms.py @@ -0,0 +1,66 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import wtforms + +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ +from mediagoblin.auth.tools import normalize_user_or_email_field + + +class RegistrationForm(wtforms.Form): + username = wtforms.TextField( + _('Username'), + [wtforms.validators.Required(), + normalize_user_or_email_field(allow_email=False)]) + password = wtforms.PasswordField( + _('Password'), + [wtforms.validators.Required(), + wtforms.validators.Length(min=5, max=1024)]) + email = wtforms.TextField( + _('Email address'), + [wtforms.validators.Required(), + normalize_user_or_email_field(allow_user=False)]) + + +class LoginForm(wtforms.Form): + username = wtforms.TextField( + _('Username or Email'), + [wtforms.validators.Required(), + normalize_user_or_email_field()]) + password = wtforms.PasswordField( + _('Password'), + [wtforms.validators.Required(), + wtforms.validators.Length(min=5, max=1024)]) + + +class ForgotPassForm(wtforms.Form): + username = wtforms.TextField( + _('Username or email'), + [wtforms.validators.Required(), + normalize_user_or_email_field()]) + + +class ChangePassForm(wtforms.Form): + password = wtforms.PasswordField( + 'Password', + [wtforms.validators.Required(), + wtforms.validators.Length(min=5, max=1024)]) + userid = wtforms.HiddenField( + '', + [wtforms.validators.Required()]) + token = wtforms.HiddenField( + '', + [wtforms.validators.Required()]) diff --git a/mediagoblin/auth/lib.py b/mediagoblin/auth/lib.py new file mode 100644 index 00000000..bfc36b28 --- /dev/null +++ b/mediagoblin/auth/lib.py @@ -0,0 +1,120 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import random + +import bcrypt + +from mediagoblin.tools.mail import send_email +from mediagoblin.tools.template import render_template +from mediagoblin import mg_globals + + +def bcrypt_check_password(raw_pass, stored_hash, extra_salt=None): + """ + Check to see if this password matches. + + Args: + - raw_pass: user submitted password to check for authenticity. + - stored_hash: The hash of the raw password (and possibly extra + salt) to check against + - extra_salt: (optional) If this password is with stored with a + non-database extra salt (probably in the config file) for extra + security, factor this into the check. + + Returns: + True or False depending on success. + """ + if extra_salt: + raw_pass = u"%s:%s" % (extra_salt, raw_pass) + + hashed_pass = bcrypt.hashpw(raw_pass.encode('utf-8'), stored_hash) + + # Reduce risk of timing attacks by hashing again with a random + # number (thx to zooko on this advice, which I hopefully + # incorporated right.) + # + # See also: + rand_salt = bcrypt.gensalt(5) + randplus_stored_hash = bcrypt.hashpw(stored_hash, rand_salt) + randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt) + + return randplus_stored_hash == randplus_hashed_pass + + +def bcrypt_gen_password_hash(raw_pass, extra_salt=None): + """ + Generate a salt for this new password. + + Args: + - raw_pass: user submitted password + - extra_salt: (optional) If this password is with stored with a + non-database extra salt + """ + if extra_salt: + raw_pass = u"%s:%s" % (extra_salt, raw_pass) + + return unicode( + bcrypt.hashpw(raw_pass.encode('utf-8'), bcrypt.gensalt())) + + +def fake_login_attempt(): + """ + Pretend we're trying to login. + + Nothing actually happens here, we're just trying to take up some + time, approximately the same amount of time as + bcrypt_check_password, so as to avoid figuring out what users are + on the system by intentionally faking logins a bunch of times. + """ + rand_salt = bcrypt.gensalt(5) + + hashed_pass = bcrypt.hashpw(str(random.random()), rand_salt) + + randplus_stored_hash = bcrypt.hashpw(str(random.random()), rand_salt) + randplus_hashed_pass = bcrypt.hashpw(hashed_pass, rand_salt) + + randplus_stored_hash == randplus_hashed_pass + + +EMAIL_FP_VERIFICATION_TEMPLATE = ( + u"http://{host}{uri}?" + u"userid={userid}&token={fp_verification_key}") + + +def send_fp_verification_email(user, request): + """ + Send the verification email to users to change their password. + + Args: + - user: a user object + - request: the request + """ + rendered_email = render_template( + request, 'mediagoblin/auth/fp_verification_email.txt', + {'username': user.username, + 'verification_url': EMAIL_FP_VERIFICATION_TEMPLATE.format( + host=request.host, + uri=request.urlgen('mediagoblin.auth.verify_forgot_password'), + userid=unicode(user.id), + fp_verification_key=user.fp_verification_key)}) + + # TODO: There is no error handling in place + send_email( + mg_globals.app_config['email_sender_address'], + [user.email], + 'GNU MediaGoblin - Change forgotten password!', + rendered_email) diff --git a/mediagoblin/auth/routing.py b/mediagoblin/auth/routing.py new file mode 100644 index 00000000..2a6abb47 --- /dev/null +++ b/mediagoblin/auth/routing.py @@ -0,0 +1,33 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +auth_routes = [ + ('mediagoblin.auth.register', '/register/', + 'mediagoblin.auth.views:register'), + ('mediagoblin.auth.login', '/login/', + 'mediagoblin.auth.views:login'), + ('mediagoblin.auth.logout', '/logout/', + 'mediagoblin.auth.views:logout'), + ('mediagoblin.auth.verify_email', '/verify_email/', + 'mediagoblin.auth.views:verify_email'), + ('mediagoblin.auth.resend_verification', '/resend_verification/', + 'mediagoblin.auth.views:resend_activation'), + ('mediagoblin.auth.forgot_password', '/forgot_password/', + 'mediagoblin.auth.views:forgot_password'), + ('mediagoblin.auth.verify_forgot_password', + '/forgot_password/verify/', + 'mediagoblin.auth.views:verify_forgot_password')] diff --git a/mediagoblin/auth/tools.py b/mediagoblin/auth/tools.py new file mode 100644 index 00000000..db6b6e37 --- /dev/null +++ b/mediagoblin/auth/tools.py @@ -0,0 +1,159 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import uuid +import logging + +import wtforms +from sqlalchemy import or_ + +from mediagoblin import mg_globals +from mediagoblin.auth import lib as auth_lib +from mediagoblin.db.models import User +from mediagoblin.tools.mail import (normalize_email, send_email, + email_debug_message) +from mediagoblin.tools.template import render_template +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +_log = logging.getLogger(__name__) + + +def normalize_user_or_email_field(allow_email=True, allow_user=True): + """ + Check if we were passed a field that matches a username and/or email + pattern. + + This is useful for fields that can take either a username or email + address. Use the parameters if you want to only allow a username for + instance""" + message = _(u'Invalid User name or email address.') + nomail_msg = _(u"This field does not take email addresses.") + nouser_msg = _(u"This field requires an email address.") + + def _normalize_field(form, field): + email = u'@' in field.data + if email: # normalize email address casing + if not allow_email: + raise wtforms.ValidationError(nomail_msg) + wtforms.validators.Email()(form, field) + field.data = normalize_email(field.data) + else: # lower case user names + if not allow_user: + raise wtforms.ValidationError(nouser_msg) + wtforms.validators.Length(min=3, max=30)(form, field) + wtforms.validators.Regexp(r'^\w+$')(form, field) + field.data = field.data.lower() + if field.data is None: # should not happen, but be cautious anyway + raise wtforms.ValidationError(message) + return _normalize_field + + +EMAIL_VERIFICATION_TEMPLATE = ( + u"http://{host}{uri}?" + u"userid={userid}&token={verification_key}") + + +def send_verification_email(user, request): + """ + Send the verification email to users to activate their accounts. + + Args: + - user: a user object + - request: the request + """ + rendered_email = render_template( + request, 'mediagoblin/auth/verification_email.txt', + {'username': user.username, + 'verification_url': EMAIL_VERIFICATION_TEMPLATE.format( + host=request.host, + uri=request.urlgen('mediagoblin.auth.verify_email'), + userid=unicode(user.id), + verification_key=user.verification_key)}) + + # TODO: There is no error handling in place + send_email( + mg_globals.app_config['email_sender_address'], + [user.email], + # TODO + # Due to the distributed nature of GNU MediaGoblin, we should + # find a way to send some additional information about the + # specific GNU MediaGoblin instance in the subject line. For + # example "GNU MediaGoblin @ Wandborg - [...]". + 'GNU MediaGoblin - Verify your email!', + rendered_email) + + +def basic_extra_validation(register_form, *args): + users_with_username = User.query.filter_by( + username=register_form.data['username']).count() + users_with_email = User.query.filter_by( + email=register_form.data['email']).count() + + extra_validation_passes = True + + if users_with_username: + register_form.username.errors.append( + _(u'Sorry, a user with that name already exists.')) + extra_validation_passes = False + if users_with_email: + register_form.email.errors.append( + _(u'Sorry, a user with that email address already exists.')) + extra_validation_passes = False + + return extra_validation_passes + + +def register_user(request, register_form): + """ Handle user registration """ + extra_validation_passes = basic_extra_validation(register_form) + + if extra_validation_passes: + # Create the user + user = User() + user.username = register_form.data['username'] + user.email = register_form.data['email'] + user.pw_hash = auth_lib.bcrypt_gen_password_hash( + register_form.password.data) + user.verification_key = unicode(uuid.uuid4()) + user.save() + + # log the user in + request.session['user_id'] = unicode(user.id) + request.session.save() + + # send verification email + email_debug_message(request) + send_verification_email(user, request) + + return user + + return None + + +def check_login_simple(username, password, username_might_be_email=False): + search = (User.username == username) + if username_might_be_email and ('@' in username): + search = or_(search, User.email == username) + user = User.query.filter(search).first() + if not user: + _log.info("User %r not found", username) + auth_lib.fake_login_attempt() + return None + if not auth_lib.bcrypt_check_password(password, user.pw_hash): + _log.warn("Wrong password for %r", username) + return None + _log.info("Logging %r in", username) + return user diff --git a/mediagoblin/auth/views.py b/mediagoblin/auth/views.py new file mode 100644 index 00000000..bb7bda77 --- /dev/null +++ b/mediagoblin/auth/views.py @@ -0,0 +1,319 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import uuid +import datetime + +from mediagoblin import messages, mg_globals +from mediagoblin.db.models import User +from mediagoblin.tools.response import render_to_response, redirect, render_404 +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.tools.mail import email_debug_message +from mediagoblin.auth import lib as auth_lib +from mediagoblin.auth import forms as auth_forms +from mediagoblin.auth.lib import send_fp_verification_email +from mediagoblin.auth.tools import (send_verification_email, register_user, + check_login_simple) + + +def register(request): + """The registration view. + + Note that usernames will always be lowercased. Email domains are lowercased while + the first part remains case-sensitive. + """ + # Redirects to indexpage if registrations are disabled + if not mg_globals.app_config["allow_registration"]: + messages.add_message( + request, + messages.WARNING, + _('Sorry, registration is disabled on this instance.')) + return redirect(request, "index") + + register_form = auth_forms.RegistrationForm(request.form) + + if request.method == 'POST' and register_form.validate(): + # TODO: Make sure the user doesn't exist already + user = register_user(request, register_form) + + if user: + # redirect the user to their homepage... there will be a + # message waiting for them to verify their email + return redirect( + request, 'mediagoblin.user_pages.user_home', + user=user.username) + + return render_to_response( + request, + 'mediagoblin/auth/register.html', + {'register_form': register_form}) + + +def login(request): + """ + MediaGoblin login view. + + If you provide the POST with 'next', it'll redirect to that view. + """ + login_form = auth_forms.LoginForm(request.form) + + login_failed = False + + if request.method == 'POST': + + username = login_form.data['username'] + + if login_form.validate(): + user = check_login_simple(username, login_form.password.data, True) + + if user: + # set up login in session + request.session['user_id'] = unicode(user.id) + request.session.save() + + if request.form.get('next'): + return redirect(request, location=request.form['next']) + else: + return redirect(request, "index") + + login_failed = True + + return render_to_response( + request, + 'mediagoblin/auth/login.html', + {'login_form': login_form, + 'next': request.GET.get('next') or request.form.get('next'), + 'login_failed': login_failed, + 'allow_registration': mg_globals.app_config["allow_registration"]}) + + +def logout(request): + # Maybe deleting the user_id parameter would be enough? + request.session.delete() + + return redirect(request, "index") + + +def verify_email(request): + """ + Email verification view + + validates GET parameters against database and unlocks the user account, if + you are lucky :) + """ + # If we don't have userid and token parameters, we can't do anything; 404 + if not 'userid' in request.GET or not 'token' in request.GET: + return render_404(request) + + user = User.query.filter_by(id=request.args['userid']).first() + + if user and user.verification_key == unicode(request.GET['token']): + user.status = u'active' + user.email_verified = True + user.verification_key = None + + user.save() + + messages.add_message( + request, + messages.SUCCESS, + _("Your email address has been verified. " + "You may now login, edit your profile, and submit images!")) + else: + messages.add_message( + request, + messages.ERROR, + _('The verification key or user id is incorrect')) + + return redirect( + request, 'mediagoblin.user_pages.user_home', + user=user.username) + + +def resend_activation(request): + """ + The reactivation view + + Resend the activation email. + """ + + if request.user is None: + messages.add_message( + request, + messages.ERROR, + _('You must be logged in so we know who to send the email to!')) + + return redirect(request, 'mediagoblin.auth.login') + + if request.user.email_verified: + messages.add_message( + request, + messages.ERROR, + _("You've already verified your email address!")) + + return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username']) + + request.user.verification_key = unicode(uuid.uuid4()) + request.user.save() + + email_debug_message(request) + send_verification_email(request.user, request) + + messages.add_message( + request, + messages.INFO, + _('Resent your verification email.')) + return redirect( + request, 'mediagoblin.user_pages.user_home', + user=request.user.username) + + +def forgot_password(request): + """ + Forgot password view + + Sends an email with an url to renew forgotten password. + Use GET querystring parameter 'username' to pre-populate the input field + """ + fp_form = auth_forms.ForgotPassForm(request.form, + username=request.args.get('username')) + + if not (request.method == 'POST' and fp_form.validate()): + # Either GET request, or invalid form submitted. Display the template + return render_to_response(request, + 'mediagoblin/auth/forgot_password.html', {'fp_form': fp_form}) + + # If we are here: method == POST and form is valid. username casing + # has been sanitized. Store if a user was found by email. We should + # not reveal if the operation was successful then as we don't want to + # leak if an email address exists in the system. + found_by_email = '@' in fp_form.username.data + + if found_by_email: + user = User.query.filter_by( + email = fp_form.username.data).first() + # Don't reveal success in case the lookup happened by email address. + success_message=_("If that email address (case sensitive!) is " + "registered an email has been sent with instructions " + "on how to change your password.") + + else: # found by username + user = User.query.filter_by( + username = fp_form.username.data).first() + + if user is None: + messages.add_message(request, + messages.WARNING, + _("Couldn't find someone with that username.")) + return redirect(request, 'mediagoblin.auth.forgot_password') + + success_message=_("An email has been sent with instructions " + "on how to change your password.") + + if user and not(user.email_verified and user.status == 'active'): + # Don't send reminder because user is inactive or has no verified email + messages.add_message(request, + messages.WARNING, + _("Could not send password recovery email as your username is in" + "active or your account's email address has not been verified.")) + + return redirect(request, 'mediagoblin.user_pages.user_home', + user=user.username) + + # SUCCESS. Send reminder and return to login page + if user: + user.fp_verification_key = unicode(uuid.uuid4()) + user.fp_token_expire = datetime.datetime.now() + \ + datetime.timedelta(days=10) + user.save() + + email_debug_message(request) + send_fp_verification_email(user, request) + + messages.add_message(request, messages.INFO, success_message) + return redirect(request, 'mediagoblin.auth.login') + + +def verify_forgot_password(request): + """ + Check the forgot-password verification and possibly let the user + change their password because of it. + """ + # get form data variables, and specifically check for presence of token + formdata = _process_for_token(request) + if not formdata['has_userid_and_token']: + return render_404(request) + + formdata_token = formdata['vars']['token'] + formdata_userid = formdata['vars']['userid'] + formdata_vars = formdata['vars'] + + # check if it's a valid user id + user = User.query.filter_by(id=formdata_userid).first() + if not user: + return render_404(request) + + # check if we have a real user and correct token + if ((user and user.fp_verification_key and + user.fp_verification_key == unicode(formdata_token) and + datetime.datetime.now() < user.fp_token_expire + and user.email_verified and user.status == 'active')): + + cp_form = auth_forms.ChangePassForm(formdata_vars) + + if request.method == 'POST' and cp_form.validate(): + user.pw_hash = auth_lib.bcrypt_gen_password_hash( + cp_form.password.data) + user.fp_verification_key = None + user.fp_token_expire = None + user.save() + + messages.add_message( + request, + messages.INFO, + _("You can now log in using your new password.")) + return redirect(request, 'mediagoblin.auth.login') + else: + return render_to_response( + request, + 'mediagoblin/auth/change_fp.html', + {'cp_form': cp_form}) + + # in case there is a valid id but no user with that id in the db + # or the token expired + else: + return render_404(request) + + +def _process_for_token(request): + """ + Checks for tokens in formdata without prior knowledge of request method + + For now, returns whether the userid and token formdata variables exist, and + the formdata variables in a hash. Perhaps an object is warranted? + """ + # retrieve the formdata variables + if request.method == 'GET': + formdata_vars = request.GET + else: + formdata_vars = request.form + + formdata = { + 'vars': formdata_vars, + 'has_userid_and_token': + 'userid' in formdata_vars and 'token' in formdata_vars} + + return formdata diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini new file mode 100644 index 00000000..b213970d --- /dev/null +++ b/mediagoblin/config_spec.ini @@ -0,0 +1,191 @@ +[mediagoblin] +# HTML title of the pages +html_title = string(default="GNU MediaGoblin") + +# link to source for this MediaGoblin site +source_link = string(default="https://gitorious.org/mediagoblin/mediagoblin") + +# Enabled media types +media_types = string_list(default=list("mediagoblin.media_types.image")) + +# database stuff +sql_engine = string(default="sqlite:///%(here)s/mediagoblin.db") + +# Where temporary files used in processing and etc are kept +workbench_path = string(default="%(here)s/user_dev/media/workbench") + +# Where to store cryptographic sensible data +crypto_path = string(default="%(here)s/user_dev/crypto") + +# Where mediagoblin-builtin static assets are kept +direct_remote_path = string(default="/mgoblin_static/") + +# set to false to enable sending notices +email_debug_mode = boolean(default=True) +email_sender_address = string(default="notice@mediagoblin.example.org") +email_smtp_host = string(default='') +email_smtp_port = integer(default=25) +email_smtp_user = string(default=None) +email_smtp_pass = string(default=None) + +# Set to false to disable registrations +allow_registration = boolean(default=True) + +# tag parsing +tags_max_length = integer(default=255) + +# Enable/disable comments +allow_comments = boolean(default=True) + +# Whether comments are ascending or descending +comments_ascending = boolean(default=True) + +# By default not set, but you might want something like: +# "%(here)s/user_dev/templates/" +local_templates = string() + +# Whether or not celery is set up via an environment variable or +# something else (and thus mediagoblin should not attempt to set it up +# itself) +celery_setup_elsewhere = boolean(default=False) + +# Whether or not users are able to upload files of any filetype with +# their media entries -- This is useful if you want to provide the +# source files for a media file but can also be a HUGE security risk. +allow_attachments = boolean(default=False) + +# Cookie stuff +csrf_cookie_name = string(default='mediagoblin_csrftoken') + +# Push stuff +push_urls = string_list(default=list()) + +exif_visible = boolean(default=False) +original_date_visible = boolean(default=False) + +# Theming stuff +theme_install_dir = string(default="%(here)s/user_dev/themes/") +theme_web_path = string(default="/theme_static/") +theme_linked_assets_dir = string(default="%(here)s/user_dev/theme_static/") +theme = string() + +# plugin default assets directory +plugin_web_path = string(default="/plugin_static/") +plugin_linked_assets_dir = string(default="%(here)s/user_dev/plugin_static/") + + +[storage:publicstore] +storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage") +base_dir = string(default="%(here)s/user_dev/media/public") +base_url = string(default="/mgoblin_media/") + +[storage:queuestore] +storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage") +base_dir = string(default="%(here)s/user_dev/media/queue") + +[media:medium] +# Dimensions used when creating media display images. +max_width = integer(default=640) +max_height = integer(default=640) + +[media:thumb] +# Dimensions used when creating media thumbnails +# This is unfortunately not implemented in the media +# types yet. You can help! +# TODO: Make plugins follow the media size settings +max_width = integer(default=180) +max_height = integer(default=180) + +[media_type:mediagoblin.media_types.image] +# One of BICUBIC, BILINEAR, NEAREST, ANTIALIAS +resize_filter = string(default="ANTIALIAS") +#level of compression used when resizing images +quality = integer(default=90) + +[media_type:mediagoblin.media_types.video] +# Should we keep the original file? +keep_original = boolean(default=False) + +# 0 means autodetect, autodetect means number_of_CPUs - 1 +vp8_threads = integer(default=0) +# Range: 0..10 +vp8_quality = integer(default=8) +# Range: -0.1..1 +vorbis_quality = float(default=0.3) + +# Autoplay the video when page is loaded? +auto_play = boolean(default=True) + +[[skip_transcode]] +mime_types = string_list(default=list("video/webm")) +container_formats = string_list(default=list("Matroska")) +video_codecs = string_list(default=list("VP8 video")) +audio_codecs = string_list(default=list("Vorbis")) +dimensions_match = boolean(default=True) + +[media_type:mediagoblin.media_types.audio] +keep_original = boolean(default=True) +# vorbisenc quality +quality = float(default=0.3) +create_spectrogram = boolean(default=True) +spectrogram_fft_size = integer(default=4096) + +[media_type:mediagoblin.media_types.ascii] +thumbnail_font = string(default=None) + +[media_type:mediagoblin.media_types.pdf] +pdf_js = boolean(default=True) + + +[celery] +# default result stuff +CELERY_RESULT_BACKEND = string(default="database") +CELERY_RESULT_DBURI = string(default="sqlite:///%(here)s/celery.db") + +# default kombu stuff +BROKER_TRANSPORT = string(default="sqlalchemy") +BROKER_HOST = string(default="sqlite:///%(here)s/kombu.db") + +# known booleans +CELERY_RESULT_PERSISTENT = boolean() +CELERY_CREATE_MISSING_QUEUES = boolean() +BROKER_USE_SSL = boolean() +BROKER_CONNECTION_RETRY = boolean() +CELERY_ALWAYS_EAGER = boolean() +CELERY_EAGER_PROPAGATES_EXCEPTIONS = boolean() +CELERY_IGNORE_RESULT = boolean() +CELERY_TRACK_STARTED = boolean() +CELERY_DISABLE_RATE_LIMITS = boolean() +CELERY_ACKS_LATE = boolean() +CELERY_STORE_ERRORS_EVEN_IF_IGNORED = boolean() +CELERY_SEND_TASK_ERROR_EMAILS = boolean() +CELERY_SEND_EVENTS = boolean() +CELERY_SEND_TASK_SENT_EVENT = boolean() +CELERYD_LOG_COLOR = boolean() +CELERY_REDIRECT_STDOUTS = boolean() + +# known ints +CELERYD_CONCURRENCY = integer() +CELERYD_PREFETCH_MULTIPLIER = integer() +CELERY_AMQP_TASK_RESULT_EXPIRES = integer() +CELERY_AMQP_TASK_RESULT_CONNECTION_MAX = integer() +REDIS_PORT = integer() +REDIS_DB = integer() +BROKER_PORT = integer() +BROKER_CONNECTION_TIMEOUT = integer() +CELERY_BROKER_CONNECTION_MAX_RETRIES = integer() +CELERY_TASK_RESULT_EXPIRES = integer() +CELERY_MAX_CACHED_RESULTS = integer() +CELERY_DEFAULT_RATE_LIMIT = integer() +CELERYD_MAX_TASKS_PER_CHILD = integer() +CELERYD_TASK_TIME_LIMIT = integer() +CELERYD_TASK_SOFT_TIME_LIMIT = integer() +MAIL_PORT = integer() +CELERYBEAT_MAX_LOOP_INTERVAL = integer() + +# known floats +CELERYD_ETA_SCHEDULER_PRECISION = float() + +# known lists +CELERY_ROUTES = string_list() +CELERY_IMPORTS = string_list() diff --git a/mediagoblin/db/__init__.py b/mediagoblin/db/__init__.py new file mode 100644 index 00000000..27ca4b06 --- /dev/null +++ b/mediagoblin/db/__init__.py @@ -0,0 +1,49 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Database Abstraction/Wrapper Layer +================================== + +This submodule is for most of the db specific stuff. + +There are two main ideas here: + +1. Open up a small possibility to replace mongo by another + db. This means, that all direct mongo accesses should + happen in the db submodule. While all the rest uses an + API defined by this submodule. + + Currently this API happens to be basicly mongo. + Which means, that the abstraction/wrapper layer is + extremely thin. + +2. Give the rest of the app a simple and easy way to get most of + their db needs. Which often means some simple import + from db.util. + +What does that mean? + +* Never import mongo directly outside of this submodule. + +* Inside this submodule you can do whatever is needed. The + API border is exactly at the submodule layer. Nowhere + else. + +* helper functions can be moved in here. They become part + of the db.* API + +""" diff --git a/mediagoblin/db/base.py b/mediagoblin/db/base.py new file mode 100644 index 00000000..699a503a --- /dev/null +++ b/mediagoblin/db/base.py @@ -0,0 +1,78 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import scoped_session, sessionmaker, object_session + +Session = scoped_session(sessionmaker()) + + +class GMGTableBase(object): + query = Session.query_property() + + @classmethod + def find(cls, query_dict): + return cls.query.filter_by(**query_dict) + + @classmethod + def find_one(cls, query_dict): + return cls.query.filter_by(**query_dict).first() + + @classmethod + def one(cls, query_dict): + return cls.find(query_dict).one() + + def get(self, key): + return getattr(self, key) + + def setdefault(self, key, defaultvalue): + # The key *has* to exist on sql. + return getattr(self, key) + + def save(self): + sess = object_session(self) + if sess is None: + sess = Session() + sess.add(self) + sess.commit() + + def delete(self, commit=True): + """Delete the object and commit the change immediately by default""" + sess = object_session(self) + assert sess is not None, "Not going to delete detached %r" % self + sess.delete(self) + if commit: + sess.commit() + + +Base = declarative_base(cls=GMGTableBase) + + +class DictReadAttrProxy(object): + """ + Maps read accesses to obj['key'] to obj.key + and hides all the rest of the obj + """ + def __init__(self, proxied_obj): + self.proxied_obj = proxied_obj + + def __getitem__(self, key): + try: + return getattr(self.proxied_obj, key) + except AttributeError: + raise KeyError("%r is not an attribute on %r" + % (key, self.proxied_obj)) diff --git a/mediagoblin/db/extratypes.py b/mediagoblin/db/extratypes.py new file mode 100644 index 00000000..f2304af0 --- /dev/null +++ b/mediagoblin/db/extratypes.py @@ -0,0 +1,63 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from sqlalchemy.types import TypeDecorator, Unicode, TEXT +import json + + +class PathTupleWithSlashes(TypeDecorator): + "Represents a Tuple of strings as a slash separated string." + + impl = Unicode + + def process_bind_param(self, value, dialect): + if value is not None: + if len(value) == 0: + value = None + else: + value = '/'.join(value) + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = tuple(value.split('/')) + return value + + +# The following class and only this one class is in very +# large parts based on example code from sqlalchemy. +# +# The original copyright notice and license follows: +# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file> +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php +# +class JSONEncoded(TypeDecorator): + "Represents an immutable structure as a json-encoded string." + + impl = TEXT + + def process_bind_param(self, value, dialect): + if value is not None: + value = json.dumps(value) + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value diff --git a/mediagoblin/db/migration_tools.py b/mediagoblin/db/migration_tools.py new file mode 100644 index 00000000..c0c7e998 --- /dev/null +++ b/mediagoblin/db/migration_tools.py @@ -0,0 +1,276 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.common import simple_printer +from sqlalchemy import Table + +class TableAlreadyExists(Exception): + pass + + +class MigrationManager(object): + """ + Migration handling tool. + + Takes information about a database, lets you update the database + to the latest migrations, etc. + """ + + def __init__(self, name, models, migration_registry, session, + printer=simple_printer): + """ + Args: + - name: identifier of this section of the database + - session: session we're going to migrate + - migration_registry: where we should find all migrations to + run + """ + self.name = unicode(name) + self.models = models + self.session = session + self.migration_registry = migration_registry + self._sorted_migrations = None + self.printer = printer + + # For convenience + from mediagoblin.db.models import MigrationData + + self.migration_model = MigrationData + self.migration_table = MigrationData.__table__ + + @property + def sorted_migrations(self): + """ + Sort migrations if necessary and store in self._sorted_migrations + """ + if not self._sorted_migrations: + self._sorted_migrations = sorted( + self.migration_registry.items(), + # sort on the key... the migration number + key=lambda migration_tuple: migration_tuple[0]) + + return self._sorted_migrations + + @property + def migration_data(self): + """ + Get the migration row associated with this object, if any. + """ + return self.session.query( + self.migration_model).filter_by(name=self.name).first() + + @property + def latest_migration(self): + """ + Return a migration number for the latest migration, or 0 if + there are no migrations. + """ + if self.sorted_migrations: + return self.sorted_migrations[-1][0] + else: + # If no migrations have been set, we start at 0. + return 0 + + @property + def database_current_migration(self): + """ + Return the current migration in the database. + """ + # If the table doesn't even exist, return None. + if not self.migration_table.exists(self.session.bind): + return None + + # Also return None if self.migration_data is None. + if self.migration_data is None: + return None + + return self.migration_data.version + + def set_current_migration(self, migration_number=None): + """ + Set the migration in the database to migration_number + (or, the latest available) + """ + self.migration_data.version = migration_number or self.latest_migration + self.session.commit() + + def migrations_to_run(self): + """ + Get a list of migrations to run still, if any. + + Note that this will fail if there's no migration record for + this class! + """ + assert self.database_current_migration is not None + + db_current_migration = self.database_current_migration + + return [ + (migration_number, migration_func) + for migration_number, migration_func in self.sorted_migrations + if migration_number > db_current_migration] + + + def init_tables(self): + """ + Create all tables relative to this package + """ + # sanity check before we proceed, none of these should be created + for model in self.models: + # Maybe in the future just print out a "Yikes!" or something? + if model.__table__.exists(self.session.bind): + raise TableAlreadyExists( + u"Intended to create table '%s' but it already exists" % + model.__table__.name) + + self.migration_model.metadata.create_all( + self.session.bind, + tables=[model.__table__ for model in self.models]) + + def create_new_migration_record(self): + """ + Create a new migration record for this migration set + """ + migration_record = self.migration_model( + name=self.name, + version=self.latest_migration) + self.session.add(migration_record) + self.session.commit() + + def dry_run(self): + """ + Print out a dry run of what we would have upgraded. + """ + if self.database_current_migration is None: + self.printer( + u'~> Woulda initialized: %s\n' % self.name_for_printing()) + return u'inited' + + migrations_to_run = self.migrations_to_run() + if migrations_to_run: + self.printer( + u'~> Woulda updated %s:\n' % self.name_for_printing()) + + for migration_number, migration_func in migrations_to_run(): + self.printer( + u' + Would update %s, "%s"\n' % ( + migration_number, migration_func.func_name)) + + return u'migrated' + + def name_for_printing(self): + if self.name == u'__main__': + return u"main mediagoblin tables" + else: + # TODO: Use the friendlier media manager "human readable" name + return u'media type "%s"' % self.name + + def init_or_migrate(self): + """ + Initialize the database or migrate if appropriate. + + Returns information about whether or not we initialized + ('inited'), migrated ('migrated'), or did nothing (None) + """ + assure_migrations_table_setup(self.session) + + # Find out what migration number, if any, this database data is at, + # and what the latest is. + migration_number = self.database_current_migration + + # Is this our first time? Is there even a table entry for + # this identifier? + # If so: + # - create all tables + # - create record in migrations registry + # - print / inform the user + # - return 'inited' + if migration_number is None: + self.printer(u"-> Initializing %s... " % self.name_for_printing()) + + self.init_tables() + # auto-set at latest migration number + self.create_new_migration_record() + + self.printer(u"done.\n") + self.set_current_migration() + return u'inited' + + # Run migrations, if appropriate. + migrations_to_run = self.migrations_to_run() + if migrations_to_run: + self.printer( + u'-> Updating %s:\n' % self.name_for_printing()) + for migration_number, migration_func in migrations_to_run: + self.printer( + u' + Running migration %s, "%s"... ' % ( + migration_number, migration_func.func_name)) + migration_func(self.session) + self.set_current_migration(migration_number) + self.printer('done.\n') + + return u'migrated' + + # Otherwise return None. Well it would do this anyway, but + # for clarity... ;) + return None + + +class RegisterMigration(object): + """ + Tool for registering migrations + + Call like: + + @RegisterMigration(33) + def update_dwarves(database): + [...] + + This will register your migration with the default migration + registry. Alternately, to specify a very specific + migration_registry, you can pass in that as the second argument. + + Note, the number of your migration should NEVER be 0 or less than + 0. 0 is the default "no migrations" state! + """ + def __init__(self, migration_number, migration_registry): + assert migration_number > 0, "Migration number must be > 0!" + assert migration_number not in migration_registry, \ + "Duplicate migration numbers detected! That's not allowed!" + + self.migration_number = migration_number + self.migration_registry = migration_registry + + def __call__(self, migration): + self.migration_registry[self.migration_number] = migration + return migration + + +def assure_migrations_table_setup(db): + """ + Make sure the migrations table is set up in the database. + """ + from mediagoblin.db.models import MigrationData + + if not MigrationData.__table__.exists(db.bind): + MigrationData.metadata.create_all( + db.bind, tables=[MigrationData.__table__]) + + +def inspect_table(metadata, table_name): + """Simple helper to get a ref to an already existing table""" + return Table(table_name, metadata, autoload=True, + autoload_with=metadata.bind) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py new file mode 100644 index 00000000..2c553396 --- /dev/null +++ b/mediagoblin/db/migrations.py @@ -0,0 +1,289 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import datetime +import uuid + +from sqlalchemy import (MetaData, Table, Column, Boolean, SmallInteger, + Integer, Unicode, UnicodeText, DateTime, + ForeignKey) +from sqlalchemy.exc import ProgrammingError +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.sql import and_ +from migrate.changeset.constraint import UniqueConstraint + +from mediagoblin.db.migration_tools import RegisterMigration, inspect_table +from mediagoblin.db.models import MediaEntry, Collection, User + +MIGRATIONS = {} + + +@RegisterMigration(1, MIGRATIONS) +def ogg_to_webm_audio(db_conn): + metadata = MetaData(bind=db_conn.bind) + + file_keynames = Table('core__file_keynames', metadata, autoload=True, + autoload_with=db_conn.bind) + + db_conn.execute( + file_keynames.update().where(file_keynames.c.name == 'ogg'). + values(name='webm_audio') + ) + db_conn.commit() + + +@RegisterMigration(2, MIGRATIONS) +def add_wants_notification_column(db_conn): + metadata = MetaData(bind=db_conn.bind) + + users = Table('core__users', metadata, autoload=True, + autoload_with=db_conn.bind) + + col = Column('wants_comment_notification', Boolean, + default=True, nullable=True) + col.create(users, populate_defaults=True) + db_conn.commit() + + +@RegisterMigration(3, MIGRATIONS) +def add_transcoding_progress(db_conn): + metadata = MetaData(bind=db_conn.bind) + + media_entry = inspect_table(metadata, 'core__media_entries') + + col = Column('transcoding_progress', SmallInteger) + col.create(media_entry) + db_conn.commit() + + +class Collection_v0(declarative_base()): + __tablename__ = "core__collections" + + id = Column(Integer, primary_key=True) + title = Column(Unicode, nullable=False) + slug = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.now, + index=True) + description = Column(UnicodeText) + creator = Column(Integer, ForeignKey(User.id), nullable=False) + items = Column(Integer, default=0) + +class CollectionItem_v0(declarative_base()): + __tablename__ = "core__collection_items" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) + collection = Column(Integer, ForeignKey(Collection.id), nullable=False) + note = Column(UnicodeText, nullable=True) + added = Column(DateTime, nullable=False, default=datetime.datetime.now) + position = Column(Integer) + + ## This should be activated, normally. + ## But this would change the way the next migration used to work. + ## So it's commented for now. + __table_args__ = ( + UniqueConstraint('collection', 'media_entry'), + {}) + +collectionitem_unique_constraint_done = False + +@RegisterMigration(4, MIGRATIONS) +def add_collection_tables(db_conn): + Collection_v0.__table__.create(db_conn.bind) + CollectionItem_v0.__table__.create(db_conn.bind) + + global collectionitem_unique_constraint_done + collectionitem_unique_constraint_done = True + + db_conn.commit() + + +@RegisterMigration(5, MIGRATIONS) +def add_mediaentry_collected(db_conn): + metadata = MetaData(bind=db_conn.bind) + + media_entry = inspect_table(metadata, 'core__media_entries') + + col = Column('collected', Integer, default=0) + col.create(media_entry) + db_conn.commit() + + +class ProcessingMetaData_v0(declarative_base()): + __tablename__ = 'core__processing_metadata' + + id = Column(Integer, primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False, + index=True) + callback_url = Column(Unicode) + +@RegisterMigration(6, MIGRATIONS) +def create_processing_metadata_table(db): + ProcessingMetaData_v0.__table__.create(db.bind) + db.commit() + + +# Okay, problem being: +# Migration #4 forgot to add the uniqueconstraint for the +# new tables. While creating the tables from scratch had +# the constraint enabled. +# +# So we have four situations that should end up at the same +# db layout: +# +# 1. Fresh install. +# Well, easy. Just uses the tables in models.py +# 2. Fresh install using a git version just before this migration +# The tables are all there, the unique constraint is also there. +# This migration should do nothing. +# But as we can't detect the uniqueconstraint easily, +# this migration just adds the constraint again. +# And possibly fails very loud. But ignores the failure. +# 3. old install, not using git, just releases. +# This one will get the new tables in #4 (now with constraint!) +# And this migration is just skipped silently. +# 4. old install, always on latest git. +# This one has the tables, but lacks the constraint. +# So this migration adds the constraint. +@RegisterMigration(7, MIGRATIONS) +def fix_CollectionItem_v0_constraint(db_conn): + """Add the forgotten Constraint on CollectionItem""" + + global collectionitem_unique_constraint_done + if collectionitem_unique_constraint_done: + # Reset it. Maybe the whole thing gets run again + # For a different db? + collectionitem_unique_constraint_done = False + return + + metadata = MetaData(bind=db_conn.bind) + + CollectionItem_table = inspect_table(metadata, 'core__collection_items') + + constraint = UniqueConstraint('collection', 'media_entry', + name='core__collection_items_collection_media_entry_key', + table=CollectionItem_table) + + try: + constraint.create() + except ProgrammingError: + # User probably has an install that was run since the + # collection tables were added, so we don't need to run this migration. + pass + + db_conn.commit() + + +@RegisterMigration(8, MIGRATIONS) +def add_license_preference(db): + metadata = MetaData(bind=db.bind) + + user_table = inspect_table(metadata, 'core__users') + + col = Column('license_preference', Unicode) + col.create(user_table) + db.commit() + + +@RegisterMigration(9, MIGRATIONS) +def mediaentry_new_slug_era(db): + """ + Update for the new era for media type slugs. + + Entries without slugs now display differently in the url like: + /u/cwebber/m/id=251/ + + ... because of this, we should back-convert: + - entries without slugs should be converted to use the id, if possible, to + make old urls still work + - slugs with = (or also : which is now also not allowed) to have those + stripped out (small possibility of breakage here sadly) + """ + + def slug_and_user_combo_exists(slug, uploader): + return db.execute( + media_table.select( + and_(media_table.c.uploader==uploader, + media_table.c.slug==slug))).first() is not None + + def append_garbage_till_unique(row, new_slug): + """ + Attach junk to this row until it's unique, then save it + """ + if slug_and_user_combo_exists(new_slug, row.uploader): + # okay, still no success; + # let's whack junk on there till it's unique. + new_slug += '-' + uuid.uuid4().hex[:4] + # keep going if necessary! + while slug_and_user_combo_exists(new_slug, row.uploader): + new_slug += uuid.uuid4().hex[:4] + + db.execute( + media_table.update(). \ + where(media_table.c.id==row.id). \ + values(slug=new_slug)) + + metadata = MetaData(bind=db.bind) + + media_table = inspect_table(metadata, 'core__media_entries') + + for row in db.execute(media_table.select()): + # no slug, try setting to an id + if not row.slug: + append_garbage_till_unique(row, unicode(row.id)) + # has "=" or ":" in it... we're getting rid of those + elif u"=" in row.slug or u":" in row.slug: + append_garbage_till_unique( + row, row.slug.replace(u"=", u"-").replace(u":", u"-")) + + db.commit() + + +@RegisterMigration(10, MIGRATIONS) +def unique_collections_slug(db): + """Add unique constraint to collection slug""" + metadata = MetaData(bind=db.bind) + collection_table = inspect_table(metadata, "core__collections") + existing_slugs = {} + slugs_to_change = [] + + for row in db.execute(collection_table.select()): + # if duplicate slug, generate a unique slug + if row.creator in existing_slugs and row.slug in \ + existing_slugs[row.creator]: + slugs_to_change.append(row.id) + else: + if not row.creator in existing_slugs: + existing_slugs[row.creator] = [row.slug] + else: + existing_slugs[row.creator].append(row.slug) + + for row_id in slugs_to_change: + new_slug = unicode(uuid.uuid4()) + db.execute(collection_table.update(). + where(collection_table.c.id == row_id). + values(slug=new_slug)) + # sqlite does not like to change the schema when a transaction(update) is + # not yet completed + db.commit() + + constraint = UniqueConstraint('creator', 'slug', + name='core__collection_creator_slug_key', + table=collection_table) + constraint.create() + + db.commit() diff --git a/mediagoblin/db/mixin.py b/mediagoblin/db/mixin.py new file mode 100644 index 00000000..9f566e36 --- /dev/null +++ b/mediagoblin/db/mixin.py @@ -0,0 +1,334 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +This module contains some Mixin classes for the db objects. + +A bunch of functions on the db objects are really more like +"utility functions": They could live outside the classes +and be called "by hand" passing the appropiate reference. +They usually only use the public API of the object and +rarely use database related stuff. + +These functions now live here and get "mixed in" into the +real objects. +""" + +import uuid +import re +import datetime + +from werkzeug.utils import cached_property + +from mediagoblin import mg_globals +from mediagoblin.media_types import get_media_managers, FileTypeNotSupported +from mediagoblin.tools import common, licenses +from mediagoblin.tools.text import cleaned_markdown_conversion +from mediagoblin.tools.url import slugify + + +class UserMixin(object): + @property + def bio_html(self): + return cleaned_markdown_conversion(self.bio) + + +class GenerateSlugMixin(object): + """ + Mixin to add a generate_slug method to objects. + + Depends on: + - self.slug + - self.title + - self.check_slug_used(new_slug) + """ + def generate_slug(self): + """ + Generate a unique slug for this object. + + This one does not *force* slugs, but usually it will probably result + in a niceish one. + + The end *result* of the algorithm will result in these resolutions for + these situations: + - If we have a slug, make sure it's clean and sanitized, and if it's + unique, we'll use that. + - If we have a title, slugify it, and if it's unique, we'll use that. + - If we can't get any sort of thing that looks like it'll be a useful + slug out of a title or an existing slug, bail, and don't set the + slug at all. Don't try to create something just because. Make + sure we have a reasonable basis for a slug first. + - If we have a reasonable basis for a slug (either based on existing + slug or slugified title) but it's not unique, first try appending + the entry's id, if that exists + - If that doesn't result in something unique, tack on some randomly + generated bits until it's unique. That'll be a little bit of junk, + but at least it has the basis of a nice slug. + """ + #Is already a slug assigned? Check if it is valid + if self.slug: + self.slug = slugify(self.slug) + + # otherwise, try to use the title. + elif self.title: + # assign slug based on title + self.slug = slugify(self.title) + + # We don't want any empty string slugs + if self.slug == u"": + self.slug = None + + # Do we have anything at this point? + # If not, we're not going to get a slug + # so just return... we're not going to force one. + if not self.slug: + return # giving up! + + # Otherwise, let's see if this is unique. + if self.check_slug_used(self.slug): + # It looks like it's being used... lame. + + # Can we just append the object's id to the end? + if self.id: + slug_with_id = u"%s-%s" % (self.slug, self.id) + if not self.check_slug_used(slug_with_id): + self.slug = slug_with_id + return # success! + + # okay, still no success; + # let's whack junk on there till it's unique. + self.slug += '-' + uuid.uuid4().hex[:4] + # keep going if necessary! + while self.check_slug_used(self.slug): + self.slug += uuid.uuid4().hex[:4] + + +class MediaEntryMixin(GenerateSlugMixin): + def check_slug_used(self, slug): + # import this here due to a cyclic import issue + # (db.models -> db.mixin -> db.util -> db.models) + from mediagoblin.db.util import check_media_slug_used + + return check_media_slug_used(self.uploader, slug, self.id) + + @property + def description_html(self): + """ + Rendered version of the description, run through + Markdown and cleaned with our cleaning tool. + """ + return cleaned_markdown_conversion(self.description) + + def get_display_media(self): + """Find the best media for display. + + We try checking self.media_manager.fetching_order if it exists to + pull down the order. + + Returns: + (media_size, media_path) + or, if not found, None. + + """ + fetch_order = self.media_manager.media_fetch_order + + # No fetching order found? well, give up! + if not fetch_order: + return None + + media_sizes = self.media_files.keys() + + for media_size in fetch_order: + if media_size in media_sizes: + return media_size, self.media_files[media_size] + + def main_mediafile(self): + pass + + @property + def slug_or_id(self): + if self.slug: + return self.slug + else: + return u'id:%s' % self.id + + def url_for_self(self, urlgen, **extra_args): + """ + Generate an appropriate url for ourselves + + Use a slug if we have one, else use our 'id'. + """ + uploader = self.get_uploader + + return urlgen( + 'mediagoblin.user_pages.media_home', + user=uploader.username, + media=self.slug_or_id, + **extra_args) + + @property + def thumb_url(self): + """Return the thumbnail URL (for usage in templates) + Will return either the real thumbnail or a default fallback icon.""" + # TODO: implement generic fallback in case MEDIA_MANAGER does + # not specify one? + if u'thumb' in self.media_files: + thumb_url = mg_globals.app.public_store.file_url( + self.media_files[u'thumb']) + else: + # No thumbnail in media available. Get the media's + # MEDIA_MANAGER for the fallback icon and return static URL + # Raises FileTypeNotSupported in case no such manager is enabled + manager = self.media_manager + thumb_url = mg_globals.app.staticdirector(manager[u'default_thumb']) + return thumb_url + + @cached_property + def media_manager(self): + """Returns the MEDIA_MANAGER of the media's media_type + + Raises FileTypeNotSupported in case no such manager is enabled + """ + # TODO, we should be able to make this a simple lookup rather + # than iterating through all media managers. + for media_type, manager in get_media_managers(): + if media_type == self.media_type: + return manager(self) + # Not found? Then raise an error + raise FileTypeNotSupported( + "MediaManager not in enabled types. Check media_types in config?") + + def get_fail_exception(self): + """ + Get the exception that's appropriate for this error + """ + if self.fail_error: + return common.import_component(self.fail_error) + + def get_license_data(self): + """Return license dict for requested license""" + return licenses.get_license_by_url(self.license or "") + + def exif_display_iter(self): + if not self.media_data: + return + exif_all = self.media_data.get("exif_all") + + for key in exif_all: + label = re.sub('(.)([A-Z][a-z]+)', r'\1 \2', key) + yield label.replace('EXIF', '').replace('Image', ''), exif_all[key] + + def exif_display_data_short(self): + """Display a very short practical version of exif info""" + if not self.media_data: + return + + exif_all = self.media_data.get("exif_all") + + exif_short = {} + + if 'Image DateTimeOriginal' in exif_all: + # format date taken + takendate = datetime.datetime.strptime( + exif_all['Image DateTimeOriginal']['printable'], + '%Y:%m:%d %H:%M:%S').date() + taken = takendate.strftime('%B %d %Y') + + exif_short.update({'Date Taken': taken}) + + aperture = None + if 'EXIF FNumber' in exif_all: + fnum = str(exif_all['EXIF FNumber']['printable']).split('/') + + # calculate aperture + if len(fnum) == 2: + aperture = "f/%.1f" % (float(fnum[0])/float(fnum[1])) + elif fnum[0] != 'None': + aperture = "f/%s" % (fnum[0]) + + if aperture: + exif_short.update({'Aperture': aperture}) + + short_keys = [ + ('Camera', 'Image Model', None), + ('Exposure', 'EXIF ExposureTime', lambda x: '%s sec' % x), + ('ISO Speed', 'EXIF ISOSpeedRatings', None), + ('Focal Length', 'EXIF FocalLength', lambda x: '%s mm' % x)] + + for label, key, fmt_func in short_keys: + try: + val = fmt_func(exif_all[key]['printable']) if fmt_func \ + else exif_all[key]['printable'] + exif_short.update({label: val}) + except KeyError: + pass + + return exif_short + + +class MediaCommentMixin(object): + @property + def content_html(self): + """ + the actual html-rendered version of the comment displayed. + Run through Markdown and the HTML cleaner. + """ + return cleaned_markdown_conversion(self.content) + + +class CollectionMixin(GenerateSlugMixin): + def check_slug_used(self, slug): + # import this here due to a cyclic import issue + # (db.models -> db.mixin -> db.util -> db.models) + from mediagoblin.db.util import check_collection_slug_used + + return check_collection_slug_used(self.creator, slug, self.id) + + @property + def description_html(self): + """ + Rendered version of the description, run through + Markdown and cleaned with our cleaning tool. + """ + return cleaned_markdown_conversion(self.description) + + @property + def slug_or_id(self): + return (self.slug or self.id) + + def url_for_self(self, urlgen, **extra_args): + """ + Generate an appropriate url for ourselves + + Use a slug if we have one, else use our 'id'. + """ + creator = self.get_creator + + return urlgen( + 'mediagoblin.user_pages.user_collection', + user=creator.username, + collection=self.slug_or_id, + **extra_args) + + +class CollectionItemMixin(object): + @property + def note_html(self): + """ + the actual html-rendered version of the note displayed. + Run through Markdown and the HTML cleaner. + """ + return cleaned_markdown_conversion(self.note) diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py new file mode 100644 index 00000000..2b925983 --- /dev/null +++ b/mediagoblin/db/models.py @@ -0,0 +1,524 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +TODO: indexes on foreignkeys, where useful. +""" + +import logging +import datetime + +from sqlalchemy import Column, Integer, Unicode, UnicodeText, DateTime, \ + Boolean, ForeignKey, UniqueConstraint, PrimaryKeyConstraint, \ + SmallInteger +from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm.collections import attribute_mapped_collection +from sqlalchemy.sql.expression import desc +from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.util import memoized_property + +from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded +from mediagoblin.db.base import Base, DictReadAttrProxy +from mediagoblin.db.mixin import UserMixin, MediaEntryMixin, MediaCommentMixin, CollectionMixin, CollectionItemMixin +from mediagoblin.tools.files import delete_media_files +from mediagoblin.tools.common import import_component + +# It's actually kind of annoying how sqlalchemy-migrate does this, if +# I understand it right, but whatever. Anyway, don't remove this :P +# +# We could do migration calls more manually instead of relying on +# this import-based meddling... +from migrate import changeset + +_log = logging.getLogger(__name__) + + +class User(Base, UserMixin): + """ + TODO: We should consider moving some rarely used fields + into some sort of "shadow" table. + """ + __tablename__ = "core__users" + + id = Column(Integer, primary_key=True) + username = Column(Unicode, nullable=False, unique=True) + # Note: no db uniqueness constraint on email because it's not + # reliable (many email systems case insensitive despite against + # the RFC) and because it would be a mess to implement at this + # point. + email = Column(Unicode, nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + pw_hash = Column(Unicode, nullable=False) + email_verified = Column(Boolean, default=False) + status = Column(Unicode, default=u"needs_email_verification", nullable=False) + # Intented to be nullable=False, but migrations would not work for it + # set to nullable=True implicitly. + wants_comment_notification = Column(Boolean, default=True) + license_preference = Column(Unicode) + verification_key = Column(Unicode) + is_admin = Column(Boolean, default=False, nullable=False) + url = Column(Unicode) + bio = Column(UnicodeText) # ?? + fp_verification_key = Column(Unicode) + fp_token_expire = Column(DateTime) + + ## TODO + # plugin data would be in a separate model + + def __repr__(self): + return '<{0} #{1} {2} {3} "{4}">'.format( + self.__class__.__name__, + self.id, + 'verified' if self.email_verified else 'non-verified', + 'admin' if self.is_admin else 'user', + self.username) + + def delete(self, **kwargs): + """Deletes a User and all related entries/comments/files/...""" + # Collections get deleted by relationships. + + media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id) + for media in media_entries: + # TODO: Make sure that "MediaEntry.delete()" also deletes + # all related files/Comments + media.delete(del_orphan_tags=False, commit=False) + + # Delete now unused tags + # TODO: import here due to cyclic imports!!! This cries for refactoring + from mediagoblin.db.util import clean_orphan_tags + clean_orphan_tags(commit=False) + + # Delete user, pass through commit=False/True in kwargs + super(User, self).delete(**kwargs) + _log.info('Deleted user "{0}" account'.format(self.username)) + + +class MediaEntry(Base, MediaEntryMixin): + """ + TODO: Consider fetching the media_files using join + """ + __tablename__ = "core__media_entries" + + id = Column(Integer, primary_key=True) + uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True) + title = Column(Unicode, nullable=False) + slug = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.now, + index=True) + description = Column(UnicodeText) # ?? + media_type = Column(Unicode, nullable=False) + state = Column(Unicode, default=u'unprocessed', nullable=False) + # or use sqlalchemy.types.Enum? + license = Column(Unicode) + collected = Column(Integer, default=0) + + fail_error = Column(Unicode) + fail_metadata = Column(JSONEncoded) + + transcoding_progress = Column(SmallInteger) + + queued_media_file = Column(PathTupleWithSlashes) + + queued_task_id = Column(Unicode) + + __table_args__ = ( + UniqueConstraint('uploader', 'slug'), + {}) + + get_uploader = relationship(User) + + media_files_helper = relationship("MediaFile", + collection_class=attribute_mapped_collection("name"), + cascade="all, delete-orphan" + ) + media_files = association_proxy('media_files_helper', 'file_path', + creator=lambda k, v: MediaFile(name=k, file_path=v) + ) + + attachment_files_helper = relationship("MediaAttachmentFile", + cascade="all, delete-orphan", + order_by="MediaAttachmentFile.created" + ) + attachment_files = association_proxy("attachment_files_helper", "dict_view", + creator=lambda v: MediaAttachmentFile( + name=v["name"], filepath=v["filepath"]) + ) + + tags_helper = relationship("MediaTag", + cascade="all, delete-orphan" # should be automatically deleted + ) + tags = association_proxy("tags_helper", "dict_view", + creator=lambda v: MediaTag(name=v["name"], slug=v["slug"]) + ) + + collections_helper = relationship("CollectionItem", + cascade="all, delete-orphan" + ) + collections = association_proxy("collections_helper", "in_collection") + + ## TODO + # fail_error + + def get_comments(self, ascending=False): + order_col = MediaComment.created + if not ascending: + order_col = desc(order_col) + return self.all_comments.order_by(order_col) + + def url_to_prev(self, urlgen): + """get the next 'newer' entry by this user""" + media = MediaEntry.query.filter( + (MediaEntry.uploader == self.uploader) + & (MediaEntry.state == u'processed') + & (MediaEntry.id > self.id)).order_by(MediaEntry.id).first() + + if media is not None: + return media.url_for_self(urlgen) + + def url_to_next(self, urlgen): + """get the next 'older' entry by this user""" + media = MediaEntry.query.filter( + (MediaEntry.uploader == self.uploader) + & (MediaEntry.state == u'processed') + & (MediaEntry.id < self.id)).order_by(desc(MediaEntry.id)).first() + + if media is not None: + return media.url_for_self(urlgen) + + @property + def media_data(self): + return getattr(self, self.media_data_ref) + + def media_data_init(self, **kwargs): + """ + Initialize or update the contents of a media entry's media_data row + """ + media_data = self.media_data + + if media_data is None: + # Get the correct table: + table = import_component(self.media_type + '.models:DATA_MODEL') + # No media data, so actually add a new one + media_data = table(**kwargs) + # Get the relationship set up. + media_data.get_media_entry = self + else: + # Update old media data + for field, value in kwargs.iteritems(): + setattr(media_data, field, value) + + @memoized_property + def media_data_ref(self): + return import_component(self.media_type + '.models:BACKREF_NAME') + + def __repr__(self): + safe_title = self.title.encode('ascii', 'replace') + + return '<{classname} {id}: {title}>'.format( + classname=self.__class__.__name__, + id=self.id, + title=safe_title) + + def delete(self, del_orphan_tags=True, **kwargs): + """Delete MediaEntry and all related files/attachments/comments + + This will *not* automatically delete unused collections, which + can remain empty... + + :param del_orphan_tags: True/false if we delete unused Tags too + :param commit: True/False if this should end the db transaction""" + # User's CollectionItems are automatically deleted via "cascade". + # Comments on this Media are deleted by cascade, hopefully. + + # Delete all related files/attachments + try: + delete_media_files(self) + except OSError, error: + # Returns list of files we failed to delete + _log.error('No such files from the user "{1}" to delete: ' + '{0}'.format(str(error), self.get_uploader)) + _log.info('Deleted Media entry id "{0}"'.format(self.id)) + # Related MediaTag's are automatically cleaned, but we might + # want to clean out unused Tag's too. + if del_orphan_tags: + # TODO: Import here due to cyclic imports!!! + # This cries for refactoring + from mediagoblin.db.util import clean_orphan_tags + clean_orphan_tags(commit=False) + # pass through commit=False/True in kwargs + super(MediaEntry, self).delete(**kwargs) + + +class FileKeynames(Base): + """ + keywords for various places. + currently the MediaFile keys + """ + __tablename__ = "core__file_keynames" + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True) + + def __repr__(self): + return "<FileKeyname %r: %r>" % (self.id, self.name) + + @classmethod + def find_or_new(cls, name): + t = cls.query.filter_by(name=name).first() + if t is not None: + return t + return cls(name=name) + + +class MediaFile(Base): + """ + TODO: Highly consider moving "name" into a new table. + TODO: Consider preloading said table in software + """ + __tablename__ = "core__mediafiles" + + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False) + name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False) + file_path = Column(PathTupleWithSlashes) + + __table_args__ = ( + PrimaryKeyConstraint('media_entry', 'name_id'), + {}) + + def __repr__(self): + return "<MediaFile %s: %r>" % (self.name, self.file_path) + + name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True) + name = association_proxy('name_helper', 'name', + creator=FileKeynames.find_or_new + ) + + +class MediaAttachmentFile(Base): + __tablename__ = "core__attachment_files" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False) + name = Column(Unicode, nullable=False) + filepath = Column(PathTupleWithSlashes) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +class Tag(Base): + __tablename__ = "core__tags" + + id = Column(Integer, primary_key=True) + slug = Column(Unicode, nullable=False, unique=True) + + def __repr__(self): + return "<Tag %r: %r>" % (self.id, self.slug) + + @classmethod + def find_or_new(cls, slug): + t = cls.query.filter_by(slug=slug).first() + if t is not None: + return t + return cls(slug=slug) + + +class MediaTag(Base): + __tablename__ = "core__media_tags" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False, index=True) + tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True) + name = Column(Unicode) + # created = Column(DateTime, nullable=False, default=datetime.datetime.now) + + __table_args__ = ( + UniqueConstraint('tag', 'media_entry'), + {}) + + tag_helper = relationship(Tag) + slug = association_proxy('tag_helper', 'slug', + creator=Tag.find_or_new + ) + + def __init__(self, name=None, slug=None): + Base.__init__(self) + if name is not None: + self.name = name + if slug is not None: + self.tag_helper = Tag.find_or_new(slug) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +class MediaComment(Base, MediaCommentMixin): + __tablename__ = "core__media_comments" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) + author = Column(Integer, ForeignKey(User.id), nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + content = Column(UnicodeText, nullable=False) + + # Cascade: Comments are owned by their creator. So do the full thing. + # lazy=dynamic: People might post a *lot* of comments, + # so make the "posted_comments" a query-like thing. + get_author = relationship(User, + backref=backref("posted_comments", + lazy="dynamic", + cascade="all, delete-orphan")) + + # Cascade: Comments are somewhat owned by their MediaEntry. + # So do the full thing. + # lazy=dynamic: MediaEntries might have many comments, + # so make the "all_comments" a query-like thing. + get_media_entry = relationship(MediaEntry, + backref=backref("all_comments", + lazy="dynamic", + cascade="all, delete-orphan")) + + +class Collection(Base, CollectionMixin): + """An 'album' or 'set' of media by a user. + + On deletion, contained CollectionItems get automatically reaped via + SQL cascade""" + __tablename__ = "core__collections" + + id = Column(Integer, primary_key=True) + title = Column(Unicode, nullable=False) + slug = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.now, + index=True) + description = Column(UnicodeText) + creator = Column(Integer, ForeignKey(User.id), nullable=False) + # TODO: No of items in Collection. Badly named, can we migrate to num_items? + items = Column(Integer, default=0) + + # Cascade: Collections are owned by their creator. So do the full thing. + get_creator = relationship(User, + backref=backref("collections", + cascade="all, delete-orphan")) + + __table_args__ = ( + UniqueConstraint('creator', 'slug'), + {}) + + def get_collection_items(self, ascending=False): + #TODO, is this still needed with self.collection_items being available? + order_col = CollectionItem.position + if not ascending: + order_col = desc(order_col) + return CollectionItem.query.filter_by( + collection=self.id).order_by(order_col) + + +class CollectionItem(Base, CollectionItemMixin): + __tablename__ = "core__collection_items" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) + collection = Column(Integer, ForeignKey(Collection.id), nullable=False) + note = Column(UnicodeText, nullable=True) + added = Column(DateTime, nullable=False, default=datetime.datetime.now) + position = Column(Integer) + + # Cascade: CollectionItems are owned by their Collection. So do the full thing. + in_collection = relationship(Collection, + backref=backref( + "collection_items", + cascade="all, delete-orphan")) + + get_media_entry = relationship(MediaEntry) + + __table_args__ = ( + UniqueConstraint('collection', 'media_entry'), + {}) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +class ProcessingMetaData(Base): + __tablename__ = 'core__processing_metadata' + + id = Column(Integer, primary_key=True) + media_entry_id = Column(Integer, ForeignKey(MediaEntry.id), nullable=False, + index=True) + media_entry = relationship(MediaEntry, + backref=backref('processing_metadata', + cascade='all, delete-orphan')) + callback_url = Column(Unicode) + + @property + def dict_view(self): + """A dict like view on this object""" + return DictReadAttrProxy(self) + + +MODELS = [ + User, MediaEntry, Tag, MediaTag, MediaComment, Collection, CollectionItem, MediaFile, FileKeynames, + MediaAttachmentFile, ProcessingMetaData] + + +###################################################### +# Special, migrations-tracking table +# +# Not listed in MODELS because this is special and not +# really migrated, but used for migrations (for now) +###################################################### + +class MigrationData(Base): + __tablename__ = "core__migrations" + + name = Column(Unicode, primary_key=True) + version = Column(Integer, nullable=False, default=0) + +###################################################### + + +def show_table_init(engine_uri): + if engine_uri is None: + engine_uri = 'sqlite:///:memory:' + from sqlalchemy import create_engine + engine = create_engine(engine_uri, echo=True) + + Base.metadata.create_all(engine) + + +if __name__ == '__main__': + from sys import argv + print repr(argv) + if len(argv) == 2: + uri = argv[1] + else: + uri = None + show_table_init(uri) diff --git a/mediagoblin/db/models_v0.py b/mediagoblin/db/models_v0.py new file mode 100644 index 00000000..bdedec2e --- /dev/null +++ b/mediagoblin/db/models_v0.py @@ -0,0 +1,342 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +TODO: indexes on foreignkeys, where useful. +""" + +########################################################################### +# WHAT IS THIS FILE? +# ------------------ +# +# Upon occasion, someone runs into this file and wonders why we have +# both a models.py and a models_v0.py. +# +# The short of it is: you can ignore this file. +# +# The long version is, in two parts: +# +# - We used to use MongoDB, then we switched to SQL and SQLAlchemy. +# We needed to convert peoples' databases; the script we had would +# switch them to the first version right after Mongo, convert over +# all their tables, then run any migrations that were added after. +# +# - That script is now removed, but there is some discussion of +# writing a test that would set us at the first SQL migration and +# run everything after. If we wrote that, this file would still be +# useful. But for now, it's legacy! +# +########################################################################### + + +import datetime +import sys + +from sqlalchemy import ( + Column, Integer, Unicode, UnicodeText, DateTime, Boolean, ForeignKey, + UniqueConstraint, PrimaryKeyConstraint, SmallInteger, Float) +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm.collections import attribute_mapped_collection +from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.util import memoized_property + +from mediagoblin.db.extratypes import PathTupleWithSlashes, JSONEncoded +from mediagoblin.db.base import GMGTableBase, Session + + +Base_v0 = declarative_base(cls=GMGTableBase) + + +class User(Base_v0): + """ + TODO: We should consider moving some rarely used fields + into some sort of "shadow" table. + """ + __tablename__ = "core__users" + + id = Column(Integer, primary_key=True) + username = Column(Unicode, nullable=False, unique=True) + email = Column(Unicode, nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + pw_hash = Column(Unicode, nullable=False) + email_verified = Column(Boolean, default=False) + status = Column(Unicode, default=u"needs_email_verification", nullable=False) + verification_key = Column(Unicode) + is_admin = Column(Boolean, default=False, nullable=False) + url = Column(Unicode) + bio = Column(UnicodeText) # ?? + fp_verification_key = Column(Unicode) + fp_token_expire = Column(DateTime) + + ## TODO + # plugin data would be in a separate model + + +class MediaEntry(Base_v0): + """ + TODO: Consider fetching the media_files using join + """ + __tablename__ = "core__media_entries" + + id = Column(Integer, primary_key=True) + uploader = Column(Integer, ForeignKey(User.id), nullable=False, index=True) + title = Column(Unicode, nullable=False) + slug = Column(Unicode) + created = Column(DateTime, nullable=False, default=datetime.datetime.now, + index=True) + description = Column(UnicodeText) # ?? + media_type = Column(Unicode, nullable=False) + state = Column(Unicode, default=u'unprocessed', nullable=False) + # or use sqlalchemy.types.Enum? + license = Column(Unicode) + + fail_error = Column(Unicode) + fail_metadata = Column(JSONEncoded) + + queued_media_file = Column(PathTupleWithSlashes) + + queued_task_id = Column(Unicode) + + __table_args__ = ( + UniqueConstraint('uploader', 'slug'), + {}) + + get_uploader = relationship(User) + + media_files_helper = relationship("MediaFile", + collection_class=attribute_mapped_collection("name"), + cascade="all, delete-orphan" + ) + + attachment_files_helper = relationship("MediaAttachmentFile", + cascade="all, delete-orphan", + order_by="MediaAttachmentFile.created" + ) + + tags_helper = relationship("MediaTag", + cascade="all, delete-orphan" + ) + + def media_data_init(self, **kwargs): + """ + Initialize or update the contents of a media entry's media_data row + """ + session = Session() + + media_data = session.query(self.media_data_table).filter_by( + media_entry=self.id).first() + + # No media data, so actually add a new one + if media_data is None: + media_data = self.media_data_table( + media_entry=self.id, + **kwargs) + session.add(media_data) + # Update old media data + else: + for field, value in kwargs.iteritems(): + setattr(media_data, field, value) + + @memoized_property + def media_data_table(self): + # TODO: memoize this + models_module = self.media_type + '.models' + __import__(models_module) + return sys.modules[models_module].DATA_MODEL + + +class FileKeynames(Base_v0): + """ + keywords for various places. + currently the MediaFile keys + """ + __tablename__ = "core__file_keynames" + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True) + + def __repr__(self): + return "<FileKeyname %r: %r>" % (self.id, self.name) + + @classmethod + def find_or_new(cls, name): + t = cls.query.filter_by(name=name).first() + if t is not None: + return t + return cls(name=name) + + +class MediaFile(Base_v0): + """ + TODO: Highly consider moving "name" into a new table. + TODO: Consider preloading said table in software + """ + __tablename__ = "core__mediafiles" + + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False) + name_id = Column(SmallInteger, ForeignKey(FileKeynames.id), nullable=False) + file_path = Column(PathTupleWithSlashes) + + __table_args__ = ( + PrimaryKeyConstraint('media_entry', 'name_id'), + {}) + + def __repr__(self): + return "<MediaFile %s: %r>" % (self.name, self.file_path) + + name_helper = relationship(FileKeynames, lazy="joined", innerjoin=True) + name = association_proxy('name_helper', 'name', + creator=FileKeynames.find_or_new + ) + + +class MediaAttachmentFile(Base_v0): + __tablename__ = "core__attachment_files" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False) + name = Column(Unicode, nullable=False) + filepath = Column(PathTupleWithSlashes) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + + +class Tag(Base_v0): + __tablename__ = "core__tags" + + id = Column(Integer, primary_key=True) + slug = Column(Unicode, nullable=False, unique=True) + + def __repr__(self): + return "<Tag %r: %r>" % (self.id, self.slug) + + @classmethod + def find_or_new(cls, slug): + t = cls.query.filter_by(slug=slug).first() + if t is not None: + return t + return cls(slug=slug) + + +class MediaTag(Base_v0): + __tablename__ = "core__media_tags" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), + nullable=False, index=True) + tag = Column(Integer, ForeignKey(Tag.id), nullable=False, index=True) + name = Column(Unicode) + # created = Column(DateTime, nullable=False, default=datetime.datetime.now) + + __table_args__ = ( + UniqueConstraint('tag', 'media_entry'), + {}) + + tag_helper = relationship(Tag) + slug = association_proxy('tag_helper', 'slug', + creator=Tag.find_or_new + ) + + def __init__(self, name=None, slug=None): + Base_v0.__init__(self) + if name is not None: + self.name = name + if slug is not None: + self.tag_helper = Tag.find_or_new(slug) + + +class MediaComment(Base_v0): + __tablename__ = "core__media_comments" + + id = Column(Integer, primary_key=True) + media_entry = Column( + Integer, ForeignKey(MediaEntry.id), nullable=False, index=True) + author = Column(Integer, ForeignKey(User.id), nullable=False) + created = Column(DateTime, nullable=False, default=datetime.datetime.now) + content = Column(UnicodeText, nullable=False) + + get_author = relationship(User) + + +class ImageData(Base_v0): + __tablename__ = "image__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref("image__media_data", cascade="all, delete-orphan")) + + width = Column(Integer) + height = Column(Integer) + exif_all = Column(JSONEncoded) + gps_longitude = Column(Float) + gps_latitude = Column(Float) + gps_altitude = Column(Float) + gps_direction = Column(Float) + + +class VideoData(Base_v0): + __tablename__ = "video__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref("video__media_data", cascade="all, delete-orphan")) + + width = Column(SmallInteger) + height = Column(SmallInteger) + + +class AsciiData(Base_v0): + __tablename__ = "ascii__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref("ascii__media_data", cascade="all, delete-orphan")) + + +class AudioData(Base_v0): + __tablename__ = "audio__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref("audio__media_data", cascade="all, delete-orphan")) + + +###################################################### +# Special, migrations-tracking table +# +# Not listed in MODELS because this is special and not +# really migrated, but used for migrations (for now) +###################################################### + +class MigrationData(Base_v0): + __tablename__ = "core__migrations" + + name = Column(Unicode, primary_key=True) + version = Column(Integer, nullable=False, default=0) + +###################################################### diff --git a/mediagoblin/db/open.py b/mediagoblin/db/open.py new file mode 100644 index 00000000..0b1679fb --- /dev/null +++ b/mediagoblin/db/open.py @@ -0,0 +1,101 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from sqlalchemy import create_engine, event +import logging + +from mediagoblin.db.base import Base, Session +from mediagoblin import mg_globals + +_log = logging.getLogger(__name__) + + +class DatabaseMaster(object): + def __init__(self, engine): + self.engine = engine + + for k, v in Base._decl_class_registry.iteritems(): + setattr(self, k, v) + + def commit(self): + Session.commit() + + def save(self, obj): + Session.add(obj) + Session.flush() + + def check_session_clean(self): + for dummy in Session(): + _log.warn("STRANGE: There are elements in the sql session. " + "Please report this and help us track this down.") + break + + def reset_after_request(self): + Session.rollback() + Session.remove() + + +def load_models(app_config): + import mediagoblin.db.models + + for media_type in app_config['media_types']: + _log.debug("Loading %s.models", media_type) + __import__(media_type + ".models") + + for plugin in mg_globals.global_config.get('plugins', {}).keys(): + _log.debug("Loading %s.models", plugin) + try: + __import__(plugin + ".models") + except ImportError as exc: + _log.debug("Could not load {0}.models: {1}".format( + plugin, + exc)) + + +def _sqlite_fk_pragma_on_connect(dbapi_con, con_record): + """Enable foreign key checking on each new sqlite connection""" + dbapi_con.execute('pragma foreign_keys=on') + + +def _sqlite_disable_fk_pragma_on_connect(dbapi_con, con_record): + """ + Disable foreign key checking on each new sqlite connection + (Good for migrations!) + """ + dbapi_con.execute('pragma foreign_keys=off') + + +def setup_connection_and_db_from_config(app_config, migrations=False): + engine = create_engine(app_config['sql_engine']) + + # Enable foreign key checking for sqlite + if app_config['sql_engine'].startswith('sqlite://'): + if migrations: + event.listen(engine, 'connect', + _sqlite_disable_fk_pragma_on_connect) + else: + event.listen(engine, 'connect', _sqlite_fk_pragma_on_connect) + + # logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) + + Session.configure(bind=engine) + + return DatabaseMaster(engine) + + +def check_db_migrations_current(db): + pass diff --git a/mediagoblin/db/util.py b/mediagoblin/db/util.py new file mode 100644 index 00000000..6ffec44d --- /dev/null +++ b/mediagoblin/db/util.py @@ -0,0 +1,76 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.db.base import Session +from mediagoblin.db.models import MediaEntry, Tag, MediaTag, Collection + + +########################## +# Random utility functions +########################## + + +def atomic_update(table, query_dict, update_values): + table.find(query_dict).update(update_values, + synchronize_session=False) + Session.commit() + + +def check_media_slug_used(uploader_id, slug, ignore_m_id): + query = MediaEntry.query.filter_by(uploader=uploader_id, slug=slug) + if ignore_m_id is not None: + query = query.filter(MediaEntry.id != ignore_m_id) + does_exist = query.first() is not None + return does_exist + + +def media_entries_for_tag_slug(dummy_db, tag_slug): + return MediaEntry.query \ + .join(MediaEntry.tags_helper) \ + .join(MediaTag.tag_helper) \ + .filter( + (MediaEntry.state == u'processed') + & (Tag.slug == tag_slug)) + + +def clean_orphan_tags(commit=True): + """Search for unused MediaTags and delete them""" + q1 = Session.query(Tag).outerjoin(MediaTag).filter(MediaTag.id==None) + for t in q1: + Session.delete(t) + # The "let the db do all the work" version: + # q1 = Session.query(Tag.id).outerjoin(MediaTag).filter(MediaTag.id==None) + # q2 = Session.query(Tag).filter(Tag.id.in_(q1)) + # q2.delete(synchronize_session = False) + if commit: + Session.commit() + + +def check_collection_slug_used(creator_id, slug, ignore_c_id): + filt = (Collection.creator == creator_id) \ + & (Collection.slug == slug) + if ignore_c_id is not None: + filt = filt & (Collection.id != ignore_c_id) + does_exist = Session.query(Collection.id).filter(filt).first() is not None + return does_exist + + +if __name__ == '__main__': + from mediagoblin.db.open import setup_connection_and_db_from_config + + db = setup_connection_and_db_from_config({'sql_engine':'sqlite:///mediagoblin.db'}) + + clean_orphan_tags() diff --git a/mediagoblin/decorators.py b/mediagoblin/decorators.py new file mode 100644 index 00000000..f3535fcf --- /dev/null +++ b/mediagoblin/decorators.py @@ -0,0 +1,237 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from functools import wraps + +from urlparse import urljoin +from werkzeug.exceptions import Forbidden, NotFound +from werkzeug.urls import url_quote + +from mediagoblin import mg_globals as mgg +from mediagoblin.db.models import MediaEntry, User +from mediagoblin.tools.response import redirect, render_404 + + +def require_active_login(controller): + """ + Require an active login from the user. + """ + @wraps(controller) + def new_controller_func(request, *args, **kwargs): + if request.user and \ + request.user.status == u'needs_email_verification': + return redirect( + request, 'mediagoblin.user_pages.user_home', + user=request.user.username) + elif not request.user or request.user.status != u'active': + next_url = urljoin( + request.urlgen('mediagoblin.auth.login', + qualified=True), + request.url) + + return redirect(request, 'mediagoblin.auth.login', + next=next_url) + + return controller(request, *args, **kwargs) + + return new_controller_func + +def active_user_from_url(controller): + """Retrieve User() from <user> URL pattern and pass in as url_user=... + + Returns a 404 if no such active user has been found""" + @wraps(controller) + def wrapper(request, *args, **kwargs): + user = User.query.filter_by(username=request.matchdict['user']).first() + if user is None: + return render_404(request) + + return controller(request, *args, url_user=user, **kwargs) + + return wrapper + + +def user_may_delete_media(controller): + """ + Require user ownership of the MediaEntry to delete. + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + uploader_id = kwargs['media'].uploader + if not (request.user.is_admin or + request.user.id == uploader_id): + raise Forbidden() + + return controller(request, *args, **kwargs) + + return wrapper + + +def user_may_alter_collection(controller): + """ + Require user ownership of the Collection to modify. + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + creator_id = request.db.User.find_one( + {'username': request.matchdict['user']}).id + if not (request.user.is_admin or + request.user.id == creator_id): + raise Forbidden() + + return controller(request, *args, **kwargs) + + return wrapper + + +def uses_pagination(controller): + """ + Check request GET 'page' key for wrong values + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + try: + page = int(request.GET.get('page', 1)) + if page < 0: + return render_404(request) + except ValueError: + return render_404(request) + + return controller(request, page=page, *args, **kwargs) + + return wrapper + + +def get_user_media_entry(controller): + """ + Pass in a MediaEntry based off of a url component + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + user = User.query.filter_by(username=request.matchdict['user']).first() + if not user: + raise NotFound() + + media = None + + # might not be a slug, might be an id, but whatever + media_slug = request.matchdict['media'] + + # if it starts with id: it actually isn't a slug, it's an id. + if media_slug.startswith(u'id:'): + try: + media = MediaEntry.query.filter_by( + id=int(media_slug[3:]), + state=u'processed', + uploader=user.id).first() + except ValueError: + raise NotFound() + else: + # no magical id: stuff? It's a slug! + media = MediaEntry.query.filter_by( + slug=media_slug, + state=u'processed', + uploader=user.id).first() + + if not media: + # Didn't find anything? Okay, 404. + raise NotFound() + + return controller(request, media=media, *args, **kwargs) + + return wrapper + + +def get_user_collection(controller): + """ + Pass in a Collection based off of a url component + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + user = request.db.User.find_one( + {'username': request.matchdict['user']}) + + if not user: + return render_404(request) + + collection = request.db.Collection.find_one( + {'slug': request.matchdict['collection'], + 'creator': user.id}) + + # Still no collection? Okay, 404. + if not collection: + return render_404(request) + + return controller(request, collection=collection, *args, **kwargs) + + return wrapper + + +def get_user_collection_item(controller): + """ + Pass in a CollectionItem based off of a url component + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + user = request.db.User.find_one( + {'username': request.matchdict['user']}) + + if not user: + return render_404(request) + + collection_item = request.db.CollectionItem.find_one( + {'id': request.matchdict['collection_item'] }) + + # Still no collection item? Okay, 404. + if not collection_item: + return render_404(request) + + return controller(request, collection_item=collection_item, *args, **kwargs) + + return wrapper + + +def get_media_entry_by_id(controller): + """ + Pass in a MediaEntry based off of a url component + """ + @wraps(controller) + def wrapper(request, *args, **kwargs): + media = MediaEntry.query.filter_by( + id=request.matchdict['media_id'], + state=u'processed').first() + # Still no media? Okay, 404. + if not media: + return render_404(request) + + given_username = request.matchdict.get('user') + if given_username and (given_username != media.get_uploader.username): + return render_404(request) + + return controller(request, media=media, *args, **kwargs) + + return wrapper + + +def get_workbench(func): + """Decorator, passing in a workbench as kwarg which is cleaned up afterwards""" + + @wraps(func) + def new_func(*args, **kwargs): + with mgg.workbench_manager.create() as workbench: + return func(*args, workbench=workbench, **kwargs) + + return new_func diff --git a/mediagoblin/edit/__init__.py b/mediagoblin/edit/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/edit/__init__.py @@ -0,0 +1,15 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/mediagoblin/edit/forms.py b/mediagoblin/edit/forms.py new file mode 100644 index 00000000..3b2486de --- /dev/null +++ b/mediagoblin/edit/forms.py @@ -0,0 +1,107 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import wtforms + +from mediagoblin.tools.text import tag_length_validator, TOO_LONG_TAG_WARNING +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ +from mediagoblin.tools.licenses import licenses_as_choices + +class EditForm(wtforms.Form): + title = wtforms.TextField( + _('Title'), + [wtforms.validators.Length(min=0, max=500)]) + description = wtforms.TextAreaField( + _('Description of this work'), + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) + tags = wtforms.TextField( + _('Tags'), + [tag_length_validator], + description=_( + "Separate tags by commas.")) + slug = wtforms.TextField( + _('Slug'), + [wtforms.validators.Required(message=_("The slug can't be empty"))], + description=_( + "The title part of this media's address. " + "You usually don't need to change this.")) + license = wtforms.SelectField( + _('License'), + [wtforms.validators.Optional(),], + choices=licenses_as_choices()) + +class EditProfileForm(wtforms.Form): + bio = wtforms.TextAreaField( + _('Bio'), + [wtforms.validators.Length(min=0, max=500)], + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) + url = wtforms.TextField( + _('Website'), + [wtforms.validators.Optional(), + wtforms.validators.URL(message=_("This address contains errors"))]) + + +class EditAccountForm(wtforms.Form): + license_preference = wtforms.SelectField( + _('License preference'), + [ + wtforms.validators.Optional(), + wtforms.validators.AnyOf([lic[0] for lic in licenses_as_choices()]), + ], + choices=licenses_as_choices(), + description=_('This will be your default license on upload forms.')) + wants_comment_notification = wtforms.BooleanField( + label=_("Email me when others comment on my media")) + + +class EditAttachmentsForm(wtforms.Form): + attachment_name = wtforms.TextField( + 'Title') + attachment_file = wtforms.FileField( + 'File') + +class EditCollectionForm(wtforms.Form): + title = wtforms.TextField( + _('Title'), + [wtforms.validators.Length(min=0, max=500), wtforms.validators.Required(message=_("The title can't be empty"))]) + description = wtforms.TextAreaField( + _('Description of this collection'), + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) + slug = wtforms.TextField( + _('Slug'), + [wtforms.validators.Required(message=_("The slug can't be empty"))], + description=_( + "The title part of this collection's address. " + "You usually don't need to change this.")) + + +class ChangePassForm(wtforms.Form): + old_password = wtforms.PasswordField( + _('Old password'), + [wtforms.validators.Required()], + description=_( + "Enter your old password to prove you own this account.")) + new_password = wtforms.PasswordField( + _('New password'), + [wtforms.validators.Required(), + wtforms.validators.Length(min=6, max=30)], + id="password") diff --git a/mediagoblin/edit/lib.py b/mediagoblin/edit/lib.py new file mode 100644 index 00000000..aab537a0 --- /dev/null +++ b/mediagoblin/edit/lib.py @@ -0,0 +1,24 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +def may_edit_media(request, media): + """Check, if the request's user may edit the media details""" + if media.uploader == request.user.id: + return True + if request.user.is_admin: + return True + return False diff --git a/mediagoblin/edit/routing.py b/mediagoblin/edit/routing.py new file mode 100644 index 00000000..622729ac --- /dev/null +++ b/mediagoblin/edit/routing.py @@ -0,0 +1,28 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.routing import add_route + +add_route('mediagoblin.edit.profile', '/u/<string:user>/edit/', + 'mediagoblin.edit.views:edit_profile') +add_route('mediagoblin.edit.legacy_edit_profile', '/edit/profile/', + 'mediagoblin.edit.views:legacy_edit_profile') +add_route('mediagoblin.edit.account', '/edit/account/', + 'mediagoblin.edit.views:edit_account') +add_route('mediagoblin.edit.delete_account', '/edit/account/delete/', + 'mediagoblin.edit.views:delete_account') +add_route('mediagoblin.edit.pass', '/edit/password/', + 'mediagoblin.edit.views:change_pass') diff --git a/mediagoblin/edit/views.py b/mediagoblin/edit/views.py new file mode 100644 index 00000000..508c380d --- /dev/null +++ b/mediagoblin/edit/views.py @@ -0,0 +1,371 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from datetime import datetime + +from werkzeug.exceptions import Forbidden +from werkzeug.utils import secure_filename + +from mediagoblin import messages +from mediagoblin import mg_globals + +from mediagoblin.auth import lib as auth_lib +from mediagoblin.edit import forms +from mediagoblin.edit.lib import may_edit_media +from mediagoblin.decorators import (require_active_login, active_user_from_url, + get_media_entry_by_id, + user_may_alter_collection, get_user_collection) +from mediagoblin.tools.response import render_to_response, \ + redirect, redirect_obj +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.tools.text import ( + convert_to_tag_list_of_dicts, media_tags_as_string) +from mediagoblin.tools.url import slugify +from mediagoblin.db.util import check_media_slug_used, check_collection_slug_used + +import mimetypes + + +@get_media_entry_by_id +@require_active_login +def edit_media(request, media): + if not may_edit_media(request, media): + raise Forbidden("User may not edit this media") + + defaults = dict( + title=media.title, + slug=media.slug, + description=media.description, + tags=media_tags_as_string(media.tags), + license=media.license) + + form = forms.EditForm( + request.form, + **defaults) + + if request.method == 'POST' and form.validate(): + # Make sure there isn't already a MediaEntry with such a slug + # and userid. + slug = slugify(form.slug.data) + slug_used = check_media_slug_used(media.uploader, slug, media.id) + + if slug_used: + form.slug.errors.append( + _(u'An entry with that slug already exists for this user.')) + else: + media.title = form.title.data + media.description = form.description.data + media.tags = convert_to_tag_list_of_dicts( + form.tags.data) + + media.license = unicode(form.license.data) or None + media.slug = slug + media.save() + + return redirect_obj(request, media) + + if request.user.is_admin \ + and media.uploader != request.user.id \ + and request.method != 'POST': + messages.add_message( + request, messages.WARNING, + _("You are editing another user's media. Proceed with caution.")) + + return render_to_response( + request, + 'mediagoblin/edit/edit.html', + {'media': media, + 'form': form}) + + +# Mimetypes that browsers parse scripts in. +# Content-sniffing isn't taken into consideration. +UNSAFE_MIMETYPES = [ + 'text/html', + 'text/svg+xml'] + + +@get_media_entry_by_id +@require_active_login +def edit_attachments(request, media): + if mg_globals.app_config['allow_attachments']: + form = forms.EditAttachmentsForm() + + # Add any attachements + if 'attachment_file' in request.files \ + and request.files['attachment_file']: + + # Security measure to prevent attachments from being served as + # text/html, which will be parsed by web clients and pose an XSS + # threat. + # + # TODO + # This method isn't flawless as some browsers may perform + # content-sniffing. + # This method isn't flawless as we do the mimetype lookup on the + # machine parsing the upload form, and not necessarily the machine + # serving the attachments. + if mimetypes.guess_type( + request.files['attachment_file'].filename)[0] in \ + UNSAFE_MIMETYPES: + public_filename = secure_filename('{0}.notsafe'.format( + request.files['attachment_file'].filename)) + else: + public_filename = secure_filename( + request.files['attachment_file'].filename) + + attachment_public_filepath \ + = mg_globals.public_store.get_unique_filepath( + ['media_entries', unicode(media.id), 'attachment', + public_filename]) + + attachment_public_file = mg_globals.public_store.get_file( + attachment_public_filepath, 'wb') + + try: + attachment_public_file.write( + request.files['attachment_file'].stream.read()) + finally: + request.files['attachment_file'].stream.close() + + media.attachment_files.append(dict( + name=form.attachment_name.data \ + or request.files['attachment_file'].filename, + filepath=attachment_public_filepath, + created=datetime.utcnow(), + )) + + media.save() + + messages.add_message( + request, messages.SUCCESS, + _("You added the attachment %s!") \ + % (form.attachment_name.data + or request.files['attachment_file'].filename)) + + return redirect(request, + location=media.url_for_self(request.urlgen)) + return render_to_response( + request, + 'mediagoblin/edit/attachments.html', + {'media': media, + 'form': form}) + else: + raise Forbidden("Attachments are disabled") + +@require_active_login +def legacy_edit_profile(request): + """redirect the old /edit/profile/?username=USER to /u/USER/edit/""" + username = request.GET.get('username') or request.user.username + return redirect(request, 'mediagoblin.edit.profile', user=username) + + +@require_active_login +@active_user_from_url +def edit_profile(request, url_user=None): + # admins may edit any user profile + if request.user.username != url_user.username: + if not request.user.is_admin: + raise Forbidden(_("You can only edit your own profile.")) + + # No need to warn again if admin just submitted an edited profile + if request.method != 'POST': + messages.add_message( + request, messages.WARNING, + _("You are editing a user's profile. Proceed with caution.")) + + user = url_user + + form = forms.EditProfileForm(request.form, + url=user.url, + bio=user.bio) + + if request.method == 'POST' and form.validate(): + user.url = unicode(form.url.data) + user.bio = unicode(form.bio.data) + + user.save() + + messages.add_message(request, + messages.SUCCESS, + _("Profile changes saved")) + return redirect(request, + 'mediagoblin.user_pages.user_home', + user=user.username) + + return render_to_response( + request, + 'mediagoblin/edit/edit_profile.html', + {'user': user, + 'form': form}) + + +@require_active_login +def edit_account(request): + user = request.user + form = forms.EditAccountForm(request.form, + wants_comment_notification=user.wants_comment_notification, + license_preference=user.license_preference) + + if request.method == 'POST': + form_validated = form.validate() + + if form_validated and \ + form.wants_comment_notification.validate(form): + user.wants_comment_notification = \ + form.wants_comment_notification.data + + if form_validated and \ + form.license_preference.validate(form): + user.license_preference = \ + form.license_preference.data + + if form_validated and not form.errors: + user.save() + messages.add_message(request, + messages.SUCCESS, + _("Account settings saved")) + return redirect(request, + 'mediagoblin.user_pages.user_home', + user=user.username) + + return render_to_response( + request, + 'mediagoblin/edit/edit_account.html', + {'user': user, + 'form': form}) + + +@require_active_login +def delete_account(request): + """Delete a user completely""" + user = request.user + if request.method == 'POST': + if request.form.get(u'confirmed'): + # Form submitted and confirmed. Actually delete the user account + # Log out user and delete cookies etc. + # TODO: Should we be using MG.auth.views.py:logout for this? + request.session.delete() + + # Delete user account and all related media files etc.... + request.user.delete() + + # We should send a message that the user has been deleted + # successfully. But we just deleted the session, so we + # can't... + return redirect(request, 'index') + + else: # Did not check the confirmation box... + messages.add_message( + request, messages.WARNING, + _('You need to confirm the deletion of your account.')) + + # No POST submission or not confirmed, just show page + return render_to_response( + request, + 'mediagoblin/edit/delete_account.html', + {'user': user}) + + +@require_active_login +@user_may_alter_collection +@get_user_collection +def edit_collection(request, collection): + defaults = dict( + title=collection.title, + slug=collection.slug, + description=collection.description) + + form = forms.EditCollectionForm( + request.form, + **defaults) + + if request.method == 'POST' and form.validate(): + # Make sure there isn't already a Collection with such a slug + # and userid. + slug_used = check_collection_slug_used(collection.creator, + form.slug.data, collection.id) + + # Make sure there isn't already a Collection with this title + existing_collection = request.db.Collection.find_one({ + 'creator': request.user.id, + 'title':form.title.data}) + + if existing_collection and existing_collection.id != collection.id: + messages.add_message( + request, messages.ERROR, + _('You already have a collection called "%s"!') % \ + form.title.data) + elif slug_used: + form.slug.errors.append( + _(u'A collection with that slug already exists for this user.')) + else: + collection.title = unicode(form.title.data) + collection.description = unicode(form.description.data) + collection.slug = unicode(form.slug.data) + + collection.save() + + return redirect_obj(request, collection) + + if request.user.is_admin \ + and collection.creator != request.user.id \ + and request.method != 'POST': + messages.add_message( + request, messages.WARNING, + _("You are editing another user's collection. Proceed with caution.")) + + return render_to_response( + request, + 'mediagoblin/edit/edit_collection.html', + {'collection': collection, + 'form': form}) + + +@require_active_login +def change_pass(request): + form = forms.ChangePassForm(request.form) + user = request.user + + if request.method == 'POST' and form.validate(): + + if not auth_lib.bcrypt_check_password( + form.old_password.data, user.pw_hash): + form.old_password.errors.append( + _('Wrong password')) + + return render_to_response( + request, + 'mediagoblin/edit/change_pass.html', + {'form': form, + 'user': user}) + + # Password matches + user.pw_hash = auth_lib.bcrypt_gen_password_hash( + form.new_password.data) + user.save() + + messages.add_message( + request, messages.SUCCESS, + _('Your password was changed successfully')) + + return redirect(request, 'mediagoblin.edit.account') + + return render_to_response( + request, + 'mediagoblin/edit/change_pass.html', + {'form': form, + 'user': user}) diff --git a/mediagoblin/errormiddleware.py b/mediagoblin/errormiddleware.py new file mode 100644 index 00000000..c6789f32 --- /dev/null +++ b/mediagoblin/errormiddleware.py @@ -0,0 +1,60 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from paste.exceptions.errormiddleware import make_error_middleware + +MGOBLIN_ERROR_MESSAGE = """\ +<div style="text-align:center;font-family: monospace"> + <h1>YEOWCH... that's an error!</h1> + <pre> +.-------------------------. +| __ _ | +| -, \_,------,_// | +| <\ ,-- --.\ | +| / (x ) ( X ) | +| ' '--, ,--'\ | +| / \ -v-v-u-v / | +| . '.__.--__'.\ | +| / ',___/ / \__/' | +| | | ,'\_'/, || | +| \_| | | | | || | +| W',_ ||| |||_'' | +| | '------'| | +| |__| |_|_ | +| ,,,-' '-,,, | +'-------------------------' + </pre> + <p>Something bad happened, and things broke.</p> + <p>If this is not your website, you may want to alert the owner.</p> + <br><br> + <p> + Powered... er broken... by + <a href="http://www.mediagoblin.org">MediaGoblin</a>, + a <a href="http://www.gnu.org">GNU Project</a>. + </p> +</div>""" + + +def mgoblin_error_middleware(app, global_conf, **kw): + """ + MediaGoblin wrapped error middleware. + + This is really just wrapping the error middleware from Paste. + It should take all of Paste's default options, so see: + http://pythonpaste.org/modules/exceptions.html + """ + kw['error_message'] = MGOBLIN_ERROR_MESSAGE + return make_error_middleware(app, global_conf, **kw) diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py new file mode 100644 index 00000000..d8156126 --- /dev/null +++ b/mediagoblin/gmg_commands/__init__.py @@ -0,0 +1,108 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import argparse +import os + +from mediagoblin.tools.common import import_component + + +SUBCOMMAND_MAP = { + 'shell': { + 'setup': 'mediagoblin.gmg_commands.shell:shell_parser_setup', + 'func': 'mediagoblin.gmg_commands.shell:shell', + 'help': 'Run a shell with some tools pre-setup'}, + 'adduser': { + 'setup': 'mediagoblin.gmg_commands.users:adduser_parser_setup', + 'func': 'mediagoblin.gmg_commands.users:adduser', + 'help': 'Creates an user'}, + 'makeadmin': { + 'setup': 'mediagoblin.gmg_commands.users:makeadmin_parser_setup', + 'func': 'mediagoblin.gmg_commands.users:makeadmin', + 'help': 'Makes user an admin'}, + 'changepw': { + 'setup': 'mediagoblin.gmg_commands.users:changepw_parser_setup', + 'func': 'mediagoblin.gmg_commands.users:changepw', + 'help': 'Changes a user\'s password'}, + 'dbupdate': { + 'setup': 'mediagoblin.gmg_commands.dbupdate:dbupdate_parse_setup', + 'func': 'mediagoblin.gmg_commands.dbupdate:dbupdate', + 'help': 'Set up or update the SQL database'}, + 'assetlink': { + 'setup': 'mediagoblin.gmg_commands.assetlink:assetlink_parser_setup', + 'func': 'mediagoblin.gmg_commands.assetlink:assetlink', + 'help': 'Link assets for themes and plugins for static serving'}, + # 'theme': { + # 'setup': 'mediagoblin.gmg_commands.theme:theme_parser_setup', + # 'func': 'mediagoblin.gmg_commands.theme:theme', + # 'help': 'Theming commands', + # } + + ## These might be useful, mayyyybe, but don't really work anymore + ## due to mongo change and the "versatility" of sql options. + ## + ## For now, commenting out. Might re-enable soonish? + # + # 'env_export': { + # 'setup': 'mediagoblin.gmg_commands.import_export:import_export_parse_setup', + # 'func': 'mediagoblin.gmg_commands.import_export:env_export', + # 'help': 'Exports the data for this MediaGoblin instance'}, + # 'env_import': { + # 'setup': 'mediagoblin.gmg_commands.import_export:import_export_parse_setup', + # 'func': 'mediagoblin.gmg_commands.import_export:env_import', + # 'help': 'Imports the data for this MediaGoblin instance'}, + } + + +def main_cli(): + parser = argparse.ArgumentParser( + description='GNU MediaGoblin utilities.') + parser.add_argument( + '-cf', '--conf_file', default=None, + help=( + "Config file used to set up environment. " + "Default to mediagoblin_local.ini if readable, " + "otherwise mediagoblin.ini")) + + subparsers = parser.add_subparsers(help='sub-command help') + for command_name, command_struct in SUBCOMMAND_MAP.iteritems(): + if 'help' in command_struct: + subparser = subparsers.add_parser( + command_name, help=command_struct['help']) + else: + subparser = subparsers.add_parser(command_name) + + setup_func = import_component(command_struct['setup']) + exec_func = import_component(command_struct['func']) + + setup_func(subparser) + + subparser.set_defaults(func=exec_func) + + args = parser.parse_args() + args.orig_conf_file = args.conf_file + if args.conf_file is None: + if os.path.exists('mediagoblin_local.ini') \ + and os.access('mediagoblin_local.ini', os.R_OK): + args.conf_file = 'mediagoblin_local.ini' + else: + args.conf_file = 'mediagoblin.ini' + + args.func(args) + + +if __name__ == '__main__': + main_cli() diff --git a/mediagoblin/gmg_commands/assetlink.py b/mediagoblin/gmg_commands/assetlink.py new file mode 100644 index 00000000..148ebe9e --- /dev/null +++ b/mediagoblin/gmg_commands/assetlink.py @@ -0,0 +1,151 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os + +from mediagoblin import mg_globals +from mediagoblin.init import setup_global_and_app_config +from mediagoblin.gmg_commands import util as commands_util +from mediagoblin.tools.theme import register_themes +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.tools.common import simple_printer +from mediagoblin.tools import pluginapi + + +def assetlink_parser_setup(subparser): + # theme_subparsers = subparser.add_subparsers( + # dest=u"subcommand", + # help=u'Assetlink options') + + # # Install command + # install_parser = theme_subparsers.add_parser( + # u'install', help=u'Install a theme to this mediagoblin instance') + # install_parser.add_argument( + # u'themefile', help=u'The theme archive to be installed') + + # theme_subparsers.add_parser( + # u'assetlink', + # help=( + # u"Link the currently installed theme's assets " + # u"to the served theme asset directory")) + pass + + +########### +# Utilities +########### + +def link_theme_assets(theme, link_dir, printer=simple_printer): + """ + Returns a list of string of text telling the user what we did + which should be printable. + """ + link_dir = link_dir.rstrip(os.path.sep) + link_parent_dir = os.path.dirname(link_dir) + + if theme is None: + printer(_("Cannot link theme... no theme set\n")) + return + + def _maybe_unlink_link_dir(): + """unlink link directory if it exists""" + if os.path.lexists(link_dir) \ + and os.path.islink(link_dir): + os.unlink(link_dir) + return True + + return + + if theme.get('assets_dir') is None: + printer(_("No asset directory for this theme\n")) + if _maybe_unlink_link_dir(): + printer( + _("However, old link directory symlink found; removed.\n")) + return + + _maybe_unlink_link_dir() + + # make the link directory parent dirs if necessary + if not os.path.lexists(link_parent_dir): + os.makedirs(link_parent_dir) + + os.symlink( + theme['assets_dir'].rstrip(os.path.sep), + link_dir) + printer("Linked the theme's asset directory:\n %s\nto:\n %s\n" % ( + theme['assets_dir'], link_dir)) + + +def link_plugin_assets(plugin_static, plugins_link_dir, printer=simple_printer): + """ + Arguments: + - plugin_static: a mediagoblin.tools.staticdirect.PluginStatic instance + representing the static assets of this plugins' configuration + - plugins_link_dir: Base directory plugins are linked from + """ + # link_dir is the final directory we'll link to, a combination of + # the plugin assetlink directory and plugin_static.name + link_dir = os.path.join( + plugins_link_dir.rstrip(os.path.sep), plugin_static.name) + + # make the link directory parent dirs if necessary + if not os.path.lexists(plugins_link_dir): + os.makedirs(plugins_link_dir) + + # See if the link_dir already exists. + if os.path.lexists(link_dir): + # if this isn't a symlink, there's something wrong... error out. + if not os.path.islink(link_dir): + printer(_('Could not link "%s": %s exists and is not a symlink\n') % ( + plugin_static.name, link_dir)) + return + + # if this is a symlink and the path already exists, skip it. + if os.path.realpath(link_dir) == plugin_static.file_path: + # Is this comment helpful or not? + printer(_('Skipping "%s"; already set up.\n') % ( + plugin_static.name)) + return + + # Otherwise, it's a link that went to something else... unlink it + printer(_('Old link found for "%s"; removing.\n') % ( + plugin_static.name)) + os.unlink(link_dir) + + os.symlink( + plugin_static.file_path.rstrip(os.path.sep), + link_dir) + printer('Linked asset directory for plugin "%s":\n %s\nto:\n %s\n' % ( + plugin_static.name, + plugin_static.file_path.rstrip(os.path.sep), + link_dir)) + + +def assetlink(args): + """ + Link the asset directory of the currently installed theme and plugins + """ + mgoblin_app = commands_util.setup_app(args) + app_config = mg_globals.app_config + + # link theme + link_theme_assets(mgoblin_app.current_theme, app_config['theme_linked_assets_dir']) + + # link plugin assets + ## ... probably for this we need the whole application initialized + for plugin_static in pluginapi.hook_runall("static_setup"): + link_plugin_assets( + plugin_static, app_config['plugin_linked_assets_dir']) diff --git a/mediagoblin/gmg_commands/dbupdate.py b/mediagoblin/gmg_commands/dbupdate.py new file mode 100644 index 00000000..fa25ecb2 --- /dev/null +++ b/mediagoblin/gmg_commands/dbupdate.py @@ -0,0 +1,132 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from sqlalchemy.orm import sessionmaker + +from mediagoblin.db.open import setup_connection_and_db_from_config +from mediagoblin.db.migration_tools import MigrationManager +from mediagoblin.init import setup_global_and_app_config +from mediagoblin.tools.common import import_component + +_log = logging.getLogger(__name__) +logging.basicConfig() +_log.setLevel(logging.DEBUG) + +def dbupdate_parse_setup(subparser): + pass + + +class DatabaseData(object): + def __init__(self, name, models, migrations): + self.name = name + self.models = models + self.migrations = migrations + + def make_migration_manager(self, session): + return MigrationManager( + self.name, self.models, self.migrations, session) + + +def gather_database_data(media_types, plugins): + """ + Gather all database data relevant to the extensions we have + installed so we can do migrations and table initialization. + + Returns a list of DatabaseData objects. + """ + managed_dbdata = [] + + # Add main first + from mediagoblin.db.models import MODELS as MAIN_MODELS + from mediagoblin.db.migrations import MIGRATIONS as MAIN_MIGRATIONS + + managed_dbdata.append( + DatabaseData( + u'__main__', MAIN_MODELS, MAIN_MIGRATIONS)) + + # Then get all registered media managers (eventually, plugins) + for media_type in media_types: + models = import_component('%s.models:MODELS' % media_type) + migrations = import_component('%s.migrations:MIGRATIONS' % media_type) + managed_dbdata.append( + DatabaseData(media_type, models, migrations)) + + for plugin in plugins: + try: + models = import_component('{0}.models:MODELS'.format(plugin)) + except ImportError as exc: + _log.debug('No models found for {0}: {1}'.format( + plugin, + exc)) + + models = [] + except AttributeError as exc: + _log.warning('Could not find MODELS in {0}.models, have you \ +forgotten to add it? ({1})'.format(plugin, exc)) + models = [] + + try: + migrations = import_component('{0}.migrations:MIGRATIONS'.format( + plugin)) + except ImportError as exc: + _log.debug('No migrations found for {0}: {1}'.format( + plugin, + exc)) + + migrations = {} + except AttributeError as exc: + _log.debug('Cloud not find MIGRATIONS in {0}.migrations, have you \ +forgotten to add it? ({1})'.format(plugin, exc)) + migrations = {} + + if models: + managed_dbdata.append( + DatabaseData(plugin, models, migrations)) + + + return managed_dbdata + + +def run_dbupdate(app_config, global_config): + """ + Initialize or migrate the database as specified by the config file. + + Will also initialize or migrate all extensions (media types, and + in the future, plugins) + """ + + # Gather information from all media managers / projects + dbdatas = gather_database_data( + app_config['media_types'], + global_config.get('plugins', {}).keys()) + + # Set up the database + db = setup_connection_and_db_from_config(app_config, migrations=True) + + Session = sessionmaker(bind=db.engine) + + # Setup media managers for all dbdata, run init/migrate and print info + # For each component, create/migrate tables + for dbdata in dbdatas: + migration_manager = dbdata.make_migration_manager(Session()) + migration_manager.init_or_migrate() + + +def dbupdate(args): + global_config, app_config = setup_global_and_app_config(args.conf_file) + run_dbupdate(app_config, global_config) diff --git a/mediagoblin/gmg_commands/import_export.py b/mediagoblin/gmg_commands/import_export.py new file mode 100644 index 00000000..d51a1e3e --- /dev/null +++ b/mediagoblin/gmg_commands/import_export.py @@ -0,0 +1,254 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin import mg_globals +from mediagoblin.db.open import setup_connection_and_db_from_config +from mediagoblin.storage.filestorage import BasicFileStorage +from mediagoblin.init import setup_storage, setup_global_and_app_config + +import shutil +import tarfile +import tempfile +import subprocess +import os.path +import os +import sys +import logging +from contextlib import closing + +_log = logging.getLogger('gmg.import_export') +logging.basicConfig() +_log.setLevel(logging.INFO) + + +def import_export_parse_setup(subparser): + # TODO: Add default + subparser.add_argument( + 'tar_file') + subparser.add_argument( + '--mongodump_path', default='mongodump', + help='mongodump binary') + subparser.add_argument( + '--mongorestore_path', default='mongorestore', + help='mongorestore binary') + subparser.add_argument( + '--cache_path', + help='Temporary directory where files will be temporarily dumped') + + +def _import_media(db, args): + ''' + Import media files + + Must be called after _import_database() + ''' + _log.info('-> Importing media...') + + media_cache = BasicFileStorage( + args._cache_path['media']) + + # TODO: Add import of queue files + queue_cache = BasicFileStorage(args._cache_path['queue']) + + for entry in db.MediaEntry.find(): + for name, path in entry.media_files.items(): + _log.info('Importing: {0} - {1}'.format( + entry.title.encode('ascii', 'replace'), + name)) + + media_file = mg_globals.public_store.get_file(path, mode='wb') + media_file.write( + media_cache.get_file(path, mode='rb').read()) + + _log.info('...Media imported') + + +def _import_database(db, args): + ''' + Restore mongo database from ___.bson files + ''' + _log.info('-> Importing database...') + + p = subprocess.Popen([ + args.mongorestore_path, + '-d', db.name, + os.path.join(args._cache_path['database'], db.name)]) + + p.wait() + + _log.info('...Database imported') + + +def env_import(args): + ''' + Restore mongo database and media files from a tar archive + ''' + if not args.cache_path: + args.cache_path = tempfile.mkdtemp() + + setup_global_and_app_config(args.conf_file) + + # Creates mg_globals.public_store and mg_globals.queue_store + setup_storage() + + global_config, app_config = setup_global_and_app_config(args.conf_file) + db = setup_connection_and_db_from_config( + app_config) + + tf = tarfile.open( + args.tar_file, + mode='r|gz') + + tf.extractall(args.cache_path) + + args.cache_path = os.path.join( + args.cache_path, 'mediagoblin-data') + args = _setup_paths(args) + + # Import database from extracted data + _import_database(db, args) + + _import_media(db, args) + + _clean(args) + + +def _setup_paths(args): + ''' + Populate ``args`` variable with cache subpaths + ''' + args._cache_path = dict() + PATH_MAP = { + 'media': 'media', + 'queue': 'queue', + 'database': 'database'} + + for key, val in PATH_MAP.items(): + args._cache_path[key] = os.path.join(args.cache_path, val) + + return args + + +def _create_archive(args): + ''' + Create the tar archive + ''' + _log.info('-> Compressing to archive') + + tf = tarfile.open( + args.tar_file, + mode='w|gz') + + with closing(tf): + tf.add(args.cache_path, 'mediagoblin-data/') + + _log.info('...Archiving done') + + +def _clean(args): + ''' + Remove cache directory + ''' + shutil.rmtree(args.cache_path) + + +def _export_check(args): + ''' + Run security checks for export command + ''' + if os.path.exists(args.tar_file): + overwrite = raw_input( + 'The output file already exists. ' + 'Are you **SURE** you want to overwrite it? ' + '(yes/no)> ') + if not overwrite == 'yes': + print 'Aborting.' + + return False + + return True + + +def _export_database(db, args): + _log.info('-> Exporting database...') + + p = subprocess.Popen([ + args.mongodump_path, + '-d', db.name, + '-o', args._cache_path['database']]) + + p.wait() + + _log.info('...Database exported') + + +def _export_media(db, args): + _log.info('-> Exporting media...') + + media_cache = BasicFileStorage( + args._cache_path['media']) + + # TODO: Add export of queue files + queue_cache = BasicFileStorage(args._cache_path['queue']) + + for entry in db.MediaEntry.find(): + for name, path in entry.media_files.items(): + _log.info(u'Exporting {0} - {1}'.format( + entry.title, + name)) + try: + mc_file = media_cache.get_file(path, mode='wb') + mc_file.write( + mg_globals.public_store.get_file(path, mode='rb').read()) + except Exception as e: + _log.error('Failed: {0}'.format(e)) + + _log.info('...Media exported') + + +def env_export(args): + ''' + Export database and media files to a tar archive + ''' + if args.cache_path: + if os.path.exists(args.cache_path): + _log.error('The cache directory must not exist ' + 'before you run this script') + _log.error('Cache directory: {0}'.format(args.cache_path)) + + return False + else: + args.cache_path = tempfile.mkdtemp() + + args = _setup_paths(args) + + if not _export_check(args): + _log.error('Checks did not pass, exiting') + sys.exit(0) + + globa_config, app_config = setup_global_and_app_config(args.conf_file) + + setup_storage() + + db = setup_connection_and_db_from_config(app_config) + + _export_database(db, args) + + _export_media(db, args) + + _create_archive(args) + + _clean(args) diff --git a/mediagoblin/gmg_commands/shell.py b/mediagoblin/gmg_commands/shell.py new file mode 100644 index 00000000..4998acd7 --- /dev/null +++ b/mediagoblin/gmg_commands/shell.py @@ -0,0 +1,76 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import code + +from mediagoblin import mg_globals +from mediagoblin.gmg_commands import util as commands_util + + +def shell_parser_setup(subparser): + subparser.add_argument( + '--ipython', help='Use ipython', + action="store_true") + + +SHELL_BANNER = """\ +GNU MediaGoblin shell! +---------------------- +Available vars: + - mgoblin_app: instantiated mediagoblin application + - mg_globals: mediagoblin.globals + - db: database instance +""" + +def py_shell(**user_namespace): + """ + Run a shell using normal python shell. + """ + code.interact( + banner=SHELL_BANNER, + local=user_namespace) + + +def ipython_shell(**user_namespace): + """ + Run a shell for the user using ipython. Return False if there is no IPython + """ + try: + from IPython import embed + except: + return False + + embed( + banner1=SHELL_BANNER, + user_ns=user_namespace) + return True + +def shell(args): + """ + Setup a shell for the user either a normal Python shell or an IPython one + """ + user_namespace = { + 'mg_globals': mg_globals, + 'mgoblin_app': commands_util.setup_app(args), + 'db': mg_globals.database} + + if args.ipython: + ipython_shell(**user_namespace) + else: + # Try ipython_shell first and fall back if not available + if not ipython_shell(**user_namespace): + py_shell(**user_namespace) diff --git a/mediagoblin/gmg_commands/users.py b/mediagoblin/gmg_commands/users.py new file mode 100644 index 00000000..024c8498 --- /dev/null +++ b/mediagoblin/gmg_commands/users.py @@ -0,0 +1,103 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.gmg_commands import util as commands_util +from mediagoblin.auth import lib as auth_lib +from mediagoblin import mg_globals + +def adduser_parser_setup(subparser): + subparser.add_argument( + '--username','-u', + help="Username used to login") + subparser.add_argument( + '--password','-p', + help="Your supersecret word to login, beware of storing it in bash history") + subparser.add_argument( + '--email','-e', + help="Email to receive notifications") + + +def adduser(args): + #TODO: Lets trust admins this do not validate Emails :) + commands_util.setup_app(args) + + args.username = commands_util.prompt_if_not_set(args.username, "Username:") + args.password = commands_util.prompt_if_not_set(args.password, "Password:",True) + args.email = commands_util.prompt_if_not_set(args.email, "Email:") + + db = mg_globals.database + users_with_username = \ + db.User.find({ + 'username': args.username.lower(), + }).count() + + if users_with_username: + print u'Sorry, a user with that name already exists.' + + else: + # Create the user + entry = db.User() + entry.username = unicode(args.username.lower()) + entry.email = unicode(args.email) + entry.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password) + entry.status = u'active' + entry.email_verified = True + entry.save() + + print "User created (and email marked as verified)" + + +def makeadmin_parser_setup(subparser): + subparser.add_argument( + 'username', + help="Username to give admin level") + + +def makeadmin(args): + commands_util.setup_app(args) + + db = mg_globals.database + + user = db.User.one({'username': unicode(args.username.lower())}) + if user: + user.is_admin = True + user.save() + print 'The user is now Admin' + else: + print 'The user doesn\'t exist' + + +def changepw_parser_setup(subparser): + subparser.add_argument( + 'username', + help="Username used to login") + subparser.add_argument( + 'password', + help="Your NEW supersecret word to login") + + +def changepw(args): + commands_util.setup_app(args) + + db = mg_globals.database + + user = db.User.one({'username': unicode(args.username.lower())}) + if user: + user.pw_hash = auth_lib.bcrypt_gen_password_hash(args.password) + user.save() + print 'Password successfully changed' + else: + print 'The user doesn\'t exist' diff --git a/mediagoblin/gmg_commands/util.py b/mediagoblin/gmg_commands/util.py new file mode 100644 index 00000000..6a6853d5 --- /dev/null +++ b/mediagoblin/gmg_commands/util.py @@ -0,0 +1,40 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from mediagoblin import app +import getpass + + +def setup_app(args): + """ + Setup the application after reading the mediagoblin config files + """ + mgoblin_app = app.MediaGoblinApp(args.conf_file) + + return mgoblin_app + +def prompt_if_not_set(variable, text, password=False): + """ + Checks if the variable is None and prompt for a value if it is + """ + if variable is None: + if not password: + variable=raw_input(text + u' ') + else: + variable=getpass.getpass(text + u' ') + + return variable diff --git a/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..543830c8 --- /dev/null +++ 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 new file mode 100644 index 00000000..1f086613 --- /dev/null +++ b/mediagoblin/i18n/ar/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1256 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Jiyda <jiydam@gmail.com>, 2013 +# Majid Al-Dharrab <majid@aldharrab.com>, 2011 +# minaeid90 <minaeid90@gmail.com>, 2013 +# OmarKH <Omar.w.kh@gmail.com>, 2011 +# OsamaK <osamak@gnu.org>, 2011 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Arabic (http://www.transifex.com/projects/p/mediagoblin/language/ar/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ar\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "اسم المستخدم" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "كلمة السر" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "عنوان البريد الإلكتروني" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "اسم المستخدم او الايميل" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "اسم مستخدم او ايميل غير صحيح." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "هذا الحقل لا يأخذ ايميل." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "هذا الحقل يحتاج ايميل." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "عفوًا، التسجيل غير متاح هنا." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "عذرًا، لقد اختار مستخدم آخر هذا الاسم." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "عذرًا، لقد اختار مستخدم آخر هذا الايميل." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "تم التحقق من بريدك الإلكتروني. يمكنك الآن الولوج، وتحرير ملفك الشخصي، ونشر الصور!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "مفتاح التحقق أو معرف المستخدم خاطئ" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "يجب عليك تسجيل الدخول لإرسال بريد الكترونى لك!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "لقد قمت بالفعل بالتحقق من عنوان البريد الإلكتروني الخاص بك!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "أعدنا إرسال رسالة التحقق." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "إذا كان هذا الايميل(حساس للحروف الكبيرة والصغيرة!) مُسجل, فقد تم إرسال ايميل به تعليمات عن كيفية تغيير رقمك السري." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "لم نتمكن من العثور على أحد له أسم المستخدم هذا." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "لقد تم إرسال ايميل به تعليمات عن كيفية تغيير رقمك السري." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "تعذر إرسال رسالة استعادة كلمة السر لأن اسم المستخدم معطل أو لأننا لم نتحقق من بريدك الإلكتروني." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "تستطيع الآن الدخول باستخدام رقمك السري الجديد." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "العنوان" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "وصف هذا العمل." + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "بامكانك استخدام ⏎\n<a href=\"http://daringfireball.net/projects/markdown/basics\">⏎\nMarkdown</a> للإدراج." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "الوسوم" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "قم بفصل المحددات بفصلة." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "المسار" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "لا يمكن ترك المسار فارغًا" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "مقدمة عنوان هذه الميديا, غالبا لن تحتاج لتغيره." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "ترخيص" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "السيرة" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "الموقع الإلكتروني" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "العنوان يحتوي على اخطاء" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "تفضيل رخصة" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "سوف تكون هذه رخصتك المبدئية في نماذج التحميل." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "ارسل لي رسالة عندما يقوم الاخرون بالتعليق على الميديا خاصتي" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "لا يمكن ترك العنوان فارغًا" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "وصف هذه المجموعة" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "مقدمة عنوان هذه المجموعة, غالبا لن تحتاج لتغيره." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr " كلمة السر القديمة" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "قم بإدخال رقمك السري القديم حتى تثبت انك صاحب هذا الحساب." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "رقم سري جديد" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "يوجد ملف آخر بهذا المسار لدى هذى المستخدم." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "أنت تحرّر وسائط مستخدم آخر. كن حذرًا أثناء العملية." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "لقد قمت بإضافة مرفقة %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "يمكنك فقط تعديل حسابك الخاص" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "أنت تحرّر ملف مستخدم آخر. كن حذرًا أثناء العملية." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "تم حفظ تغيرات حسابك" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "تم حفظ خصائص حسابك" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "يجب عليك تأكيد إلغاء حسابك." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "أنت لديك مجموعة تدعى \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "توجد مجموعة اخرى بهذا المسار لهذا المستخدم." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "أنت تعدل مجموعة مستخدم آخر. كن حذرًا أثناء العملية." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "كلمة سر خاطئة" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "لم يتم ربط الثيم... لاتوجد مجموعة ثيمات\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "لا يوجد مسار جيد لهذا الثيم\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "ولكن, الرابط القديم للمسار الذي تم ايجاده; حُذف.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "CSRF كوكيز غير موجودة, وهذا من الممكن ان يكون نتيجة لمانع الكوكيز او شئ من هذا القبيل.<br/>تأكد من أنك قمت بالسماح لخصائص الكوكيز لهذا الميدان." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "عذرا, انا لا ادعم هذا النوع من الملفات :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "فشل في تحويل الفيديو" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "المكان" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "عرض في <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "سماح" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "رفض" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "الاسم" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "اسم العميل المنشِئ" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "الوصف" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "سوف يكون هذا مرئي بالنسبة للمستخدمين حتى يتاح\nللبرنامج خاصتك بالتصديق عليهم." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "النوع" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>سري</strong> - يستطيع العميل\nان يقوم بطلب نسخة من GNU MediaGoblin والتي من الممكن ان \nيعترضه وكيل المستخدم (مثلا الخادم من جانب العميل).<br />\n<strong>عام</strong> - لا يستطيع العميل ارسال طلبات سرية\nلنسخة من GNU MediaGoblin (مثلا \nخادم الجافا سكريبت من جانب العميل)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "تحويل لينك" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "الرابط الموجه للبرنامج, هذا الحقل\n<strong>مطلوب</strong> لجمهور العملاء." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "هذا الحقل مطلوب لجمهور العملاء" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "العميل {0} تم تسجيله!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "ارتباطات العميل المنشئ" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "عميلك المنشئ" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "اضف" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "الملف المعطى لهذا النوع من الميديا غير صحيح." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "الملف" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "يجب أن تضع ملفًا." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "يا سلام! نُشرَت!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "تم إضافة المجموعة \"%s\"!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "تأكد من بريدك الإلكترونى!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "تسجيل خروج" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "تسجيل دخول" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "<a href=\"%(user_url)s\">%(user_name)s</a>'s حساب" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "تغيير خصائص الحساب" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "لوحة معالجة الوسائط" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "تسجيل خروج" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "أضف وسائط" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "إنشاء مجموعة جديدة" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "صورة قزم مرتبك" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "أحدث الوسائط" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "يمكنك متابعة عملية معالجة وسائط معرضك من هنا." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "توجد وسائط تحت المعالجة" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "لا توجد وسائط تحت المعالجة" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "فشلت معالجة هذه الملفات:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "لا توجد مداخل فاشلة!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "آخر 10 تحويلات ناجحة" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "لا يوجد مداخل مُعالجة بعد! " + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "قم بضبط رقمك السري الجديد" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "قم بضبط رقم سري" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "استعادة كلمة السر" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "ارسل تعليمات" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "مرحبًا يا %(username)s،\n\nإن أردت تغيير كلمة سرك في غنو ميدياغوبلن فافتح الوصلة التالية في متصفحك:\n\n%(verification_url)s\n\nإن كنت ترى أن هذه الرسالة وصلتك خطأً فتجاهلها واستمتع بحياتك!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "فشل الولوج!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "ألا تملك حسابًا بعد؟" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "أنشئ حسابًا هنا!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "أنسيت كلمة سرك؟" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "أنشئ حسابًا!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "أنشئ" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "أهلًا يا %(username)s،\n\nافتح الرابط التالي\nفي متصفحك لتفعيل حسابك في غنو ميدياغوبلن:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "برعاية <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> مشروع." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "تم النشر وفقا ل <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Source code</a> متاح." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "استكشف" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "اهلا, مرحبا بك في موقع MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "هذا الموقع يقوم بتشغيل <a href=\"http://mediagoblin.org\">MediaGoblin</a>, وهو برنامج استضافة ميديا فائق الروعة." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "لكي تضيف الميديا خاصتك, تضع التعليقات, والمزيد, يجب عليك الدخول بحساب MediaGoblin الخاص بك." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "ليس لديك واحد حتى الآن؟ انه سهل!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "شعار ميدياغوبلن" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "تعديل المرفقات ل %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "مرفقات" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "أضف مرفقة" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "ألغِ" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "احفظ التغييرات" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "هل تريد فعلا إلغاء المستخدم '%(user_name)s' وكل الميديا/التعليقات المتعلقة به؟" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "نعم, قم بإلغاء حسابي" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "احذف نهائيًا" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "تحرير %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "نغيير %(username)s خصائص الحساب" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "إلغِ حسابي" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "تحرير %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "تحرير ملف %(username)s الشخصي" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "يتم تحديد الميديا ب: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "تحميل" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "أصلي" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "عذرا, لن يتم تشغيل الصوت لأن ⏎\n»متصفحك لا يدعم HTML5 ⏎\n»صوتيا." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "تستطيع الحصول على متصفح حديث ⏎\n»يمكنه تشغيل الصوت في <a href=\"http://getfirefox.com\">⏎\n» http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "ملف أصلي" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "ملف WebM (Vorbic كوديك)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "صورة ل%(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "تبديل التدوير" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "منظور" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "مقدمة" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "أعلى" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "جانب" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "تحميل نموذج" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "بنية الملف" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "طول الكائن" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "عذرا, لن يتم تشغيل هذا الفيديو لأن ⏎\n»متصفحك لا يدعم HTML5 ⏎\n»فيديو." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "تستطيع الحصول على متصفح حديث ⏎\n»يمكنه تشغيل هذا الفيديو في <a href=\"http://getfirefox.com\">⏎\n» http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM ملف (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "إضافة مجموعة" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "اضف الميديا الخاصة بك" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (%(username)s's مجموعة)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s بواسطة <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "تعديل" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "إلغاء" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "أتود حقًا حذف %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "هل تريد فعلا إلغاء %(media_title)s من %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "إلغاء" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "%(username)s's مجموعات" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>'s مجموعات" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "اهلا, %(username)s,\n%(comment_author)s قام بالتعليق على مشاركتك (%(comment_url)s) في %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "%(username)s ميديا" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "<a href=\"%(user_url)s\">\n%(username)s\n</a>\n's ميديا بالمحدد\n<a href=\"%(tag_url)s\">\n%(tag)s\n</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "وسائط <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ اختيار الميديا بواسطة <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "أضف تعليق" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "اضف هذا التعليق" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "إضافة “%(media_title)s” لمجموعة" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "إضافة مجموعة جديدة" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "يمكنك متابعة عملية معالجة وسائط معرضك من هنا." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "آخر 10 تحميلات ناجحة خاصة بك" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "ملف %(username)s الشخصي" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "عذرًا، تعذر العثور على مستخدم بهذا الاسم." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "يجب التحقق من البريد الإلكتروني" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "أوشكنا على الانتهاء! ما زال حسابك بحاجة إلى التفعيل." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "ستصلك رسالة إلكترونية خلال لحظات بها التعليمات." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "إن لم تصل." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "أعد إرسال رسالة التحقق" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "سجّل أحدهم حسابًا بهذا الاسم، ولكننا بانتظار التفعيل حتى الآن." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "إن كنت أنت ذلك الشخص لكنك فقدت رسالة التحقق، يمكنك <a href=\"%(login_url)s\">الولوج</a> وإعادة إرسالها." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "هذه زاوية لتخبر الآخرين فيها عن نفسك." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "حرِّر الملف الشخصي" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "لم يعبئ هذا العضو بيانات ملفه بعد." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "تحديد مجموعة" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "أظهِر كل وسائط %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "هنا ستظهر وسائطك، ولكن يبدو أنك لم تضف شيئًا بعد." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "لا يبدو أنه توجد أي وسائط هنا حتى الآن..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(إلغاء)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "تم تجميعه في" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "إضافة مجموعة" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "ايقونة تغذية" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "تغذية ذرية" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "جميع الحقوق محفوظة" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "اجدد←" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "→اقدم" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "اذهب إلى صفحة:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "اجدد" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "اقدم" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "تحدد ب" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "لم نستطيع قراءة هذه الصورة." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "ويحي!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "حدث خطأ" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "غير مسموح بهذه العملية" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "عذرا ديف, لا استطيع ترك تفعل هذا!</p><p>لقد حاولت تشغيل خاصية ليست مسموحة لك. هل كنت تحاول إلغاء جميع حسابات المستخدمين مجددا؟" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "يبدو أنه لا توجد صفحة بهذا العنوان, عذرا</p><p>إذا كنت متأكد من صحة العنوان, من الممكن أن تكون الصفحة التي تبحث عنها قد تم نقلها أو إلغاءها." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "تعليق" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "بامكانك استخدام <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> للإدراج." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "أنا متأكد من رغبتي بحذف هذا العمل" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "أنا متأكد من أنني أريد إلغاء هذه المادة من المجموعة" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "مجموعة" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- إختار --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "إدراج ملاحظة" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "قام بالتعليق على مشاركتك" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "عذرا, لقد قمت بادخال تعليق فارغ." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "لقد تم إرسال تعليقك!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "من فضلك قم بفحص المداخل وقم بالمحاولة مرة أخرى." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "يجب عليك إختيار أو إضافة مجموعة" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" توجد بالفعل في المجموعة \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" أُضيفت للمجموعة \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "لقد قمت بإلغاء الميديا." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "لم يتم إلغاء الميديا لأنك لم تقم بإختيار انك متأكد من ذلك." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "أنت على وشك حذف وسائط مستخدم آخر. كن حذرًا أثناء العملية." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "لقد قمت بإلغاء المادة من المجموعة." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "لم يتم إلغاء المادة لأنك لم تقم بإختيار انك متأكد من ذلك." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "أنت على وشك حذف مادة من مجموعة مستخدم آخر. كن حذرا." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "لقد قمت بإلغاء المجموعة \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "لم يتم إلغاء المجموعة لأنك لم تقم بإختيار انك متأكد من ذلك." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "أنت على وشك حذف مجموعة مستخدم آخر. كن حذرا." diff --git a/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..ec01d7f7 --- /dev/null +++ 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 new file mode 100644 index 00000000..9ebbdf18 --- /dev/null +++ b/mediagoblin/i18n/ca/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1254 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Al fred <devaleitzer@aim.com>, 2011 +# Al fred <devaleitzer@aim.com>, 2011 +# skarbat <skarbat@gmail.com>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Catalan (http://www.transifex.com/projects/p/mediagoblin/language/ca/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ca\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Nom d'usuari" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Contrasenya" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Adreça electrònica" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Nom d'usuari o correu" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Ho sentim, el registre està desactivat en aquest cas." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Lamentablement aquest usuari ja existeix." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Perdó, ja existeix un usuari amb aquesta adreça de correu." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Ja s'ha verificat la vostra adreça electrònica. Ara podeu entrar, editar el vostre perfil i penjar imatge!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "La clau de verificació o la identificació de l'usuari no són correctes." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Has d'estar conectat per saber a qui hem d'enviar el correu!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Ja has verificat la teva adreça de correu!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Torna'm a enviar el correu de verificació" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "S'ha enviat un correu amb instruccions de com cambiar la teva contrasenya" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "No hem pogut enviar el correu de recuperació de contrasenya perquè el teu nom d'usuari és inactiu o bé l'adreça electrònica del teu compte no ha sigut verificada." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Ara et pots conectar amb la teva nova contrasenya." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Títol" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Descripció d'aquest treball." + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Pots utilitzar⏎ <a href=\"http://daringfireball.net/projects/markdown/basics\">⏎ Markdown</a> per donar-li format" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etiquetes" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Separa els tags amb comes." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Llimac" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "El llimac no pot ésser buit" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "El títol de l'adreça d'aquest mitjà. Normalment no necessites modificar això." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Llicència" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Biografia" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Lloc web" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Aquesta adreça conté errors" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Envia'm correu quan d'altres comentin al meu mitjà" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "El títol no pot ser buit" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Descripció d'aquesta col.lecció" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "La part del títol de l'adreça d'aquesta col.lecció. Normalment no cal que canviis això." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Contrasenya antiga" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Introdueix la teva contrasenya antiga per comprovar que aquest compte és teu." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nova contrasenya" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Ja existeix una entrada amb aquest llimac per aquest usuari" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Esteu editant fitxers d'un altre usuari. Aneu amb compte." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Esteu editant el perfil d'un usuari. Aneu amb compte" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Els canvis al perfil s'han guardat" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Els detalls del compte s'han guardat" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Ja tens una col.lecció anomenada \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Estas editant la col.lecció d'un altre usuari. Prossegueix amb cautela." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Contrasenya errònia" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "No es pot enllaçar el tema... no hi ha tema establert\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Tot i així, l'enllaç antic al directori s'ha trobat; eliminat.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Ho sento, no puc manegar aquest tipus d'arxiu :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "La transformació del vídeo ha fallat" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Ubicació" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Veure a <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Permetre" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Denegar" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nom" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "El nom del client OAuth" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Descripció" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Això serà visiable a usuaris que permetin que la teva aplicació\n s'autentifiqui com a ells." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Tipus" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Confidencial</strong> - El client pot\n fer peticions a la instància GNU MediaGoblin que no pot ésser\n interceptada per l'agent d'usuari (el client a la part servidor).<br />\n <strong>Public</strong> - El client no pot fer peticions \n confidencials a la instància GNU MediaGoblin (la part \n client JavaScript)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Redireccionar URI " + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "La URI de redirecció per les aplicacions, aquest camp\n és <strong>requeriment</strong> per els clients públics." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Aquest camp és requeriment per a clients públics" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "El client {0} ha sigut enregistrat!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Afegir" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Aquest tipus de fitxer no és vàlid." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Fitxer" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Heu d'escollir un fitxer." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Visca! S'ha enviat!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "S'ha afegit la col.leccio \"%s\"!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifica el teu correu electrònic" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Entra" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Modificar els ajustaments del compte" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Quadre de processament de fitxers" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Tots els fitxers" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Mitjans més recents" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Aqui pots seguir l'estat del mitjà que s'està processant a aquesta instància." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "S'està processant el fitxer" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "No s'està processant cap mitjà" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "No s'han pogut penjar els següents fitxers:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Sense entrades fallades!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Les últimes 10 pujades correctes" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Encara no hi ha entrades processades!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Estableix la teva nova contrasenya" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Establir contrasenya" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Recuperar contrasenya" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Enviar instruccions" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Hola %(username)s,⏎ ⏎ per cambiar la teva contrasenya de GNU MediaGoblin, obre la següent URL al ⏎ teu navegador:⏎ ⏎ %(verification_url)s⏎ ⏎ Si creus que hi ha un error, ignora el correu i continua essent⏎ un goblin feliç!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Inici de sessió ha fallat!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Encara no teniu un compte?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Creeu-ne un aquí!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Has oblidat la teva contrasenya?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Creeu un compte!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Crea" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Hi %(username)s,\n\nto activate your GNU MediaGoblin account, open the following URL in\nyour web browser:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Alliberat segons la <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Codi font</a> disponible." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Explorar" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hola, una benvinguda al MediaGoblin!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "El lloc esta usant <a href=\"http://mediagoblin.org\">MediaGoblin</a>, una gran i extraordinària peça de software per allotjar mitjans." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Per afegir el teu propi mitjà, col.locar comentaris, i més, pots conectar-te amb el teu compte MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "No en tens una encara? Es fàcil!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Logo de mediagoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Editant afegits per a %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Cancel·la" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Desa els canvis" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Esborrar permanentment" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Edició %(media_title)s " + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Modificant els detalls del compte de %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Editant %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Editant perfil de %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Mitjà marcat amb: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Descarregar" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Ho sento, aquest audiothis àudio no funcionarà perque \n »el teu navegador web no contempla suport d'àudio \n »HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Pots obtenir un navegador web modern que \n »podrà reproduir l'àudio, a <a href=\"http://getfirefox.com\">\n » http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Arxiu original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "Arxiu WebM (Vorbis codec)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Imatge per %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "Arxiu WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Afegir a la col.lecció" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Afegeix el teu mitjà" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (la col.lecció de %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s per a <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Editar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Esborrar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Realment vols esborrar %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Relment eliminar %(media_title)s de %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Eliminar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Hola %(username)s,\n%(comment_author)s ha comentat el teu post (%(comment_url)s) a %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Mitjà de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>'s media" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Navegant mitjà per a <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Afegeix un comentari" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Afegir aquest comentari" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Afegir una nova col.lecció" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Aqui pots seguir l'estat del mitjà que s'està processant per la teva galeria" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Les teves 10 últimes pujades correctes" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Perfil de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Lamentablement no s'ha trobat l'usuari que cercàveu." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Cal que verifiqueu l'adreça electrònica" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Gairebé esteu! Tan sols falta que activeu el vostre compte" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Us hauria d'arribar un correu amb les instruccions per a fer-ho." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Per si no hi fos:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Torna'm a enviar el correu de verificació" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Algú ja ha registrat un compte amb aquest nom d'usuari, però encara l'ha d'activar." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Si siu aqeust usuari però heu perdut el correu de verificació, podeu <a href=\"%(login_url)s\">entrar</a> i tornar-lo a enviar." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Aqui hi ha un espai per explicar de tu als demés" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Edita el perfil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Aquest usuari encara no ha escrit res al seu perfil." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "View all of %(username)s's media" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Aqui és on apareixerà el teu mitjà, però sembla que encara no hi has afegit res." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Sembla que no hi ha cap mitjà aqui encara..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "Icona RSS" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Tots els drets reservats" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Més nou" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Més antic →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Anar a la pàgina:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "més nou" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "més antic" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "No s'ha pogut llegir l'arxiu d'imatge" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Ups!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Pots usar <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> per donar format." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Estic segur que vull esborrar això" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Estic segur que vull esborrar aquest element de la col.lecció" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Sel.leccionar --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Incluir una nota" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "comentat al teu post" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Uups, el teu comentari era buit." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "El teu comentari s'ha publicat!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Si et plau, comprova les teves entrades i intenta-ho de nou." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Has de sel.leccionar o afegir una col.lecció" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" ja és a la col.lecció \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" afegir a la col.lecció \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Has esborrat el mitjà" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "El mitjà no s'ha esborrat perque no has marcat que n'estiguessis segur." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Ets a punt d'esborrar el mitjà d'un altre usuari. Prossegueix amb cautela." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Has esborrat l'element de la col.lecció" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "L'element no s'ha eliminat perque no has marcat que n'estiguessis segur." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Ets a punt d'esborrar un element de la col.lecció d'un altre usuari. Prossegueix amb cautela." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Has esborrat la col.lecció \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "La col.lecció no s'ha esborrat perquè no has marcat que n'estiguessis segur." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Ets a punt d'esborrar la col.lecció d'un altre usuari. Prossegueix amb cautela." diff --git a/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..53e3fedf --- /dev/null +++ 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 new file mode 100644 index 00000000..c78c08ac --- /dev/null +++ b/mediagoblin/i18n/da/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1254 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Morten Juhl-Johansen Zölde-Fejér <morten@writtenandread.net>, 2012 +# Olle Jonsson <olle.jonsson@gmail.com>, 2012 +# ttrudslev <tanja.trudslev@gmail.com>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Danish (http://www.transifex.com/projects/p/mediagoblin/language/da/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: da\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Brugernavn" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Kodeord" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Email adresse" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Brugernavn eller email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Desværre, registrering er ikke muligt på denne instans" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Desværre, det brugernavn er allerede brugt" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Desværre, en bruger er allerede oprettet for den email" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Din email adresse er blevet bekræftet. Du kan nu logge på, ændre din profil, og indsende billeder!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Bekræftelsesnøglen eller brugerid er forkert" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Du er nødt til at være logget ind, så vi ved hvem vi skal emaile!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Du har allerede bekræftet din email adresse!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Email til godkendelse sendt igen." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "En email er blevet sendt med instruktioner til at ændre dit kodeord." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Vi kunne ikke sende en kodeords nulstillings email da dit brugernavn er inaktivt, eller din konto's email adresse er ikke blevet godkendt." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Du kan nu logge ind med dit nye kodeord." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titel" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Beskrivelse af arbejdet" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Du kan bruge\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> til formattering." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Tags" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Separer tags med kommaer." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Titeldelen af dette medie's adresse. Du behøver normalt ikke ændre dette." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licens" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Bio" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Websted" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Denne adresse indeholder fejl" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Email mig når andre kommenterer på mine medier" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Titlen kan ikke være tom" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Beskrivelse af denne samling" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Titeldelen af denne samlings's adresse. Du behøver normalt ikke ændre dette." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Gammelt kodeord" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Skriv dit gamle kodeord for at bevise det er din konto." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Ny kodeord" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Du er ved at ændre en anden brugers' medier. Pas på." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Du er ved at ændre en bruger's profil. Pas på." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Profilændringer gemt" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Kontoindstillinger gemt" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Du har allerede en samling ved navn \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Du er ved at ændre en anden bruger's samling. Pas på." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Forkert kodeord" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Kan ikke linke til tema... intet tema sat\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Desværre, jeg understøtter ikke den filtype :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Tillad" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Forbyd" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Navn" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Navnet af OAuth klienten" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Beskrivelse" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Type" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Dette felt er nødvendigt for offentlige klienter" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Klienten {0} er blevet registreret!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Forkert fil for medietypen." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Fil" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Du må give mig en fil" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Juhuu! Delt!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Bekræft din email!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Log ind" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Har du endnu ikke en konto?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Opret en her!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Opret en konto!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Udforsk" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hey, velkommen til denne MediaGoblin side!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "For at tilføje dine egne medier, skrive kommentarer, og mere, du kan logge ind med din MediaGoblin konto." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Har du ikke en endnu? Det er let!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin logo" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Afbryd" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Gem ændringer" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Redigerer %(username)s profil" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Desværre, fandt ikke den bruger." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Næsten færdig! Din konto skal stadig aktiveres." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Der skulle komme email om et par øjeblikke med instrukser om hvordan." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Hvis det ikke gør:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Gensend verificeringsemail" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Her kan du fortælle andre om dig selv." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Ret profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Hovsa!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..e2fcf85d --- /dev/null +++ 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 new file mode 100644 index 00000000..e2147070 --- /dev/null +++ b/mediagoblin/i18n/de/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1265 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# piratenpanda <benjamin@lebsanft.org>, 2011 +# cwebber <cwebber@dustycloud.org>, 2011 +# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011-2012 +# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2013 +# Jakob Kramer <jakob.kramer@gmx.de>, 2011, 2012 +# Jakob Kramer <jakob.kramer@gmx.de>, 2012-2013 +# Jan-Christoph Borchardt <hey@jancborchardt.net>, 2011 +# Jan-Christoph Borchardt <hey@jancborchardt.net>, 2011, 2012 +# Keyzo <kyoo@kyoo.ch>, 2011 +# Elrond <elrond+mediagoblin.org@samba-tng.org>, 2011 +# Art O. Pal <artopal@fastmail.fm>, 2011 +# spaetz <sebastian@sspaeth.de>, 2012 +# Vinzenz Vietzke <vinz@vinzv.de>, 2012 +# Vinzenz Vietzke <vinz@vinzv.de>, 2011 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-28 10:43+0000\n" +"Last-Translator: Elrond <elrond+mediagoblin.org@samba-tng.org>\n" +"Language-Team: German (http://www.transifex.com/projects/p/mediagoblin/language/de/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Benutzername" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Passwort" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "E-Mail-Adresse" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Benutzername oder E-Mail-Adresse" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Benutzername oder E-Mail-Adresse" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Ungültiger Benutzername oder E-Mail-Adresse." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Dieses Feld akzeptiert keine E-Mail-Adressen." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Dieses Feld benötigt eine E-Mail-Adresse." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Benutzerregistrierung ist auf diesem Server leider deaktiviert." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Leider gibt es bereits einen Benutzer mit diesem Namen." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Leider gibt es bereits einen Benutzer mit dieser E-Mail-Adresse." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Dein GNU MediaGoblin Konto wurde hiermit aktiviert. Du kannst dich jetzt anmelden, dein Profil bearbeiten und Medien hochladen." + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Der Aktivierungsschlüssel oder die Nutzerkennung ist falsch." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Du musst angemeldet sein, damit wir wissen, wer die Email bekommt." + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Deine E-Mail-Adresse wurde bereits aktiviert." + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Aktivierungsmail wurde erneut versandt." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Falls jemand mit dieser E-Mail-Adresse (Groß- und Kleinschreibung wird unterschieden!) registriert ist, wurde eine E-Mail mit Anleitungen verschickt, wie Du Dein Passwort ändern kannst." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Es konnte niemand mit diesem Benutzernamen gefunden werden." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Es wurde eine E-Mail mit der Anleitung zur Änderung des Passwortes an Dich gesendet." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Die E-Mail zur Wiederherstellung des Passworts konnte nicht verschickt werden, weil dein Benutzername inaktiv oder deine E-Mail-Adresse noch nicht aktiviert wurde." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Du kannst dich jetzt mit deinem neuen Passwort anmelden." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titel" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Beschreibung des Werkes" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Die Texte lassen sich durch <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> formatieren." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Schlagwörter" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Kommaseparierte Schlagwörter" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Kurztitel" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Bitte gib einen Kurztitel ein" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Der Titelteil der Medienadresse. Normalerweise muss hier nichts geändert werden." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Lizenz" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Biographie" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Webseite" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Diese Adresse ist fehlerhaft" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Bevorzugte Lizenz" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Dies wird Deine Standardlizenz in den Upload-Forumularen sein." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Mir eine E-Mail schicken, wenn andere meine Medien kommentieren" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Der Titel kann nicht leer sein" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Beschreibung dieser Sammlung" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Der Titelteil dieser Sammlungsadresse. Du musst ihn normalerweise nicht ändern." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Altes Passwort" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Gib dein altes Passwort ein, um zu bestätigen, dass du dieses Konto besitzt." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Neues Passwort" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Diesen Kurztitel hast du bereits vergeben." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Du bearbeitest die Medien eines anderen Nutzers. Sei bitte vorsichtig." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Sie haben den Anhang %s hinzugefügt!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Du kannst nur dein eigenes Profil bearbeiten." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Du bearbeitest das Profil eines anderen Nutzers. Sei bitte vorsichtig." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Das Profil wurde aktualisiert" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Kontoeinstellungen gespeichert" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Du musst die Löschung deines Kontos bestätigen." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Du hast bereits eine Sammlung mit Namen »%s«!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Eine Sammlung mit diesem Kurztitel existiert bereits für diesen Benutzer." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Du bearbeitest die Sammlung eines anderen Benutzers. Sei vorsichtig." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Falsches Passwort" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Theme kann nicht verknüpft werden … Kein Theme gesetzt\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Für dieses Theme gibt es kein asset-Verzeichnis\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Trotzdem wurde eine alte Verknüpfung gefunden; sie wurde entfernt\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "Das CSRF cookie ist nicht vorhanden. Das liegt vermutlich an einem Cookie-Blocker oder ähnlichem.<br/>Bitte stelle sicher, dass Cookies von dieser Domäne erlaubt sind." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Entschuldigung, dieser Dateityp wird nicht unterstützt." + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Videokonvertierung fehlgeschlagen" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Aufnahmeort" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "In <a href=\"%(osm_url)s\">OpenStreetMap</a> öffnen" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Erlauben" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Verweigern" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Name" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Der Name des OAuth-Clients" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Beschreibung" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Dies wird für Benutzer sichtbar sein, die deiner\nAnwendung erlauben, sich als sie zu authentifizieren.." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Typ" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Vertraulich</strong> - Der Client kann\n Anfragen an die GNU MediaGoblin Instanz stellen, die nicht durch den \n Benutzer-Agent (z.B. serverseitiger Client) unterbunden werden können.<br />\n <strong>Öffentlich</strong> - Der Client kann keine vertraulichen \n Anfragen an die GNU MediaGoblin Instanz stellen (z.B. clientseitiger\n JavaScript Client)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Weiterleitungs-URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "Die Weiterleitungs-URI für die Anwendung, dieses Feld\n ist <strong>Pflicht</strong> für öffentliche Clients." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Dieses Feld ist Pflicht für öffentliche Clients" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Client {0} wurde registriert!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "OAuth-Client-Verbindungen" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Deine OAuth-Clients" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Hinzufügen" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Die Datei stimmt nicht mit dem gewählten Medientyp überein." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Datei" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Du musst eine Datei angeben." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "JAAA! Geschafft!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Sammlung »%s« hinzugefügt!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Bitte bestätige Deine E-Mail-Adresse!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "abmelden" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Anmelden" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "<a href=\"%(user_url)s\">%(user_name)s</a>s Konto" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Kontoeinstellungen ändern" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Medienverarbeitung" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Abmelden" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Medien hinzufügen" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Neues Album erstellen" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Bild eines gestressten Goblins" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Neuste Medien" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Hier kann man den Status von zu verarbeitenden Medien in diesem MediaGoblin-Exemplar sehen." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Medien in Bearbeitung" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Keine Medien in Bearbeitung" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Die folgenden Uploads sind fehlgeschlagen:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Keine fehlgeschlagenen Einträge!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Die letzten zehn erfolgreichen Uploads" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Noch keine verarbeiteten Einträge!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Dein neues Passwort" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Passwort setzen" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Passwort wiederherstellen" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Anweisungen senden" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Hallo %(username)s,\n\num dein GNU-MediaGoblin-Passwort zu ändern, öffne folgende URL\nin deinem Webbrowser:\n\n%(verification_url)s\n\nWenn du denkst, dass es sich hierbei um einen Fehler handelt,\nignoriere einfach diese E-Mail und bleib ein glücklicher Goblin!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Anmeldevorgang fehlgeschlagen!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Hast du noch keines?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Registriere dich einfach hier!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Passwort vergessen?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Neues Nutzerkonto registrieren!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Registrieren" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Hallo %(username)s,\n\num deinNutzerkonto bei GNU MediaGoblin zu aktivieren, musst du folgende Adresse in deinem Webbrowser öffnen:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Läuft mit <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, einem <a href=\"http://gnu.org/\">GNU</a>-Projekt." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Veröffentlicht unter der <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a> (<a href=\"%(source_link)s\">Quellcode</a>)." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Entdecken" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hallo du, willkommen auf dieser MediaGoblin-Seite!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Diese Webseite setzt <a href=\"http://mediagoblin.org\">MediaGoblin</a> ein, eine großartige Software für Medienhosting." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Melde Dich mit Deinem MediaGoblin-Konto an, um eigene Medien hinzuzufügen, andere zu kommentieren und vieles mehr." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Hast du noch keinen? Das geht ganz einfach!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Registriere dich auf dieser Seite</a> oder <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Installiere MediaGoblin auf deinem eigenen Server</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin Logo" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Bearbeite Anhänge von %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Anhänge" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Anhang hinzufügen" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Abbrechen" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Änderungen speichern" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Soll das Konto »%(user_name)s« und alle zu ihm gehörigen Medien / Kommentare wirklich gelöscht werden?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Ja, ich möchte mein Konto wirklich löschen" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Dauerhaft löschen" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "%(media_title)s bearbeiten" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "%(username)ss Kontoeinstellungen ändern" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Mein Konto löschen" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Bearbeite %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "%(username)ss Profil bearbeiten" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Medien mit Schlagwort: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Download" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Entschuldige, dieses Audiostück wird nicht funktionieren, weil dein Webbrowser kein HTML5-Audio unterstützt." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Hol dir auf <a href=\"http://getfirefox.com\">http://getfirefox.com</a> einen modernen Webbrowser, der dieses Audiostück abspielen kann!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Originaldatei" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM-Datei (Vorbis-Codec)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Bild für %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF-Datei" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspektive" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Vorderseite" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Modell herunterladen" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Dateiformat" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Objekthöhe" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Entschuldige, dieses Video wird nicht funktionieren, weil dein Webbrowser kein HTML5-Video unterstützt." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Hol dir auf <a href=\"http://getfirefox.com\">http://getfirefox.com</a> einen modernen Webbrowser, der dieses Video abspielen kann!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM-Datei (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Eine Sammlung hinzufügen" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Deine Medien" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (ein Album von %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s von <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Bearbeiten" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Löschen" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Möchtest du %(title)s wirklich löschen?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Wirklich »%(media_title)s« aus »%(collection_title)s« entfernen?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Entfernen" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Alben von %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Alben von <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Hallo %(username)s,\n%(comment_author)s hat dein Medium (%(comment_url)s) auf %(instance_name)s kommentiert.\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "%(username)ss Medien" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>s Medien mit dem Schlagwort <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>s Medien" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Medien von <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Einen Kommentar schreiben" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Kommentar absenden" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Hinzugefügt" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Originaldatum" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "»%(media_title)s« zu einem Album hinzufügen" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Eine neue Sammlung hinzufügen" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Du kannst hier den Status der Medien verfolgen, die sich gerade in Bearbeitung befinden." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Deine zehn letzten erfolgreichen Uploads" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)ss Profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Dieser Benutzer konnte leider nicht gefunden werden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "E-Mail Aktivierung benötigt" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Fast fertig! Dein Konto muss noch freigeschaltet werden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Gleich solltest du eine E-Mail erhalten, die beschreibt was noch zu tun bleibt." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Falls sie nicht ankommt:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Aktivierungsmail erneut senden" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Jemand hat bereits ein Konto mit diesem Benutzernamen registriert, aber es muss noch aktiviert werden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Wenn dir dieses Konto gehört und die Aktivierungsmail verloren gegangen ist, kannst du dich <a href=\"%(login_url)s\">anmelden</a> und sie erneut senden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Hier kannst Du Dich selbst beschreiben." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Profil bearbeiten" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Dieser Benutzer hat (noch) keine Daten in seinem Profil." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Sammlungen durchstöbern" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Alle Medien von %(username)s anschauen" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Hier erscheinen deine Medien, sobald du etwas hochgeladen hast." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Scheinbar gibt es hier noch nichts …" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(entfernen)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "In den Sammlungen" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Zu einer Sammlung hinzufügen" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "Feed-Symbol" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom-Feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Alle Rechte vorbehalten" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Neuere" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Ältere →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Zu Seite:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "neuer" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "älter" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Schlagwörter" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Die Bilddatei konnte nicht gelesen werden." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Hoppla!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Ein Fehler trat auf" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Funktion nicht erlaubt" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "So nicht!</p><p>Du wolltest eine Funktion verwenden zu der Du nicht die nötigen Rechte Rechte besitzt. Wolltest Du etwa schon wieder alle Nutzerkonten löschen?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Tut uns Leid, aber unter der angegebenen Adresse gibt es keine Seite!</p><p>Wenn du sicher bist, dass die Adresse stimmt, wurde die Seite eventuell verschoben oder gelöscht." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "Jahr" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "Monat" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "Woche" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "Tag" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "Stunde" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "Minute" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Kommentar" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Die Texte lassen sich durch <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> formatieren." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Ja, wirklich löschen" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Ich bin sicher, dass ich dieses Objekt aus der Sammlung entfernen möchte" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Album" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Auswählen --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Notiz anfügen" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "hat dein Medium kommentiert" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Hoppla, der Kommentartext fehlte." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Dein Kommentar wurde angenommen!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Bitte prüfe deinen Einträge und versuche erneut." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Du musst eine Sammlung auswählen oder hinzufügen" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "»%s« ist bereits in der Sammlung »%s«" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "»%s« zur Sammlung »%s« hinzugefügt" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Du hast das Medium gelöscht." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Das Medium wurde nicht gelöscht, da nicht angekreuzt hast, dass du es wirklich löschen möchtest." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Du versuchst Medien eines anderen Nutzers zu löschen. Sei bitte vorsichtig." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Du hast das Objekt aus der Sammlung gelöscht." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Das Objekt wurde nicht aus der Sammlung entfernt, weil du nicht bestätigt hast, dass du dir sicher bist." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Du bist dabei ein Objekt aus der Sammlung eines anderen Nutzers zu entfernen. Sei vorsichtig." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Du hast die Sammlung »%s« gelöscht" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Die Sammlung wurde nicht gelöscht, weil du nicht bestätigt hast, dass du dir sicher bist." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Du bist dabei eine Sammlung eines anderen Nutzers zu entfernen. Sei vorsichtig." diff --git a/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po new file mode 100644 index 00000000..1b22b786 --- /dev/null +++ b/mediagoblin/i18n/en/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1257 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2013. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2013-06-16 20:06-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" + +#: mediagoblin/auth/forms.py:25 +msgid "Username" +msgstr "" + +#: mediagoblin/auth/forms.py:29 mediagoblin/auth/forms.py:44 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "" + +#: mediagoblin/auth/forms.py:33 +msgid "Email address" +msgstr "" + +#: mediagoblin/auth/forms.py:40 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:51 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:42 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:43 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:44 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/tools.py:109 +msgid "Sorry, a user with that name already exists." +msgstr "" + +#: mediagoblin/auth/tools.py:113 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:43 +msgid "Sorry, registration is disabled on this instance." +msgstr "" + +#: mediagoblin/auth/views.py:133 +msgid "" +"Your email address has been verified. You may now login, edit your " +"profile, and submit images!" +msgstr "" + +#: mediagoblin/auth/views.py:139 +msgid "The verification key or user id is incorrect" +msgstr "" + +#: mediagoblin/auth/views.py:157 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:165 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:178 +msgid "Resent your verification email." +msgstr "" + +#: mediagoblin/auth/views.py:209 +msgid "" +"If that email address (case sensitive!) is registered an email has been " +"sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:220 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:223 +msgid "An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:230 +msgid "" +"Could not send password recovery email as your username is inactive or " +"your account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:287 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie " +"blocker or somesuch.<br/>Make sure to permit the settings of cookies for " +"this domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can " +"not be\n" +" intercepted by the user agent (e.g. server-side " +"client).<br />\n" +" <strong>Public</strong> - The client can't make " +"confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-" +"side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> " +"project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a" +" href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, " +"an extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your" +" MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an " +"account at this site</a>\n" +" or\n" +" <a class=\"button_action\" " +"href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on " +"your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:171 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:187 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:193 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/image.html:36 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/image.html:39 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at " +"%(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "An email should arrive in a few moments with instructions on how to do so." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to" +" be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can " +"<a href=\"%(login_url)s\">log in</a> and resend it." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're " +"sure the address is correct, maybe the page you're looking for has been " +"moved or deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> " +"for formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed " +"with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were " +"sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "You are about to delete another user's collection. Proceed with caution." +msgstr "" + diff --git a/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..645af16b --- /dev/null +++ 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 new file mode 100644 index 00000000..873869f0 --- /dev/null +++ b/mediagoblin/i18n/eo/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1255 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# aleksejrs <deletesoftware@yandex.ru>, 2013 +# aleksejrs <deletesoftware@yandex.ru>, 2011-2012 +# Fernando Inocencio <faigos@gmail.com>, 2011 +# tiguliano <john_w1954@fastmail.fm>, 2011 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-01 21:16+0000\n" +"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n" +"Language-Team: Esperanto (http://www.transifex.com/projects/p/mediagoblin/language/eo/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: eo\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Uzantnomo" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Pasvorto" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Retpoŝtadreso" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Uzantonomo aŭ retpoŝtadreso" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Salutnomo aŭ retpoŝtadreso" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Nevalida ensalutnomo aŭ retpoŝtadreso." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Ĉi tiu kampo ne akceptas retpoŝtadresojn." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Ĉi tiu kampo postulas retpoŝtadreson." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Bedaŭrinde, registrado estas malaktivigita en tiu ĉi instalaĵo." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Bedaŭrinde, uzanto kun tiu nomo jam ekzistas." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Ni bedaŭras, sed konto kun tiu retpoŝtadreso jam ekzistas." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Via retpoŝtadreso estas konfirmita. Vi povas nun ensaluti, redakti vian profilon, kaj alŝuti bildojn!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "La kontrol-kodo aŭ la uzantonomo ne estas korekta" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Vi devas esti ensalutita, por ke ni sciu, al kiu sendi la retleteron!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Vi jam konfirmis vian retpoŝtadreson!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Resendi vian kontrol-mesaĝon." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Se tiu retpoŝtadreso (majuskloj gravas!) estas registrita, tien senditas retletero kun instrukcio pri kiel ŝanĝi vian pasvorton." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Trovitas neniu kun tiu ensalutnomo." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Senditas retletero kun instrukcio pri kiel ŝanĝi vian pasvorton." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Ni ne povas sendi pasvortsavan retleteron, ĉar aŭ via konto estas neaktiva, aŭ ĝia retpoŝtadreso ne estis konfirmita." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Nun vi povas ensaluti per via nova pasvorto." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titolo" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Priskribo de ĉi tiu verko" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Vi povas uzi por markado la lingvon\n «<a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>»." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etikedoj" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Dividu la etikedojn per komoj." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "La distingiga adresparto" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "La distingiga adresparto ne povas esti malplena" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "La dosiertitol-bazita parto de la dosieradreso. Ordinare ne necesas ĝin ŝanĝi." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Permesilo" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Bio" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Retejo" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Ĉi tiu adreso enhavas erarojn" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Permesila prefero" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Tiu ĉi permesilo estos antaŭelektita en la alŝutformularoj." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Retpoŝtu min kiam aliaj komentas pri miaj alŝutaĵoj." + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "La titolo ne povas malpleni." + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Priskribo de la kolekto" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "La distingiga adresparto de ĉi tiu kolekto. Ordinare ne necesas ĝin ŝanĝi." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "La malnova pasvorto" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Enigu vian malnovan pasvorton por pruvi, ke ĉi tiu konto estas via." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "La nova pasvorto" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Ĉi tiu uzanto jam havas dosieron kun tiu distingiga adresparto." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Vi priredaktas dosieron de alia uzanto. Agu singardeme." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Vi aldonis la kundosieron %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Vi povas redakti nur vian propran profilon." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Vi redaktas profilon de alia uzanto. Agu singardeme." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Profilŝanĝoj estis konservitaj" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Kontagordoj estis konservitaj" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Vi bezonas konfirmi la forigon de via konto." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Vi jam havas kolekton kun la nomo «%s»!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Ĉi tiu uzanto jam havas kolekton kun tiu distingiga adresparto." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Vi redaktas kolekton de alia uzanto. Agu singardeme." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Malĝusta pasvorto" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Via pasvorto estas sukcese ŝanĝita" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Alligo de etoso ne eblas… ne estas elektita ekzistanta etoso\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Mankas dosierujo kun aspektiloj por la etoso\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Tamen trovitas — kaj forigitas — malnova simbola ligilo al dosierujo.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Mi pardonpetas, mi ne subtenas tiun dosiertipon :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Malsukcesis transkodado de filmo" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Loko" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Vidi sur <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nomo" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "La nomo de la OAuth-kliento" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Priskribo" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Tipo" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Aldoni" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "La provizita dosiero ne konformas al la informtipo." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Dosiero" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Vi devas provizi dosieron." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Hura! Alŝutitas!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Kolekto «%s» aldonitas!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Konfirmu viecon de la retpoŝtadreso!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "elsaluti" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Ensaluti" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Konto de <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Ŝanĝi kontagordojn" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Kontrolejo pri dosierpreparado." + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Elsaluti" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Aldoni dosieron" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Krei novan kolekton" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Bildo de zorgigita koboldo" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Laste aldonitaj dosieroj" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Ĉi tie vi povas observi la staton de prilaborado de alŝutaĵoj en ĉi tiu servilo." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Dosieroj preparataj" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Neniu dosieroj preparatas" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Preparado de ĉi tiuj alŝutaĵoj malsukcesis:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Ne ekzistas malsukcesaj eroj!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "La dek lastaj sukcesaj alŝutoj" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Ankoraŭ ne ekzistas eroj prilaboritaj!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Enigu vian novan pasvorton" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Difini pasvorton" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Ekhavo de nova pasvorto" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Sendi instrukcion" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Saluton, %(username)s,\n\npor ŝanĝi vian pasvorton ĉe GNUa MediaGoblin, sekvu la jenan retadreson per via TTT-legilo:\n\n%(verification_url)s\n\nSe vi pensas, ke ĉi tiu retletero estas sendita erare, simple ignoru ĝin kaj plu restu feliĉa koboldo!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Ensaluto malsukcesis!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Ĉu ankoraŭ sen konto?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Kreu ĝin ĉi tie!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Ĉu vi forgesis vian pasvorton?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Kreu konton!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Krei" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Sal %(username)s,\n\npor aktivigi vian GNU MediaGoblin konton, malfermu la sekvantan URLon en via retumilo:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Funkcias per <a href=\"http://mediagoblin.org/\" title='Versio %(version)s'>MediaGoblin</a>, unu el la <a href=\"http://gnu.org/\">projektoj de GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Disponigita laŭ la permesilo <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. Haveblas<a href=\"%(source_link)s\">fontotekstaro</a>." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Ĉirkaŭrigardi" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Saluton, kaj bonvenon al ĉi tiu MediaGoblina retpaĝaro!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Ĉi tiu retpaĝaro funkcias per <a href=\"http://mediagoblin.org\">MediaGoblin</a>, eksterordinare bonega programaro por gastigado de aŭd‐vid‐dosieroj." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Por aldoni viajn proprajn dosierojn, afiŝi komentariojn ktp, vi povas ensaluti je via MediaGoblina konto." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Ĉu vi ankoraŭ ne havas tian? Ne malĝoju!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Emblemo de MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Aldoni kundosierojn por %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Kundosieroj" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Aldoni kundosieron" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Nuligi" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Konservi ŝanĝojn" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Ŝanĝado de pasvorto de %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Konservi" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Ĉu efektive forigi la uzantokonton «%(user_name)s» kaj ĉiujn ĝiajn dosierojn/komentojn?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Jes, efektive forigi mian konton" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Forigi senrevene" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Priredaktado de %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Ŝanĝado de kontagordoj de %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Ŝanĝi la pasvorton" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Forigi mian konton." + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Redaktado de %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Redaktado de l’profilo de %(username)s'" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Dosieroj kun etikedo: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Elŝuti" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Originalo" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Bedaŭrinde, ĉi tiu sonregistraĵo ne ludiĝos, \n\tĉar via TTT-legilo ne povas ludi\n\tsonaĵojn, afiŝitajn laŭ HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Vi povas akiri modernan TTT-legilon, kapablan \n\tsonigi la registraĵon ĉe <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "originalan dosieron" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebMan dosieron (kun Vorbisa kodaĵo)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Bildo de «%(media_title)s»" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF-dosiero" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Deantaŭe" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Desupre" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Deflanke" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Elŝuti la modelon" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Informaranĝo" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Alto de la objekto" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Bedaŭrinde, ĉi tiu filmo ne montriĝos\n ĉar via TTT-legilo ne subtenas sufiĉe\n filmojn laŭ HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Vi povas elŝuti modernan TTT-legilon, kapablan \n montri la filmon, de <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "la WebM-dosieron (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Aldonado de kolekto" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Aldono de via dosiero" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (kolekto de %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Ŝanĝi" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Forigi" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Ĉu vere forigi %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Ĉu vere forigi %(media_title)s el %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Forigi" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Kolektoj de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Kolektoj de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Saluton, %(username)s.\n%(comment_author)s komentis ĉe via alŝutaĵo (%(comment_url)s) ĉe %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Dosieroj de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Dosieroj de <a href=\"%(user_url)s\">%(username)s</a> kun la etikedo <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Dosieroj de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Просмотр файлов пользователя <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Aldoni komenton" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Aldoni ĉi tiun komenton" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "antaŭ %(formatted_time)s" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Aldonita" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Kreita" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Aldoni «%(media_title)s» al kolekto" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Aldoni novan kolekton" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Ĉi tie vi povas informiĝi pri la stato de preparado de dosieroj por via galerio." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Viaj 10 lastaj sukcesaj alŝutoj." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profilo de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Uzanto ne trovita." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Necesas konfirmo de retpoŝtadreso" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Preskaŭ finite! Restas nur validigi vian konton." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Post kelkaj momentoj devas veni retletero kun instrukcio pri kiel tion fari." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Se tio ne okazas:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Resendi kontrolmesaĝon" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Iu registris konton kun tiu ĉi uzantonomo, sed ĝi devas ankoraŭ esti aktivigita." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Se vi estas tiu sed vi perdis vian kontrolmesaĝon, vi povas <a href=\"%(login_url)s\">ensaluti</a> kaj resendi ĝin." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Jen estas spaceto por rakonti pri vi al aliaj." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Redakti profilon" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Ĉi tiu uzanto ne jam aldonis informojn pri si." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Vidi kolektojn" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Rigardi ĉiujn dosierojn de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Ĝuste ĉi tie aperos viaj dosieroj, sed vi ŝajne ankoraŭ nenion alŝutis." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Ĉi tie ŝajne estas ankoraŭ neniuj dosieroj…" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(forigi)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "En kolektoj:" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Aldoni al kolekto" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "flusimbolo" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom-a informfluo" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Ĉiuj rajtoj estas rezervitaj" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Pli novaj" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Malpli novaj →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Iri al paĝo:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "pli nova" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "malpli nova" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Markita per" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Malsukcesis lego de la bildodosiero" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Oj!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Okazis eraro" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "jaro(j)" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "monato(j)" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "semajno(j)" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "tago(j)" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "horo(j)" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "minuto(j)" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Komenti" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Vi povas uzi por markado la lingvon «<a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>»." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Jes, mi volas forigi ĉi tion." + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Jes, mi volas forigi ĉi tiun dosieron el la kolekto" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Kolekto" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Elektu --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Rimarko" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "komentis je via afiŝo" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Ve, komentado estas malebligita." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Oj, via komento estis malplena." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Via komento estis afiŝita!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Bonvolu kontroli vian enigitaĵon kaj reprovi." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Necesas elekti aŭ aldoni kolekton" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "«%s» jam estas en la kolekto «%s»" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "«%s» estis aldonita al la kolekto «%s»" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Vi forigis la dosieron." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "La dosiero ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Vi estas forigonta dosieron de alia uzanto. Estu singardema." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Vi forigis la dosieron el la kolekto." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "La dosiero ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Vi estas forigonta dosieron el kolekto de alia uzanto. Agu singardeme." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Vi forigis la kolekton «%s»" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "La kolekto ne estis forigita, ĉar vi ne konfirmis vian certecon per la markilo." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Vi estas forigonta kolekton de alia uzanto. Agu singardeme." diff --git a/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..c5e50f53 --- /dev/null +++ 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 new file mode 100644 index 00000000..8c2f046f --- /dev/null +++ b/mediagoblin/i18n/es/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1263 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# aleksejrs <deletesoftware@yandex.ru>, 2011, 2012 +# ekenbrand <ekenbrand@hotmail.com>, 2011 +# nvjacobo <jacobo@gnu.org>, 2011-2012 +# Javier Di Mauro <javierdimauro@gmail.com>, 2011 +# case <juangsub@gmail.com>, 2011 +# juanman <juanma@kde.org.ar>, 2011, 2012 +# larjona <larjona99@gmail.com>, 2012 +# larjona <larjona99@gmail.com>, 2013 +# Mario Rodriguez <msrodriguez00@gmail.com>, 2011 +# Manuel Urbano Santos <mu@member.fsf.org>, 2011 +# shackra <shackra@riseup.net>, 2012 +# Elesa <stardustprincess17@hotmail.com>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-02 21:23+0000\n" +"Last-Translator: larjona <larjona99@gmail.com>\n" +"Language-Team: Spanish (http://www.transifex.com/projects/p/mediagoblin/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Nombre de usuario" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Contraseña" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Dirección de correo electrónico" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Nombre de usuario o correo electrónico" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Nombre de usuario o email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Nombre de usuario o correo electrónico inválido." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Este campo no acepta direcciones de correo." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Este campo requiere una dirección de correo." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Lo sentimos, el registro está deshabilitado en este momento." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Lo sentimos, ya existe un usuario con ese nombre." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Lo sentimos, ya existe un usuario con esa dirección de email." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Tu dirección de correo electrónico ha sido verificada. ¡Ahora puedes iniciar sesión, editar tu perfil, y enviar imágenes!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "La clave de verificación o la identificación de usuario son incorrectas" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "¡Debes iniciar sesión para que podamos saber a quién le enviamos el correo electrónico!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "¡Ya has verificado tu dirección de correo!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Se reenvió tu correo electrónico de verificación." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Si esa dirección de correo (¡sensible a mayúsculas y minúsculas!) está registrada, se ha enviado un correo con instrucciones para cambiar la contraseña." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "No se ha podido encontrar a nadie con ese nombre de usuario." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Un correo electrónico ha sido enviado con instrucciones sobre cómo cambiar tu contraseña." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "No se pudo enviar un correo electrónico de recuperación de contraseñas porque tu nombre de usuario está inactivo o la dirección de su cuenta de correo electrónico no ha sido verificada." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Ahora tu puedes iniciar sesión usando tu nueva contraseña." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Título" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Descripción de esta obra" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Puedes usar\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> para el formato." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etiquetas" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Separa las etiquetas por comas." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Ficha" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "La ficha no puede estar vacía" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "El título de esta parte de la dirección de los contenidos. Por lo general no es necesario cambiar esto." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licencia" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Bio" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Sitio web" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "La dirección contiene errores" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Preferencias de licencia" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Ésta será tu licencia predeterminada en los formularios de subida." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Envíame un correo cuando otros escriban comentarios sobre mi contenido" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "El título no puede estar vacío" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Descripción de esta colección" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "El título de la dirección de esta colección. Generalmente no necesitas cambiar esto." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Vieja contraseña" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Escriba la anterior contraseña para demostrar que esta cuenta te pertenece." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nueva contraseña" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Una entrada con esa ficha ya existe para este usuario." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Estás editando el contenido de otro usuario. Procede con precaución." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "¡Has añadido el adjunto %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Sólo puedes editar tu propio perfil." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Estás editando un perfil de usuario. Procede con precaución." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Los cambios de perfil fueron salvados" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "las configuraciones de cuenta fueron salvadas" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Necesitas confirmar el borrado de tu cuenta." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "¡Ya tienes una colección llamada \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Una colección con esa ficha ya existe para este usuario/a." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Estás editando la colección de otro usuario/a. Ten cuidado." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Contraseña incorrecta" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Se ha cambiado la contraseña correctamente" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "No se puede enlazar al tema... no hay un tema seleccionado\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "No hay directorio activo para este tema\n\n\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Sin embargo, se encontró un enlace simbólico de un directorio antiguo; ha sido borrado.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "No se pudo enlazar \"%s\": %s existe y no es un enlace simbólico\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "Omitiendo \"%s\"; ya está establecido.\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "Se encontró un enlace antiguo para \"%s\"; se eliminará.\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "No se encuentra la cookie CSRF. Esto suele ser debido a un bloqueador de cookies o similar.<br/> Por favor asegúrate de permitir las cookies para este dominio." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Lo sentidos, No soportamos ese tipo de archivo :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "ha fallado la ejecución de unoconv, comprueba el fichero de registro (log)" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Ha fallado la conversión de vídeo" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Locación" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Ver en <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Permitir" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Denegar" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nombre" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "El nombre del cliente OAuth" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Descripción" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Esto será visible para los usuarios que permitan tu aplicación\n\npara que puedan autenticarse." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Tipo" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Confidencial</strong> - El cliente puede hacer peticiones a la instancia GNU MediaGoblin que no pueden ser interceptadas por el agente de usuario (ejemplo: un cliente del lado del servidor).<br /><strong>Público</strong> - El cliente no puede hacer peticiones confidenciales a la instancia GNU MediaGoblin (ejemplo: un cliente JavaScript del lado del servidor)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Redireccionar URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "La URI para redireccionar las aplicaciones, este campo es <strong>requerido</strong> para los clientes públicos." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Este campo es requerido para los clientes públicos" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "¡El cliente {0} ha sido registrado!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "Conexiones de cliente OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Tus clientes OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Añadir " + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Archivo inválido para el formato seleccionado." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Archivo" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Debes proporcionar un archivo." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "¡Yuju! ¡Enviado!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "¡Colección \"%s\" añadida!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "¡Verifica tu email!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "cerrar sesión" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Iniciar sesión" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Cuenta de <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Cambiar la configuración de la cuenta" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Panel de procesamiento de contenido" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Cerrar sesión" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Añadir contenido" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Crear nueva colección" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Imagen de un goblin estresándose" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "El contenido más reciente" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Aquí puedes llevar un seguimiento del estado del contenido que se está procesando en esta instancia." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Procesando contenido" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "No hay contenidos en procesamiento" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Estos archivos no pudieron ser procesados:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "¡No han fallado entradas!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Últimos 10 envíos con éxito" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "¡Aún no hay entradas procesadas!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Coloca tu nueva contraseña " + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Coloca la contraseña" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Recuperar contraseña" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Enviar instrucciones" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Hola %(username)s,\n\nPara cambiar tu contraseña de GNU MediaGoblin, abre la siguiente URL en un navegador:\n\n%(verification_url)s \n\nSi piensas que esto es un error, simplemente ignora este mensaje y sigue siendo un duende feliz." + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "¡Hubo un fallo al iniciar sesión!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "¿No tienes una cuenta?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "¡Crea una aquí!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "¿Olvidaste tu contraseña?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "¡Crea una cuenta!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Crear" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Hola %(username)s,\n\npara activar tu cuenta de GNU MediaGoblin, abre la siguiente URL en tu navegador:\n\n%(verification_url)s " + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Funciona con <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, un proyecto <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Publicado bajo la <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\"> Código fuente</a> disponible." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Explorar" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hola, ¡bienvenido a este sitio de MediaGoblin!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Este sitio está montado con <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un extraordinario programa libre para alojar, gestionar y compartir contenido multimedia." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Para añadir tus propios contenidos, dejar comentarios y más, puedes iniciar sesión con tu cuenta de MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "¿Aún no tienes una? ¡Es fácil!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Crear una cuenta en este sitio</a>\n o\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instalar MediaGoblin en tu propio servidor</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Logo de MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Editando archivos adjuntos a %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Adjuntos" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Agregar adjunto" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Cancelar" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Guardar cambios" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Cambiando la contraseña de %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Guardar" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "¿Realmente quieres borrar el usuario '%(user_name)s' y todos sus contenidos/comentarios?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Sí, borrar mi cuenta" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Eliminar permanentemente" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Editando %(media_title)s " + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Cambio de %(username)s la configuración de la cuenta " + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Cambiar tu contraseña." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Borrar mi cuenta" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Editando %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Editando el perfil de %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Contenido etiquetado con: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Descargar" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Lo sentimos, este audio audio no funcionará porque \n\ttu navegador web no soporta audio de \n\tHTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Tú puedes obtener un navegador más moderno que \n\tpueda reproducir el audio <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Archivo original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "Archivo WebM (códec Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Imágenes para %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "Fichero PDF" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Alternar Rotar" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspectiva" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Frente" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Arriba" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Lateral" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Descargar modelo" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Formato de Archivo" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Altura del Objeto" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Lo siento, este vídeo no funcionará\n porque tu navegador no soporta \n vídeo HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "¡Puedes conseguir un navegador moderno \n que pueda reproducir este vídeo en <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "Archivo WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Añadir una colección" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Añade tu contenido " + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (%(username)s's collection)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s por <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Editar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Borrar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "¿Realmente deseas eliminar %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "¿Realmente quieres quitar %(media_title)s de %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Quitar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Colecciones de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Colecciones de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Hola %(username)s,\n%(comment_author)s comentó tu publicación (%(comment_url)s) en %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Contenido de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Contenido de <a href=\"%(user_url)s\">%(username)s</a> con etiqueta <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Contenido de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Explorando contenido de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Añadir un comentario" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Añade un comentario " + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "hace %(formatted_time)s" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Agregado" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Creado" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Añadir “%(media_title)s” a una colección" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Añadir una nueva colección" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Aquí puedes hacer un seguimiento del contenido que está siendo procesado." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Tus últimos 10 envíos exitosos" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Perfil de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Lo sentimos, no se encontró ese usuario." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Es necesario que verifiques tu cuenta mediante el correo de notiicación" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "¡Casi hemos terminado! Solo falta activar la cuenta." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "En unos momentos te debería llegar un correo electrónico con las instrucciones para hacerlo." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "En caso de que no:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Reenviar correo electrónico de verificación" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Alguien ya registró una cuenta con ese nombre de usuario, pero todavía no ha sido activada." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Si tú eres esa persona, pero has perdido tu correo electrónico de verificación, puedes <a href=\"%(login_url)s\">iniciar sesión</a> y reenviarlo." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Aquí hay un lugar para que le cuentes a los demás sobre ti." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Editar perfil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Este usuario (todavía) no ha completado su perfil." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Explorar colecciones" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Ver todo el contenido de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Aquí es donde estará ubicado tu contenido, pero parece que aún no has añadido nada." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Parece que aún no hay ningún contenido aquí..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(borrar)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "En la colección" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Añadir a una colección" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "Icono feed" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Todos los derechos reservados" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Más nuevo" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Más viejo →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Ir a la página:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "Más nuevo" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "Más viejo" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Marcado con" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "No se pudo leer el archivo de imagen." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "¡Ups!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Ha ocurrido un error" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Operación no permitida" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "¡Lo siento Dave, no puedo permitir que hagas eso!</p><p>Has intentado realizar una operación no permitida. ¿Has vuelto a intentar borrar todas las cuentas de usuario?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Parece que no hay ninguna página en esta dirección. ¡Lo siento!</p><p>Si estás seguro de que la dirección es correcta, quizá han borrado o movido la página que estás buscando." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "año" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "mes" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "semana" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "día" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "hora" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "minuto" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Comentario" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Puedes usar <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> para el formato." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Estoy seguro de que quiero borrar esto" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Estoy seguro/a de que quiero quitar este ítem de la colección" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Colección" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Selecciona --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Incluir una nota" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "comentó tu publicación" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Lo siento, los comentarios están desactivados." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Ups, tu comentario estaba vacío." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "¡Tu comentario ha sido publicado!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Por favor, revisa tus entradas e inténtalo de nuevo." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Tienes que seleccionar o añadir una colección" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "%s\" ya está en la colección \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" añadido a la colección \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Eliminaste el contenido" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "El contenido no se eliminó porque no marcaste que estabas seguro." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Estás a punto de eliminar un contenido de otro usuario. Procede con precaución." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Borraste el ítem de la colección." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "El ítem no fue removido porque no confirmaste que estuvieras seguro/a." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Estás a punto de borrar un ítem de la colección de otro usuario. Procede con cuidado." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Borraste la colección \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "La colección no fue borrada porque no confirmaste que estuvieras seguro/a." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Estás a punto de borrar la colección de otro usuario. Procede con cuidado." diff --git a/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..3422ad97 --- /dev/null +++ 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 new file mode 100644 index 00000000..08e73e1a --- /dev/null +++ b/mediagoblin/i18n/fa/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1252 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Numb <amir007ag@gmail.com>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Persian (http://www.transifex.com/projects/p/mediagoblin/language/fa/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: fa\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "نام کاربری" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "گذرواٰژه" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "آدرس ایمیل" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "متاسفانه،ثبتنام به طور موقت غیر فعال است." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "متاسفانه کاربری با این نام کاربری وجود دارد." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "ایمیل شما تایید شد.شما می توانید حالا وارد شوید،نمایه خود را ویرایش کنید و تصاویر خود را ثبت کنید!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "این کد تاییدیه یا شناسه کاربری صحیح نیست." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "ایمیل تاییدیه باز ارسال شد." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "عنوان" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "برچسب" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "زندگینامه" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "وبسایت" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "شما در حال ویرایش رسانه کاربر دیگری هستید.با احتیاط عمل کنید" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "شما در حال ویرایش نمایه کاربر دیگری هستید.با احتیاط عمل کنید." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "فایلی نا معتبر برای نوع رسانه داده شده." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "فایل" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "شما باید فایلی ارايه بدهید." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "هورا!ثبت شد!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "ورود" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "پنل رسیدگی به رسانه ها" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "ورود با خطا انجام شد!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "آیا حساب کاربری ندارید؟" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "در اینجا یکی بسازید!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "ساخت یک حساب کاربری!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "ساختن" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "سلام %(username)s,\n\nبرای فعال سازی شناسه کاربری گنو مدیاگوبلین خود ،پیوند زیر را در مرورگر خود باز کنید.\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "لوگو مدیاگوبلین" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "انصراف" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "ذخیره تغییرات" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "ویرایش %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "در حال ویرایش نمایه %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>'s رسانه های" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "نمایه %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "متاسفانه کاربر مورد نظر یافت نشد." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "به زودی ایمیلی حاوی شرح کار ها برای شما ارسال خواهد شد." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "در این حالت وجود ندارد." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "باز ارسال ایمیل تاییدیه" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "اگر شما آن کاربر هستید و ایمیل تایید خود را گم کرده اید،, می توانید <a href=\"%(login_url)s\">log in</a> و دوباره آنرا بفرستید.." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "ویرایش نمایه" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "نمایش تمامی رسانه های %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "اوه" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..7bc860a0 --- /dev/null +++ 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 new file mode 100644 index 00000000..6103c439 --- /dev/null +++ b/mediagoblin/i18n/fr/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1261 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# ianux <a5565930@nepwk.com>, 2011 +# alcazar <alexispay@gmail.com>, 2012 +# chesuidayeur <chesuidayeur@yahoo.fr>, 2011 +# Bibit <crash_bibit@hotmail.com>, 2013 +# joehillen <joehillen@gmail.com>, 2011 +# hellpe <hell_pe@no-log.org>, 2013 +# MarkTraceur <marktraceur@gmail.com>, 2011 +# maxineb <maxineb@members.fsf.org>, 2011 +# joar <transifex@wandborg.se>, 2011 +# Valentin Villenave <valentin@villenave.net>, 2011 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: French (http://www.transifex.com/projects/p/mediagoblin/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Nom d'utilisateur" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Mot de passe" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Adresse e-mail" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Nom d'utilisateur ou email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Nom d'utilisateur ou adresse de courriel invalide." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "L'inscription n'est pas activée sur ce serveur, désolé." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Un utilisateur existe déjà avec ce nom, désolé." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Désolé, il existe déjà un utilisateur ayant cette adresse e-mail." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Votre adresse e-mail a bien été vérifiée. Vous pouvez maintenant vous identifier, modifier votre profil, et soumettre des images !" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "La clé de vérification ou le nom d'utilisateur est incorrect." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Vous devez être authentifié afin que nous sachions à qui envoyer l'e-mail !" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Votre adresse e-mail a déjà été vérifiée !" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "E-mail de vérification renvoyé." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Nom d'utilisateur introuvable." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Un email contenant les instructions pour changer votre mot de passe viens de vous être envoyé" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Impossible d'envoyer un email de récupération de mot de passe : votre compte est inactif ou bien l'email de votre compte n'a pas été vérifiée." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Vous pouvez maintenant vous connecter avec votre nouveau mot de passe." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titre" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Descriptif pour ce travail" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Vous pouvez utiliser\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> pour le formattage." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Tags" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Séparez les champs avec des virgules." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Légende" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "La légende ne peut pas être laissée vide." + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Le titre présent dans l'URL du média. Vous n'avez généralement pas besoin de le modifier" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licence" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Bio" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Site web" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Cette adresse contiens des erreurs" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Me prévenir par email lorsque d'autres commentent mes médias" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Le titre ne peut être vide" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Description de cette collection" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Le titre affiché dans l'URL de la collection. Vous n'avez généralement pas besoin d'y toucher." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Ancien mot de passe." + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Entrez votre ancien mot de passe pour prouver que vous êtes bien le propriétaire de ce compte." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nouveau mot de passe" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Une entrée existe déjà pour cet utilisateur avec la même légende." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Vous vous apprêtez à modifier le média d'un autre utilisateur. Veuillez prendre garde." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Vous avez ajouté la pièce jointe %s !" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Vous ne pouvez modifier que votre propre profil." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Vous vous apprêtez à modifier le profil d'un utilisateur. Veuillez prendre garde." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Les changements apportés au profile ont étés sauvegardés" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Les changements des préférences du compte ont étés sauvegardés" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Vous devez confirmer la suppression de votre compte." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Vous avez déjà une collection appelée \"%s\" !" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Vous éditez la collection d'un autre utilisateurs. Faites attention." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Mauvais mot de passe" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Impossible de lier le thème... Aucun thème associé\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Aucun répertoire \"asset\" pour ce thème\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Désolé, mais je ne prends pas en charge cette extension de fichier :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "L'encodage de la vidéo à échoué" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Position" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Regarder sur <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Autoriser" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Refuser" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nom" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Description" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Type" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "URL de redirection" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "L'URI de redirection pour l'application, ce champ est <strong>requis</strong> pour les clients publics" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Ce champ est requis pour les clients publics" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Le client {0} as été enregistré !" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Ajouter" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Le fichier envoyé ne correspond pas au type de média." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Fichier" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Il vous faut fournir un fichier." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Youhou, c'est envoyé !" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Collection \"%s\" ajoutée !" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Vérifiez votre adresse e-mail !" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "Déconnexion" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "S'identifier" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Changer les paramètres du compte" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Panneau pour le traitement des médias" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Ajouter des médias" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Créer une nouvelle collection" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Tout derniers media" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Ici, vous pouvez suivre l'état des médias en cours de traitement par cette instance." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Médias en transformation" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Aucun média en transformation" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Le traitement de ces ajouts a échoué :" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Aucune entrée ayant échoué !" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "10 derniers envois terminés" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Aucune entrée traitée jusqu'à présent !" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Enregistrez votre nouveau mot de passe" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Enregistrez votre mot de passe" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Récupérer le mot de passe" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Envoyer les instructions" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Bonjour %(username)s,\n\nPour changer votre mot de passe GNU MediaGoblin, ouvrez l'URL suivante dans \nvotre navigateur internet :\n\n%(verification_url)s\n\nSi vous pensez qu'il s'agit d'une erreur, ignorez simplement cet email et restez\nun goblin heureux !" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "La connexion a échoué!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Pas encore de compte ?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Créez-en un ici !" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Vous avez oublié votre mot de passe ?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Créer un compte !" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Créer" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Bonjour %(username)s,\n\npour activer votre compte sur GNU MediaGoblin, veuillez vous rendre à l'adresse suivante avec votre navigateur web:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Disponible sous la licence <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Code source</a> disponible." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Explorer" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Bonjour, et bienvenue sur ce site MediaGoblin !" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Ce site fait tourner <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un logiciel d'hébergement de média extraordinairement génial." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Pour ajouter vos propres médias, commenter, et bien plus encore, vous pouvez vous connecter avec votre compte MediaGoblin" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Vous n'en avez pas ? C'est facile !" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Logo MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Éditer les pièces jointes de %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Pièces jointes" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Ajouter une pièce jointe" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Annuler" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Enregistrer les modifications" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Supprimer définitivement" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Modification de %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Changement des préférences du compte de %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Modification de %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Modification du profil de %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Médias taggés avec : %(tag_name)s " + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Télécharger" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Désolé, mais ce fichier audio ne se lancera pas car\nvotre navigateur web ne supporte pas l'audio HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Vous pouvez obtenir un navigateur à jour capable de lire cette vidéo sur <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Fichier original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "fichier WebM (codec Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Image de %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "fichier WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Ajouter une collection" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Ajoutez votre média" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Éditer" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Effacer" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Voulez-vous vraiment supprimer %(title)s ?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Voulez vous vraiment retirer %(media_title)s de %(collection_title)s ?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Retirer" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Bonjour %(username)s,\n%(comment_author)s a commenté votre post (%(comment_url)s) sur %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Medias de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Médias de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Parcourir les médias de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Ajouter un commentaire" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Ajouter ce commentaire" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Ajouter une nouvelle collection" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Vous pouvez suivre l'état des médias en cours de traitement pour votre galerie ici." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Vos 10 derniers envois réussis" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "profil de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Impossible de trouver cet utilisateur, désolé." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Vérification d'email nécessaire" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Presque fini ! Votre compte a encore besoin d'être activé." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Un e-mail devrait vous parvenir dans quelques instants ; il vous indiquera comment procéder." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Si la vérification n'est pas arrivée à bon port :" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Renvoyer l'e-mail de vérification" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Quelqu'un a enregistré un compte avec ce nom, mais il doit encore être activé." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Si c'est de vous qu'il s'agit, mais que vous avez perdu l'e-mail de vérification, vous pouvez vous <a href=\"%(login_url)s\">identifier</a> et le renvoyer." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Voici un endroit pour parler aux autres de vous-même." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Modifier le profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Cet utilisateur n'a pas (encore) rempli son profil." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Voir tous les médias de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "C'est là où vos médias apparaîssent, mais vous ne semblez pas avoir encore ajouté quoi que ce soit." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Il ne semble pas y avoir de média là, pour l'instant ..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "icone de flux" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "flux Atom" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Tous droits réservés" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Le plus récent" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Le plus vieux →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Aller à la page :" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "le plus récent" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "le plus vieux" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Taggé avec" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Impossible de lire l'image." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Zut !" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Une erreur est survenue" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Opération non autorisée" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Je regrette Dave, cela m'est malheureusement impossible !</p><p>Vous avez essayé d'effectuer une action pour laquelle vous n'avez pas de permission. Avez-vous tenté de supprimer tous les comptes utilisateur à nouveau ?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Il ne semble pas y avoir de page à cette adresse. Désolé ! </p><p>Si vous êtes sûr que l'adresse est correcte, peut-être que la page que vous recherchez a été déplacée ou supprimée." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Vous pouvez utilisez les <a href=\"http://daringfireball.net/projects/markdown/basics\">Balises</a> pour la mise en page." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Je suis sûr de vouloir supprimer cela" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Je suis certain de vouloir retirer cet élément de la collection" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Sélectionner --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Inclure une note" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "a commenté votre post" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Oups, votre commentaire était vide." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Votre commentaire a été posté !" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Veuillez vérifier vos entrées et réessayer." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Vous devez sélectionner ou ajouter une collection" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" est déjà dans la collection \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" as été ajouté à la collection \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Vous avez supprimé le media." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Ce media n'a pas été supprimé car vous n'avez pas confirmer que vous étiez sur." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Vous êtes sur le point de supprimer des médias d'un autre utilisateur. Procédez avec prudence." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Vous avez supprimé cet élément de la collection." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "L'élément n'as pas été supprimé car vous n'avez pas confirmé votre certitude." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Vous vous apprêtez à supprimer un élément de la collection d'un autre utilisateur. Procédez avec attention." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Vous avez supprimé la collection \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "La collection n'as pas été supprimée car vous n'avez pas confirmé votre certitude" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Vous vous apprêtez à supprimer la collection d'un autre utilisateur. Procédez avec attention." diff --git a/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..09412b0a --- /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..4a5c2b52 --- /dev/null +++ b/mediagoblin/i18n/he/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1254 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# GenghisKhan <genghiskhan@gmx.ca>, 2013 +# GenghisKhan <genghiskhan@gmx.ca>, 2012 +# GenghisKhan <genghiskhan@gmx.ca>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-01 07:11+0000\n" +"Last-Translator: GenghisKhan <genghiskhan@gmx.ca>\n" +"Language-Team: Hebrew (http://www.transifex.com/projects/p/mediagoblin/language/he/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: he\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "שם משתמש" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "סיסמה" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "כתובת דוא״ל" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "שם משתמש או דוא״ל" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "שם משתמש או דוא״ל" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "שם משתמש או דוא״ל שגוי." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "שדה זה לא לוקח כתובות דוא״ל." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "שדה זה מצריך כתובת דוא״ל." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "צר לי, רישום הינו מנוטרל על שרת זה." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "צר לי, משתמש עם שם זה כבר קיים." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "צר לי, משתמש עם דוא״ל זה כבר קיים." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "כתובת הדוא״ל שלך אומתה. כעת באפשרותך להתחבר, לערוך את דיוקנך, ולשלוח תמונות!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "מפתח האימות או זהות משתמש הינם שגויים" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "עליך להתחבר על מנת שנדע אל מי לשלוח את הדוא״ל!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "כבר אימתת את כתובת הדוא״ל שלך!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "שלח שוב את דוא״ל האימות שלך." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "במידה וכתובת הדוא״ל הזו (תלוי רישיות!) רשומה דוא״ל נשלח עם הוראות בנוגע לכיצד לשנות את סיסמתך." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "לא היה ניתן למצוא מישהו עם שם משתמש זה." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "דוא״ל נשלח בצירוף הוראות בנוגע לכיצד ניתן לשנות את סיסמתך." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "לא היה ניתן לשלוח דוא״ל לשחזור סיסמה מאחר ושם המשתמש שלך אינו פעיל או שכתובת הדוא״ל של חשבונך לא אומתה." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "כעת ביכולתך להתחבר באמצעות סיסמתך החדשה." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "כותרת" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "תיאור של מלאכה זו" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "ביכולתך להשתמש בתחביר\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> לעיצוב." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "תגיות" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "הפרד תגיות בעזרת פסיקים." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "חשופית" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "החשופית לא יכולה להיות ריקה" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "אזור הכותרת של כתובת מדיה זו. לרוב אין הכרח לשנות את חלק זה." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "רשיון" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "ביו" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "אתר רשת" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "כתובת זו מכילה שגיאות" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "עדיפות רשיון" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "זה יהיה הרשיוןן המשתמט (ברירת מחדל) שלך בטופסי העלאה." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "שלח לי דוא״ל כאשר אחרים מגיבים על המדיה שלי" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "הכותרת לא יכולה להיות ריקה" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "תיאור אוסף זה" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "אזור הכותרת של כתובת אוסף זה. לרוב אין הכרח לשנות את חלק זה." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "סיסמה ישנה" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "הזן את סיסמתך הישנה כדי להוכיח שאתה הבעלים של חשבון זה." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "סיסמה חדשה" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "רשומה עם חשופית זו כבר קיימת עבור משתמש זה." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "אתה עורך מדיה של משתמש אחר. המשך בזהירות." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "הוספת את התצריף %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "באפשרותך לערוך רק את הדיוקן שלך." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "אתה עורך דיוקן של משתמש. המשך בזהירות." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "שינויי דיוקן נשמרו" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "הגדרות חשבון נשמרו" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "עליך לאמת את המחיקה של חשבונך." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "כבר יש לך אוסף שקרוי בשם \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "אוסף עם חשופית זו כבר קיים עבור משתמש זה." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "אתה עורך אוסף של משתמש אחר. המשך בזהירות." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "סיסמה שגויה" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "סיסמתך שונתה בהצלחה" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "לא ניתן לקשר אל מוטיב... לא הוגדר מוטיב\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "אין מדור נכס עבור מוטיב זה\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "בכל אופן, קישור מדור symlink נמצא; הוסר.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "לא היתה אפשרות לקשר את \"%s\": %s קיים ואינו קישור סמלי (symlink)\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "מדלג על \"%s\"; כבר מוגדר.\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "קישור ישן נמצא עבור \"%s\"; מסיר כעת.\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "עוגיית CSRF לא נוכחת. זה קרוב לוודאי נובע משום חוסם עוגייה או משהו בסגנון.<br/>הבטח קביעה של עוגיות עבור תחום זה." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "צר לי, אינני תומך בטיפוס קובץ זה :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "unoconv נכשל לפעול, בדוק קובץ יומן" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "המרת וידאו נכשלה" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "מיקום" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "הצגה אצל <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "התר" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "אסור" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "שם" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "השם של לקוח OAuth" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "תיאור" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "זה יראה למשתמשים שמתירים\n ליישומים שלך לאמת אותם." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "טיפוס" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>סודי</strong> - הלקוח יכול\n ליצור בקשות אל שרת GNU MediaGoblin שלא יכולות להיבלם\n על ידי user agent (למשל לקוח server-side).<br />\n <strong>פומבי</strong> - הלקוח לא יכול ליצור בקשות\n סודיות אל של GNU MediaGoblin (למשל לקוח\n JavaScript מתופעל client-side)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "שדה זה הינו דרוש עבור לקוחות פומביים" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "הלקוח {0} נרשם!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "חיבורי לקוח OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "לקוחות OAuth שלך" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "הוסף" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "ניתן קובץ שגוי עבור טיפוס מדיה." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "קובץ" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "עליך לספק קובץ." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "הידד! נשלח!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "אוסף \"%s\" התווסף!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "אמת את הדוא״ל שלך!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "התנתקות" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "התחברות" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "החשבון של <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "שנה הגדרות חשבון" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "לוח עיבוד מדיה" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "התנתקות" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "הוספת מדיה" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "צור אוסף חדש" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "תמונה של גובלין מתאמץ יתר על המידה" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "המדיה האחרונה ביותר" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "כאן ביכולתך לעקוב אחר המצב של המדיה שמתעבדת בשרת זה." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "מדיה באמצע-עיבוד" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "אין מדיה באמצע-עיבוד" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "העלאות אלה נכשלו להתעבד:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "אין רשומות כושלות!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "10 העלאות מוצלחות אחרונות" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "אין רישומים מעובדים, עדיין!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "הגדר את סיסמתך החדשה" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "הגדר סיסמה" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "שחזר סיסמה" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "שלח הוראות" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "שלום %(username)s,\n\nבכדי לשנות את סיסמתך אצל GNU MediaGoblin, עליך לפתוח את הכתובת הבאה \nבתוך דפדפן הרשת שלך:\n\n%(verification_url)s\n\nבמידה ואתה חושב שמדובר בשגיאה, פשוט התעלם מן דוא״ל זה והמשך להיות\nגובלין מאושר!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "התחברות נכשלה!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "אין לך חשבון עדיין?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "צור חשבון כאן!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "שכחת את סיסמתך?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "צור חשבון!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "צור" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "שלום %(username)s,\n\nבכדי להפעיל את חשבונך אצל GNU MediaGoblin, עליך לפתוח את הכתובת הבאה\nבתוך דפדפן הרשת שלך:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "ממונע על ידי <a href=\"http://mediagoblin.org/\" title='גירסה %(version)s'>MediaGoblin</a>, פרויקט <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "משוחרר תחת הרשיון <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">קוד מקור</a> זמין." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "לחקור" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "שלום לך, ברוך בואך אל אתר MediaGoblin זה!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "אתר זה מריץ <a href=\"http://mediagoblin.org\">MediaGoblin</a>, חתיכת תוכנת אירוח מדיה יוצאת מן הכלל." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "בכדי להוסיף את המדיה שלך, להשים תגובות, ועוד, ביכולתך להתחבר עם חשבון MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "אין ברשותך חשבון עדיין? זה קל!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">צור חשבון באתר זה</a>\n או\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">התקן את MediaGoblin על שרתך</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "לוגו MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "עריכת תצריפים עבור %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "תצריפים" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "הוספת תצריף" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "ביטול" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "שמור שינויים" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "משנה כעת את הסיסמה של %(username)s'" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "שמור" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "באמת למחוק את המשתמש '%(user_name)s' ואת כל המדיה/התגובות הקשורות?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "כן, באמת למחוק את חשבוני" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "מחק לצמיתות" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "ערוך %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "שינוי הגדרות חשבון עבור %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "שנה את סיסמתך." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "מחק את החשבון שלי" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "עריכת %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "עריכת דיוקן עבור %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "מדיה מתויגת עם: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "הורד" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "מקורית" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "צר לי, אודיו זה לא יעבוד מכיוון \n\tשדפדפן הרשת שלך לא תומך \n\tאודיו של HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "ביכולתך להשיג דפדפן רשת מודרני \n\tשכן מסוגל לנגן את אודיו זה אצל <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "קובץ מקורי" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "קובץ WebM (קודק Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "תמונה עבור %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "קובץ PDF" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "החלף סיבוב" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "נקודת מבט" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "לפנים" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "ראש" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "צד" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "הורד מודל" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "פורמט קובץ" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "גובה אובייקט" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "צר לי, וידאו זה לא יעבוד מכיוון \n שדפדפן הרשת שלך לא תומך \n וידאו של HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "ביכולתך להשיג דפדפן רשת מודרני \n שכן מסוגל לנגן את וידאו זה אצל <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "קובץ WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "הוסף אוסף" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "הוספת המדיה שלך" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (אוסף של %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s מאת <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "ערוך" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "מחק" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "באמת למחוק את %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "באמת להסיר את %(media_title)s מן %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "הסר" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "אוספים של %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "אוספים של <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "שלום %(username)s,\n%(comment_author)s הגיב/ה על פרסומך (%(comment_url)s) אצל %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "המדיה של %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "מדיה משתמש <a href=\"%(user_url)s\">%(username)s</a> עם תגית <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "המדיה של <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ עיון במדיה מאת <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "הוסף תגובה" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "הוסף את תגובה זו" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "מלפני %(formatted_time)s" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "התווסף" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "נוצר" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "הוסף את “%(media_title)s” אל אוסף" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "הוסף אוסף חדש" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "ביכולתך לעקוב כאן אחר מצב של מדיה שמצויה בתהליך עיבוד עבור הגלריה שלך." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "10 ההעלאות המוצלחות שלך" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "הדיוקן של %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "צר לי, משתמש נתון לא נמצא." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "נדרש אימות דוא״ל" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "כמעת סיימנו! חשבונך עדיין צריך אקטיבציה." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "דוא״ל צפוי להגיע בעוד מספר רגעים בצירוף הוראות בנוגע לכיצד לעשות כך." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "במידה וזה לא:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "שלח דוא״ל אימות" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "מישהו רשם חשבון עם שם משתמש זה, אך עליו להיות מופעל." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "אם אתה אכן אדם זה אולם איבדת את דוא״ל האימות שלך, ביכולתך <a href=\"%(login_url)s\">להתחבר</a> ולשלוחו מחדש." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "הנה מקום לומר לאחרים אודותייך." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "ערוך דיוקן" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "משתמש זה לא מילא דיוקן (עדיין)." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "עיון באוספים" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "צפיה בכל המדיה של %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "כאן זה המקום בו המדיה שלך תופיע, אולם לא נראה שהוספת משהו עדיין." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "לא נראה שיש כאן מדיה כלשהי עדיין..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(הסר)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "הוסף אל אוסף" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "צלמית ערוץ" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "ערוץ Atom" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "כל הזכויות שמורות" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "חדש יותר ←" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "→ ישן יותר" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "מעבר אל עמוד:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "חדש יותר" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "ישן יותר" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "מתויגת עם" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "לא היה ניתן לקרוא את קובץ התמונה." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "אופס!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "אירעה שגיאה" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "פעולה לא מורשית" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "צר לי דוד, אני לא יכול להתיר לך לעשות זאת!</p><p>ניסית לבצע פעולה שאינך מורשה לעשות. האם ניסית למחוק את כל החשבונות של המשתמשים שוב?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "לא נראה שקיים עמוד בכתובת זו. צר לי!</p><p>אם אתה בטוח שהכתובת הינה מדויקת, ייתכן שהעמוד שאתה מחפש כעת הועבר או נמחק." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "שנה" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "חודש" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "שבוע" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "יום" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "שעה" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "דקה" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "תגובה" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "ביכולתך לעשות שימוש בתחביר <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> לעיצוב." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "אני בטוח שברצוני למחוק זאת" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "אני בטוח שברצוני להסיר את פריט זה מן האוסף" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "אוסף" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- בחר --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "הכללת פתק" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "הגיב/ה על פרסומך" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "מצטערים, תגובות מנוטרלות." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "אופס, תגובתך היתה ריקה." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "תגובתך פורסמה!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "אנא בדוק את רשומותיך ונסה שוב." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "עליך לבחור או להוסיף אוסף" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" כבר קיים באוסף \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" התווסף אל האוסף \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "מחקת את מדיה זו." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "המדיה לא נמחקה מכיוון שלא סימנת שאתה בטוח." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "בחרת למחוק מדיה של משתמש אחר. המשך בזהירות." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "מחקת את הפריט מן אוסף זה." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "הפריט לא הוסר מכיוון שלא סימנת שאתה בטוח." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "בחרת למחוק פריט מן אוסף של משתמש אחר. המשך בזהירות." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "מחקת את האוסף \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "האוסף לא הוסר מכיוון שלא סימנת שאתה בטוח." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "בחרת למחוק אוסף של משתמש אחר. המשך בזהירות." diff --git a/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..d22f6ee6 --- /dev/null +++ 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 new file mode 100644 index 00000000..c9f814fc --- /dev/null +++ b/mediagoblin/i18n/ia/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Aleksandr Brezhnev <abrezhnev@gmail.com>, 2012 +# Emilio Sepúlveda <emisepulvedam@gmail.com>, 2011 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Interlingua (http://www.transifex.com/projects/p/mediagoblin/language/ia/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ia\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Nomine de usator" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Contrasigno" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Adresse de e-posta" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titulo" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etiquettas" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Sito web" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "File" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Initiar session" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Crear un conto!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Cancellar" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profilo de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..596ab843 --- /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..77896b87 --- /dev/null +++ b/mediagoblin/i18n/is_IS/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# tryggvib <tryggvib@fsfi.is>, 2012 +# tryggvib <tryggvib@fsfi.is>, 2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-05 22:51+0000\n" +"Last-Translator: tryggvib <tryggvib@fsfi.is>\n" +"Language-Team: Icelandic (Iceland) (http://www.transifex.com/projects/p/mediagoblin/language/is_IS/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: is_IS\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Notandanafn" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Lykilorð" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Netfang" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Notandanafn eða tölvupóstur" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Notandanafn eða netfang" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Ógilt notandanafn eða netfang" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Þessi reitur tekur ekki við netföngum." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "í þennan reit verður að slá inn netfang." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Því miður er nýskráning ekki leyfð á þessu svæði." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Því miður er nú þegar til notandi með þetta nafn." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Því miður þá er annar notandi í kerfinu með þetta netfang skráð." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Netfangið þitt hefur verið staðfest. Þú getur núna innskráð þig, breytt kenniskránni þinni og sent inn efni!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Staðfestingarlykillinn eða notendaauðkennið er rangt" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Þú verður að hafa innskráð þig svo við vitum hvert á að senda tölvupóstinn!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Þú hefur staðfest netfangið þitt!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Endursendi staðfestingartölvupóst" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Ef þetta netfang (há- og lágstafir skipta máli) er skráð hjá okkur hefur tölvupóstur verið sendur með leiðbeiningum um hvernig þú getur breytt lykilorðinu þínu." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Gat ekki fundið neinn með þetta notandanafn." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Tölvupóstur hefur verið sendur með leiðbeiningum um hvernig þú átt að breyta lykilorðinu þínu." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Gat ekki sent tölvupóst um endurstillingu lykilorðs því notandanafnið þitt er óvirkt eða þá að þú hefur ekki staðfest netfangið þitt." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Þú getur núna innskráð þig með nýja lykilorðinu þínu." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titill" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Lýsing á þessu efni" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Þú getur notað\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> til að stílgera textann." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Efnisorð" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Aðskildu efnisorðin með kommum." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Vefslóðarormur" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Vefslóðarormurinn getur ekki verið tómur" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Titilhlutinn í vefslóð þessa efnis. Þú þarft vanalega ekki að breyta þessu." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Leyfi" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Lýsing" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Vefsíða" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Þetta netfang inniheldur villur" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Leyfiskjörstilling" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Þetta verður sjálfgefna leyfið þegar þú vilt hlaða upp efni." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Senda mér tölvupóst þegar einhver bætir athugasemd við efnið mitt" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Þessi titill getur verið innihaldslaus" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Lýsing á þessu albúmi" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Titilhlutinn í vefslóð þessa albúms. Þú þarft vanalega ekki að breyta þessu." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Gamla lykilorðið" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Skráðu gamla lykilorðið þitt til að sanna að þú átt þennan aðgang." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nýtt lykilorð" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Efni merkt með þessum vefslóðarormi er nú þegar til fyrir þennan notanda." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Þú ert að breyta efni annars notanda. Farðu mjög varlega." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Þú bættir við viðhenginu %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Þú getur bara breytt þinni eigin kenniskrá." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Þú ert að breyta kenniskrá notanda. Farðu mjög varlega." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Breytingar á kenniskrá vistaðar" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Aðgangsstillingar vistaðar" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Þú verður að samþykkja eyðingu á notandaaðganginum þínum." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Þú hefur nú þegar albúm sem kallast \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Albúm með þessu vefslóðarormi er nú þegar til fyrir þennan notanda." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Þú ert að breyta albúmi annars notanda. Farðu mjög varlega." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Vitlaust lykilorð" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Það tókst að breyta lykilorðinu þínu" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Get ekki hlekkjað í þema... ekkert þema stillt\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Engin eignamappa fyrir þetta þema\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Fann samt gamlan táknrænan tengil á möppu; fjarlægður.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "Gat ekki tengt \"%s\": %s er til og er ekki sýndartengill\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "Hoppa yfir \"%s\"; hefur nú þegar verið sett upp.\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "Gamall tengill fannst fyrir \"%s\"; fjarlægi.\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "CSRF smygildi ekki til staðar. Þetta er líklegast orsakað af smygildishindrara eða einhverju þess háttar.<br/>Athugaðu hvort þú leyfir ekki alveg örugglega smygildi fyrir þetta lén." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Ég styð því miður ekki þessa gerð af skrám :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "tekst ekki að keyra unoconv, athugaðu annálsskrá" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Myndbandsþverkótun mistókst" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Staðsetning" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Skoða á <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Leyfa" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Banna" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nafn" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Nafn OAuth biðlarans" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Lýsing" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Þetta verður sýnilegt öðrum notendum sem leyfir\n forritinu þínu að skrá sig inn sem þeir." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Tegund" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Trúnaður</strong> - Biðlarinn getur\n sent beiðnir til GNU MediaGoblin vefsvæðisins sem geta ekki verið\n truflaðar af notandaforriti (t.d. forriti á vefþjóni).<br />\n <strong>Opinbert</strong> - Biðlarinn getur ekki gert trúnaðarbundnar\n beiðnir til GNU MediaGoblin vefsvæðisins (t.d. Javascript biðlara\n hjá notanda)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Áframsendingarvefslóð" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "Áframsendingarvefslóðin fyrir forritin, þessi reitur\n er <strong>nauðsynlegur</strong> fyrir opinbera biðlara." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Þessi reitur er nauðsynlegur fyrir opinbera biðlara" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Biðlarinn {0} hefur verið skráður!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "Biðlarartengingar OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "OAuth-biðlararnir þínir" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Bæta við" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Ógild skrá gefin fyrir þessa margmiðlunartegund." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Skrá" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Þú verður að gefa upp skrá." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Jibbí jei! Það tókst að senda inn!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Albúmið \"%s\" var búið til!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Staðfestu netfangið þitt!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "útskrá" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Innskrá" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Notandaaðgangur: <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Breyta stillingum notandaaðgangs" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Margmiðlunarvinnsluskiki" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Skrá út" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Senda inn efni" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Búa til nýtt albúm" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Mynd af durt í stresskasti" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Nýlegt efni" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Hér getur þú fylgst með margmiðlunarefni sem verið er að vinna á þessu vefsvæði." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Efni í vinnslu" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Ekkert efni í vinnslu" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Það mistókst að fullvinna þessar innsendingar:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Engar bilaðar innsendingar!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Síðustu 10 árangursríku innsendingarnar" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Ekkert fullunnið efni enn!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Skrifaðu inn nýja lykilorðið þitt" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Skrá lykilorð" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Endurstilla lykilorð" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Senda leiðbeiningar" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Hæ %(username)s,\n\ntil að breyta GNU MediaGoblin lykilorðinu þínu opnar þú eftirfarandi vefslóð í \nvafranum þínum:\n\n%(verification_url)s\n\nEf þú heldur að það sé einhver vitleysa í gangi husnar þú bara þennan póst og heldur áfram að vera\nánægður durtur!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Mistókst að skrá þig inn." + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Ertu ekki með notendaaðgang?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Búðu til aðgang hérna!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Gleymdirðu lykilorðinu þínu?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Búðu til nýjan aðgang!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Búa til" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Hæ %(username)s,\n\ntil að virkja GNU MediaGoblin aðganginn þinn, opnaðu þá eftirfarandi vefslóði í\nvafranum þínum:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Keyrt af <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, sem er <a href=\"http://gnu.org/\">GNU</a> verkefni." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Gefið út undir <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Frumkóti</a> aðgengilegur." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Skoða" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hæ! Gakktu í bæinn á þetta MediaGoblin vefsvæði!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Þetta vefsvæði keyrir á <a href=\"http://mediagoblin.org\">MediaGoblin</a> sem er ótrúlega frábær hugbúnaður til að geyma margmiðlunarefni." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Til að senda inn þitt efni, gera athugasemdir og fleira getur þú skráð þig inn með þínum MediaGoblin aðgangi." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Ertu ekki með aðgang? Það er auðvelt að búa til!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Búa til aðgang á þessari síðu</a>\neða\n<a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Settu upp þinn eigin margmiðlunarþjón</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin einkennismerkið" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Breyti viðhengjum við: %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Viðhengi" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Bæta við viðhengi" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Hætta við" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Vista breytingar" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Breyti lykilorði fyrir notandann: %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Vista" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Virkilega eyða notanda '%(user_name)s' og tengt efni/athugasemdir?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Já, ég vil örugglega eyða aðganginum mínum" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Eytt algjörlega" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Breyti %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Breyti notandaaðgangsstillingum fyrir: %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Breyta lykilorðinu þínu." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Eyða aðganginum mínum" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Breyti %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Breyti kenniskrá notandans: %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Efni merkt með: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Sækja af Netinu" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Upphafleg skrá" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Því miður mun þessi hljóðskrá ekki virka því \n\tvafrinn þinn styður ekki HTML5 \n\thljóðskrár." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Þú getur náð í nýlegan vafra sem \n\tgetur spilað hljóðskrár á <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Upphaflega skráin" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM skrá (Vorbis víxlþjöppun)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Mynd fyrir %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF skrá" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Stilla snúning af eða á" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Sjónhorf" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Framhlið" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Toppur" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Hlið" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Hala niður líkani" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Skráarsnið" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Hæð hlutar" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Því miður mun þetta myndband ekki virka því\n vafrinn þinn styður ekki HTML5 \n myndbönd." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Þú getur náð í nýlegan vafra sem \n sem getur spilað myndbandið á <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM skrá (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Búa til albúm" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Sendu inn efni" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (albúm sem %(username)s á)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s sem <a href=\"%(user_url)s\">%(username)s</a> bjó til" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Breyta" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Eyða" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Virkilega eyða %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Virkilega fjarlægja %(media_title)s úr %(collection_title)s albúminu?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Fjarlægja" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Albúm sem %(username)s á" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Albúm sem <a href=\"%(user_url)s\">%(username)s</a> á" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Hæ %(username)s,\n%(comment_author)s skrifaði athugasemd við færsluna þína (%(comment_url)s) á %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Efni sem %(username)s á" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Efni sem <a href=\"%(user_url)s\">%(username)s</a> á og er merkt með <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Efni sem <a href=\"%(user_url)s\">%(username)s</a> á" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Skoða efnið sem <a href=\"%(user_url)s\">%(username)s</a> setti inn" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Bæta við athugasemd" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Senda inn þessa athugasemd" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "Fyrir %(formatted_time)s" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Bætt við" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Skapað" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Setja '%(media_title)s' í albúm" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Búa til nýtt albúm" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Þú getur fylgst með hvernig gengur að vinna með margmiðlunarefnið fyrir safnið þitt hérna." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Síðustu 10 árangursíku innsendingarnar þínar" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Kenniskrá fyrir: %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Því miður fannst notandinn ekki." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Staðfesting á netfangi nauðsynleg" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Næstum því búið! Notandaaðgangurinn þinn verður að vera staðfestur." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Tölvupóstur ætti að berast til þín eftir smástund með leiðbeiningum um hvernig þú átt að gera það." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Ef það gerist ekki:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Endursenda staðfestingarpóst" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Einhver hefur búið til aðgang með þessu notandanafni en hefur ekki enn virkjað aðganginn." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Ef þú ert þessi aðili en hefur týnt staðfestingarpóstinum getur þú <a href=\"%(login_url)s\">skráð þig inn</a> og endursent hann" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Hér er svæði til að segja öðrum frá þér." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Breyta kenniskrá" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Þessi notandi hefur ekki fyllt inn í upplýsingar um sig (ennþá)." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Skoða albúm" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Skoða efnið sem %(username)s á" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Þetta er staðurinn þar sem efnið þitt birtist en þú virðist ekki hafa sent neitt inn ennþá." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Það virðist ekki vera neitt efni hérna ennþá..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(fjarlægja)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Sett í albúm" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Setja í albúm" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "fréttaveituteikn" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom fréttaveita" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Öll réttindi áskilin" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Nýrri" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Eldri →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Fara á síðu:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "nýrri" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "eldri" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Merkt með" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Gat ekki lesið myndskrána." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Obbosí!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Villa kom upp" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Aðgerð ekki leyfileg" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Fyrirgefðu Davíð. Ég get ekki leyft þér að gera þetta!</p></p>Þú hefur reynt að framkvæma aðger sem þú hefur ekki leyfi til. Varstu að reyna að eyða öllum notendunum aftur?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Því miður! Það virðist ekki vera nein síða á þessari vefslóð.</p><p>Ef þú ert viss um að vefslóðin sé rétt hefur vefsíðan sem þú ert að leita að kannski verið flutt eða fjarlægð." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "ár" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "mánuður" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "vika" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "dagur" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "klukkustund" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "mínúta" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Athugasemd" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Þú getur notað <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> til að stílgera textann" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Ég er viss um að ég vilji eyða þessu" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Ég er viss um að ég vilji fjarlægja þetta efni úr albúminu" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Albúm" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Velja --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Bæta við minnispunktum" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "skrifaði athugasemd við færsluna þína" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Því miður, athugasemdir eru óvirkar." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Obbosí! Athugasemdin þín var innihaldslaus." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Athugasemdin þín var skráð!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Vinsamlegast kíktu á innsendingarnar þínar og reyndu aftur." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Þú verður að velja eða búa til albúm" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" er nú þegar í albúminu \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" sett í albúmið \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Þú eyddir þessu efni." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Efninu var ekki eytt þar sem þú merktir ekki við að þú værir viss." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Þú ert í þann mund að fara að eyða efni frá öðrum notanda. Farðu mjög varlega." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Þú tókst þetta efni úr albúminu." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Þetta efni var ekki fjarlægt af því að þú merktir ekki við að þú værir viss." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Þú ert í þann mund að fara að eyða efni úr albúmi annars notanda. Farðu mjög varlega." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Þú eyddir albúminu \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Þessu albúmi var ekki eytt vegna þess að þu merktir ekki við að þú værir viss." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Þú ert í þann mund að fara að eyða albúmi annars notanda. Farðu mjög varlega." diff --git a/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..62575b62 --- /dev/null +++ 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 new file mode 100644 index 00000000..c782fc62 --- /dev/null +++ b/mediagoblin/i18n/it/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1256 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Francesco Apruzzese <cescoap@gmail.com>, 2012 +# gdb <gaedeb01@gmail.com>, 2013 +# pikappa469 <pikappa469@alice.it>, 2011 +# nunni <robi@nunnisoft.ch>, 2011 +# Damtux <sun_lion@live.com>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Italian (http://www.transifex.com/projects/p/mediagoblin/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Nome utente" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Password" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Indirizzo email" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Nome utente o indirizzo email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Spiacente, la registrazione è disabilitata su questa istanza." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Spiacente, esiste già un utente con quel nome." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Siamo spiacenti, un utente con quell'indirizzo email esiste già." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Il tuo indirizzo email è stato verificato. Ora puoi accedere, modificare il tuo profilo e caricare immagini!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "La chiave di verifica o l'id utente è sbagliato" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Devi effettuare l'accesso così possiamo sapere a chi inviare l'email!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Hai già verificato il tuo indirizzo email!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Rispedisci email di verifica" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Ti è stata inviata un'email con le istruzioni per cambiare la tua password." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Impossibile inviare l'email di recupero password perchè il tuo nome utente è inattivo o il tuo indirizzo email non è stato verificato." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Ora puoi effettuare l'accesso con la nuova password." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titolo" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Descrizione di questo lavoro" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Puoi usare il\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> per la formattazione." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Tags" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Separa le tags con la virgola." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Il titolo è parte dell'indirizzo del file. Nella maggior parte dei casi non c'è bisogno di cambiarlo." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licenza" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Biografia" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Sito web" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Questo indirizzo contiene errori" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Inviami messaggi email quando altre persone commentano i miei files multimediali" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Password vecchia" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Inserisci la vecchia password per dimostrare di essere il proprietario dell'account." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nuova password" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Stai modificando files multimediali di un altro utente. Procedi con attenzione." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Stai modificando il profilo di un utente. Procedi con attenzione." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Cambiamenti del profilo salvati" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Impostazioni del profilo salvate" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Password errata" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Mi dispiace, non supporto questo tipo di file :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Transcodifica video fallita" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Posizione" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Visualizza su <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Aggiungi" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "File non valido per il tipo di file multimediale indicato." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "File" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Devi specificare un file." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Evviva! Caricato!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifica la tua email!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Accedi" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Cambia le impostazioni dell'account" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Pannello di elaborazione files multimediali" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Aggiungi files multimediali" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Files multimediali più recenti" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Qui è possibile tenere traccia dello stato dei file multimediali in fase di elaborazione in questa istanza." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Files multimediali in elaborazione" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Nessun file multimediale in elaborazione" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "L'elaborazione di questi files caricati è fallita:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Ultimi 10 caricamenti riusciti" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Imposta la nuova password" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Imposta password" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Recupera Password" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Invia istruzioni" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Ciao %(username)s,\n\nper cambiare la tua password MediaGoblin apri il seguente URL nel\ntuo browser web:\n\n%(verification_url)s\n\nSe pensi che questo sia un errore, ignora semplicemente questa email e continua ad essere \nun goblin felice!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Accesso fallito!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Non hai ancora un account?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Creane uno qui!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Hai dimenticato la tua password?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Crea un account!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Crea" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Ciao %(username)s,\n\nper attivare il tuo account GNU MediaGoblin, apri il seguente URL nel \ntuo navigatore web.\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Rilasciato con licenza <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Codice sorgente</a> disponibile." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Esplora" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Ciao, benvenuto in questo sito MediaGoblin!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Questo sito sta utilizzando <a href=\"http://mediagoblin.org\">Mediagoblin</a>, un ottimo programma per caricare e condividere files multimediali." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Per aggiungere i tuoi file multimediali, scrivere commenti e altro puoi accedere con il tuo account MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Non ne hai già uno? E' semplice!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Simbolo di MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Stai modificando gli allegati di %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Allegati" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Aggiungi allegato" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Annulla" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Salva i cambiamenti" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Elimina definitivamente" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Stai modificando %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Stai cambiando le impostazioni dell'account di %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Stai modificando il profilo di %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "File taggato con: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Scarica" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Originale" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Spiacente ma è impossibile leggere questo file audio perché\n\til tuo browser web non supporta l'HTML5 \n\taudio." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Puoi scaricare un browser web moderno,\n\t in grado di leggere questo file audio, qui <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "File originario" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "File WebM (codec Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "File WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Aggiungi il tuo file multimediale" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Modifica" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Elimina" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Vuoi davvero eliminare %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Ciao %(username)s,\n%(comment_author)s ha commentato il tuo post (%(comment_url)s) su %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Files multimediali di %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Files multimediali di <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Stai guardando i files multimediali di <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Aggiungi un commento" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Aggiungi questo commento" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Puoi controllare lo stato dei files multimediali in elaborazione per la tua galleria qui." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "I tuoi ultimi 10 caricamenti riusciti" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "profilo di %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Mi dispiace, utente non trovato." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "E' necessario verificare l'indirizzo email" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Quasi finito! Il tuo account deve ancora essere attivato." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "In breve dovresti ricevere un email contenente istruzioni su come fare." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Nel caso non fosse:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Rispedisci email di verifica" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Qualcuno ha registrato un account con questo nome utente, ma deve ancora essere attivato." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Se sei quella persona ma hai perso l'email di verifica, puoi <a href=\"%(login_url)s\">accedere</a> e rispedirla." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Ecco un posto dove raccontare agli altri di te." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Modifica profilo" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Questo utente non ha (ancora) compilato il proprio profilo." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Visualizza tutti i files multimediali di %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Qui è dove appariranno i tuoi files multimediali, ma sembra che tu non abbia ancora aggiunto niente." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Sembra che non ci sia ancora nessun file multimediale qui..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "feed icon" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Tutti i diritti riservati" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Più recente" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Più vecchio →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Vai alla pagina:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "più recente" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "più vecchio" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Taggato con" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Impossibile leggere il file immagine." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Oops!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Puoi usare il <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> per la formattazione." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Sono sicuro di volerlo eliminare" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "ha commentato il tuo post" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Oops, il tuo commento era vuoto." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Il tuo commento è stato aggiunto!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Hai eliminato il file." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Il file non è stato eliminato perchè non hai confermato di essere sicuro." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Stai eliminando un file multimediale di un altro utente. Procedi con attenzione." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..3c82d1ff --- /dev/null +++ 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 new file mode 100644 index 00000000..97d68127 --- /dev/null +++ b/mediagoblin/i18n/ja/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Avery <averym@gmail.com>, 2011 +# parlegon <parlegon@gmail.com>, 2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Japanese (http://www.transifex.com/projects/p/mediagoblin/language/ja/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ja\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "ユーザネーム" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "パスワード" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "メールアドレス" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "申し訳ありませんが、このインスタンスで登録は無効になっています。" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "申し訳ありませんが、その名前を持つユーザーがすでに存在しています。" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "メアドが確認されています。これで、ログインしてプロファイルを編集し、画像を提出することができます!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "検証キーまたはユーザーIDが間違っています" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "検証メールを再送しました。" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "タイトル" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "タグ" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "スラグ" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "スラグは必要です。" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "自己紹介" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "URL" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "そのスラグを持つエントリは、このユーザーは既に存在します。" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "あなたは、他のユーザーのメディアを編集しています。ご注意ください。" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "あなたは、他のユーザーのプロファイルを編集しています。ご注意ください。" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "追加" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "ファイル" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "ファイルを提供する必要があります。" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "投稿終了!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "ログイン" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "まだアカウントを持っていませんか?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "ここで作成!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "パスワードを忘れましたか?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "アカウントを作成!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "%(username)s様へ\n\nGNU MediaGoblinアカウントを検証にするには、このURLを開いてください。\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "こんにちは、このMediaGoblinサイトへようこそ!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "キャンセル" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "投稿する" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "%(media_title)sを編集中" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "%(username)sさんのプロフィールを編集中" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "ダウンロード" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "編集" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "削除" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>さんのコンテンツ" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)sさんのプロフィール" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "申し訳ありませんが、そのユーザーは見つかりませんでした。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "メールは、その方法の指示でいくつかの瞬間に到着します。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "到着しない場合は、" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "確認メールを再送信" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "あなたの確認メールを紛失した場合、<a href=\"%(login_url)s\">ログイン</a>して再送できます。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "プロフィールを編集" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "%(username)sさんのコンテンツをすべて見る" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..7d37ab7c --- /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..5333de02 --- /dev/null +++ b/mediagoblin/i18n/ko_KR/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1252 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Jin-hoon Kim <newvgund@gmail.com>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Korean (Korea) (http://www.transifex.com/projects/p/mediagoblin/language/ko_KR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ko_KR\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "사용자 이름" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "비밀번호" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "email 주소" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "사용자 이름 또는 email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "죄송합니다. 지금은 가입 하실 수 없습니다." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "죄송합니다. 해당 사용자 이름이 이미 존재 합니다." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "죄송합니다. 사용자와 해당 이메일은 이미 등록되어 있습니다." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "해당 email 주소가 이미 인증 되어 있습니다. 지금 로그인하시고 계정 정보를 수정하고 사진을 전송해 보세요!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "인증 키 또는 사용자 ID가 올바르지 않습니다." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "로그인을 하셔야 고블린에서 메일을 보낼 수 있습니다!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "이미 인증받은 email 주소를 가지고 있습니다!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "인증 메일을 다시 보내 주세요." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "비밀번호를 변경하는 방법에 대한 설명서가 메일로 전송 되었습니다." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "사용자의 이름이 존재하지 않거나, 사용자의 email 주소가 인증되지 않아 비밀번호 복구 메일을 보낼 수 없습니다." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "이제 새로운 비밀번호로 로그인 하실 수 있습니다." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "제목" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "이 작업에 대한 설명" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "포멧팅을 사용하려면\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> 링크를 참고 하세요." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "태그" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "태그는 , 로 구분 됩니다." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "'슬러그'" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "'슬러그'는 공백일 수 없습니다." + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "제목은 미디어 주소의 일부분 입니다. 수정하지 않아도 됩니다." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "License" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "소개" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "웹 주소" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "주소에 에러가 있습니다." + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "제 미디어에 대한 컨텍을 원한다면, 메일을 보내주세요." + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "제목은 공백일 수 없습니다." + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "모음집에 대한 설명" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "예전 비밀번호" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "계정 확인을 위해, 이전 비밀 번호를 입력해 주세요." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "새로운 비밀번호" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "해당 유저에 대한 '슬러그'가 이미 존재합니다." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "다른 사용자의 미디어를 수정하고 있습니다. 조심해서 수정하세요." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "사용자의 계정 정보를 수정하고 있습니다. 조심해서 수정하세요." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "계정 정보가 저장 되었습니다." + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "계정 설정이 저장 되었습니다." + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "\"%s\" 모음집을 이미 가지고 있습니다!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "다른 유저의 모음집을 수정 중 입니다. 주의하세요." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "잘못된 비밀번호" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "테마에 연결할 수 없습니다... 테마 셋이 없습니다.\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "이 테마를 위한 에셋 디렉토리가 없습니다.\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "그런데, 오래된 디렉토리 심볼릭 링크를 찾았습니다; 지워졌습니다.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "죄송합니다. 해당 타입의 파일은 지원하지 않아요 :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "비디오 변환에 실패 했습니다." + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "장소" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr " <a href=\"%(osm_url)s\">OpenStreetMap</a>으로 보기" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "허용" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "거부" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "이름" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "설명" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "종류" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "리다이렉트 URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "이 항목은 공개 사용자들을 위해 꼭 필요 합니다." + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "사용자 {0}님이 등록 되었습니다!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "추가" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "알수없는 미디어 파일 입니다." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "파일" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "파일을 등록하셔야 합니다." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "이햐!! 등록했습니다!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "\"%s\" 모음집이 추가되었습니다!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "메일을 확인하세요!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "로그인" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "계정 설정 변경" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "미디어 작업 패널" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "미디어 추가" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "가장 최근에 등록된 미디어" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "미디어 작업중..." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "작업중인 미디어가 없습니다." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "다음 작업을 하는 중에 업로드에 실패하였습니다.:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "지난 10개의 업로드 목록" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "새로운 비밀번호를 설정 하세요." + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "비밀번호 설정" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "비밀번호 복구" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "설명서 보내기" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "안녕하세요 %(username)s,\n\nGNU MediaGoblin의 사용자 계정 비밀번호를 바꾸시려면, 인터넷 창을 여시고 아래 URL을 통해 접속 하세요. :\n\n%(verification_url)s\n\n오류라고 생각 된다면, 이 메일을 무시하시고 고블린을 즐기세요!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "로그인에 실패 했습니다!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "아직 계정이 없으세요?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "이곳에서 새로 만드세요!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "비밀번호를 잊으셨나요?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "계정을 새로 만듭니다!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "생성" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "안녕하세요 %(username)s님,\n\nGNU MediaGoblin 계정을 활성화 하시려면, 아래의 URL 주소를 브라우져로 접속하세요.\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Released under the <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Source code</a> available." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "탐색" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "안녕하세요! 미디어 고블린 사이트에 온걸 환영 합니다!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "이사이트는 <a href=\"http://mediagoblin.org\">MediaGoblin</a>으로 작동 중입니다. 이는 특이한 미디어 호스팅 소프트웨어중 하나 입니다." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "자신의 미디어를 추가하고, 댓글을 남기세요! 미디어 고블린 계정으로 내역을 확인 하실 수 있습니다!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "아직 아무것도 없으시다구요? 매우 쉽습니다!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin 로고" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "%(media_title)s의 첨부 수정 중..." + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "첨부" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "첨부 추가" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "취소" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "저장" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "영구적으로 삭제" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "%(media_title)s 편집중..." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "%(username)s'의 계정 설정 변경중..." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "%(collection_title)s 편집 중" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "%(username)s의 계정 정보 수정중..." + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "미디어는 다음으로 태그 되었습니다.: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "다운로드" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "원본" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "사용중이신 웹 브라우져가 HTML5를 지원하지 않아\n\t오디오 파일을 재생할 수 없습니다.\n\t죄송합니다." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "사운드 파일을 재생 하시려면\n\t이곳에서 최신의 브라우져를 다운받으세요! <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "원본 파일" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM 파일 (Vorbis 코덱)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "%(media_title)s 이미지" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM 파일 (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "모음집 추가" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "미디어 등록하기" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (%(username)s의 모음집)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>의 %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "수정" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "삭제" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "%(title)s 을 지우시겠습니까?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "%(collection_title)s의 %(media_title)s을 삭제 하시겠습니까?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "지우기" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "안녕하세요 %(username)s님,\n%(comment_author)s 가 (%(comment_url)s) 게시물에 %(instance_name)s 덧글을 등록 하였습니다.\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "%(username)s의 미디어" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>의 미디어" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ <a href=\"%(user_url)s\">%(username)s</a>의 미디어를 보고 있습니다." + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "덧글 달기" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "덧글 추가" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "새 모음집 추가" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "갤러리에서 미디어 작업을 하면 해당 내용을 추적할 수 있습니다." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "지난 10개의 업로드 목록" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)s의 계정 정보" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "죄송합니다. 해당 유저를 찾지 못했습니다." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "email 인증이 필요합니다." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "이미 완료했습니다! 사용자 계정은 활성화 되어 있습니다." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "곧 email 을 통해 지침서가 도착할 겁니다." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "이런경우는 작동하지 않습니다.:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "인증메일 다시 보내기" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "누군가 해당 사용자 이름으로 등록은 했으나, 아직 활성화 하지 않았습니다." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "정상적인 계정이나, 인증 메일을 잃어버리셨다면 <a href=\"%(login_url)s\">로그인</a> 을 하시고 인증 메일을 새로 보내주세요." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "당신에 대해 소개해 보세요." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "계정 정보 수정" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "이 사용자는 계정 정보를 입력하지 않았습니다." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "%(username)s의 모든 미디어 보기" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "이곳에 등록한 미디어가 나타나게 됩니다. 하지만 아직 아무런 미디어를 등록하지 않으셨네요." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "아직 어떠한 미디어도 존재하지 않습니다." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "피드 아이콘" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom 피드" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "All rights reserved" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← 최근" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "이전 →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "페이지로 이동:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "최근" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "이전" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "태그 정보" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "이미지 파일을 읽을 수 없습니다." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "웁스!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "포멧팅을 위해 <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> 을 사용할 수 있습니다.." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "이걸 지우고 싶습니다." + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "이 모음집의 항목을 삭제하는 것을 확인 했습니다." + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- 선택 --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "노트 추가" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "게시물에 덧글이 달렸습니다." + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "오우, 댓글이 비었습니다." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "댓글이 등록 되었습니다!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "확인을 하시고 다시 시도하세요." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "모음집을 추가하거나 기존 모음집을 선택하세요." + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" 모음집이 이미 존재 합니다. \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" 모음집을 추가했습니다. \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "미디어를 삭제 했습니다." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "확인 체크를 하지 않았습니다. 미디어는 삭제되지 않았습니다." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "다른 사람의 미디어를 삭제하려고 합니다. 다시 한번 확인하세요." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "모음집에 있는 항목을 삭제 했습니다." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "확인을 하지 않았습니다. 항목은 삭제하지 않았습니다." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "다른 사용자의 모음집에 있는 항목을 삭제하였습니다. 주의하세요." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "\"%s\" 모음집을 삭제하셨습니다." + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "확인을 하지 않았습니다. 모음집은 삭제하지 않았습니다." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "다른 사용자의 모음집을 삭제하려고 합니다. 주의하세요." diff --git a/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..4e6e51ce --- /dev/null +++ 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 new file mode 100644 index 00000000..14e4fb33 --- /dev/null +++ b/mediagoblin/i18n/nl/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# schendje <mail@jefvanschendel.nl>, 2011, 2012 +# mvanderboom <mvanderboom@gmail.com>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Dutch (http://www.transifex.com/projects/p/mediagoblin/language/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Gebruikersnaam" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Wachtwoord" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "E-mail adres" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Gebruikersnaam of email-adres" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Sorry, registratie is uitgeschakeld op deze instantie." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Sorry, er bestaat al een gebruiker met die naam." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Sorry, een gebruiker met dat e-mailadres bestaat al." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Uw e-mailadres is geverifieerd. U kunt nu inloggen, uw profiel bewerken, en afbeeldingen toevoegen!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "De verificatie sleutel of gebruikers-ID is onjuist" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Je moet ingelogd zijn, anders weten we niet waar we de e-mail naartoe moeten sturen!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Je hebt je e-mailadres al geverifieerd!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Verificatie e-mail opnieuw opgestuurd." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Een e-mail met instructies om je wachtwoord te veranderen is verstuurd." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Email kon niet verstuurd worden omdat je gebruikersnaam inactief is of omdat je e-mailadres nog niet geverifieerd is." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Je kunt nu inloggen met je nieuwe wachtwoord." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titel" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Beschrijving van dit werk" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Voor opmaak kun je <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> gebruiken." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etiket" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Hou labels gescheiden met komma's." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Slug" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "De slug kan niet leeg zijn" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Het titelgedeelte van het adres van deze media. Normaal gesproken hoef je deze niet te veranderen." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licentie" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Bio" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Website" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Dit adres bevat fouten" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Oud wachtwoord" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Vul je oude wachtwoord in om te bewijzen dat dit jouw account is" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nieuw wachtwoord" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Er bestaat al een met die slug voor deze gebruiker." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "U bent de media van een andere gebruiker aan het aanpassen. Ga voorzichtig te werk." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "U bent een gebruikersprofiel aan het aanpassen. Ga voorzichtig te werk." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Profielaanpassingen opgeslagen" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Accountinstellingen opgeslagen" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Verkeerd wachtwoord" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Sorry, dat bestandstype wordt niet ondersteunt." + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Locatie" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Bekijken op <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Voeg toe" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Verkeerd bestandsformaat voor mediatype opgegeven." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Bestand" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "U moet een bestand aangeven." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Mooizo! Toegevoegd!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifieer je e-mailadres!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Inloggen" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Accountinstellingen aanpassen" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Mediaverwerkingspaneel" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Voeg media toe" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Nieuwste media" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Media te verwerken" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Geen media om te verwerken" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Deze toevoegingen konden niet verwerkt worden:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Voer je nieuwe wachtwoord in" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Wachtwoord opslaan" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Wachtwoord herstellen" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Stuur instructies" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Hoi %(username)s,\n\nOm je wachtwoord voor GNU MediaGoblin te veranderen, moet je dit adres in je webbrowser openen:\n\n%(verification_url)s\n\nAls je denkt dat dit niet klopt, kun je deze e-mail gewoon negeren." + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Inloggen is mislukt!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Heeft u nog geen account?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Maak er hier een!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Wachtwoord vergeten?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Maak een account aan!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Creëer" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Hallo %(username)s , open de volgende URL in uw webbrowser om uw GNU MediaGoblin account te activeren: %(verification_url)s " + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Uitgegeven onder de <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>-licentie. <a href=\"%(source_link)s\">Broncode</a> available." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Verkennen" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hoi, welkom op deze MediaGoblin website!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Deze website draait <a href=\"http://mediagoblin.org\">MediaGoblin</a>, een buitengewoon goed stuk software voor mediahosting." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Heb je er nog geen? Het is heel eenvoudig!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin logo" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Annuleren" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Wijzigingen opslaan" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Permanent verwijderen" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "%(media_title)s aanpassen" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "%(username)ss accountinstellingen aanpassen" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Het profiel aanpassen van %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Media met het label: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Origineel" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Sorry, dit geluidsfragment zal niet werken omdat\n»uw web-browser geen HTML5 ondersteunt⏎\n»audio." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "U kunt een moderne web-browser die \n\taudio kan afspelen vinden op <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Afbeelding voor %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Voeg media toe" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Pas aan" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Verwijderen" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Zeker weten dat je %(title)s wil verwijderen?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Media van %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Media van <a href=\"%(user_url)s\"> %(username)s </a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Blader door media van <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Geef een reactie" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Voeg dit bericht toe" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Hier kun je de status zien van de media die verwerkt worden." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profiel van %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Sorry, die gebruiker kon niet worden gevonden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Emailverificatie is nodig" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Bijna klaar! Je account moet nog geactiveerd worden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Een e-mail zou in een paar ogenblikken aan moeten komen met instructies hiertoe." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Zoniet:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Stuur de verificatie e-mail opnieuw op." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Iemand heeft een account met deze gebruikersnaam gemaakt, maar hij moet nog geactiveerd worden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Als u die persoon bent, maar de verificatie e-mail verloren hebt, kunt u <a href=\"%(login_url)s\">inloggen</a> en hem nogmaals verzenden." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Hier is een plekje om anderen over jezelf te vertellen." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Profiel aanpassen." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Deze gebruiker heeft zijn of haar profiel (nog) niet ingevuld." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Bekijk alle media van %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Dit is waar je nieuwe media zal verschijnen, maar het lijkt erop dat je nog niets heb toegevoegd." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Het lijkt erop dat er nog geen media is." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "feed icoon" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Alle rechten voorbehouden" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Nieuwer" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Ouder →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Ga naar pagina:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "nieuwer" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "ouder" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Getagged met" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Kon het afbeeldingsbestand niet lezen." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Oeps!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Voor opmaak kun je <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:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Oeps, je bericht was leeg." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Je bericht is geplaatst!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Je hebt deze media verwijderd." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Deze media was niet verwijderd omdat je niet hebt aangegeven dat je het zeker weet." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Je staat op het punt de media van iemand anders te verwijderen. Pas op." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..9cbd03b2 --- /dev/null +++ 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 new file mode 100644 index 00000000..6a11d5da --- /dev/null +++ b/mediagoblin/i18n/nn_NO/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# velmont <odin.omdal@gmail.com>, 2013 +# velmont <odin.omdal@gmail.com>, 2011-2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-31 15:40+0000\n" +"Last-Translator: velmont <odin.omdal@gmail.com>\n" +"Language-Team: Norwegian Nynorsk (Norway) (http://www.transifex.com/projects/p/mediagoblin/language/nn_NO/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: nn_NO\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Brukarnamn" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Passord" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Epost" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Brukarnamn eller epost" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Brukarnamn eller epost" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Ugyldig brukarnamn eller passord." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Dette feltet tek ikkje epostadresser." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Dette feltet krev ei epostadresse." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Registrering er slege av. Orsak." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Ein konto med dette brukarnamnet finst allereide." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Ein brukar med den epostadressa finst allereie." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Kontoen din er stadfesta. Du kan no logga inn, endra profilen din og lasta opp filer." + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Stadfestingsnykelen eller brukar-ID-en din er feil." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Du må vera innlogga, slik me veit kven som skal ha eposten." + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Du har allereie verifisiert epostadressa." + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Send ein ny stadfestingsepost." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Dersom denne epostadressa er registrert, har ein epost med instruksjonar for å endra passord vorte sendt til han." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Fann ingen med det brukarnamnet." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Sender epost med instruksjonar for å endra passordet ditt." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Kunne ikkje senda epost. Brukarnamnet ditt er inaktivt eller uverifisert." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Du kan no logga inn med det nye passordet ditt." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Tittel" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Skildring av verk" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Du kan bruka <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> til formattering." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Merkelappar" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Separer merkelappar med komma." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Nettnamn" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Nettnamnet kan ikkje vera tomt" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Nettnamnet (adressetittel) for verket di. Trengst ikkje endrast." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Lisens" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Presentasjon" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Heimeside" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Adressa inneheld feil" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Lisens-val" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Dette vil vera standardvalet ditt for lisens." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Send meg epost når andre kjem med innspel på verka mine." + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Tittelen kjan ikkje vera tom" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Forklaringa til denne samlinga" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Tittel-delen av denne samlinga si adresse. Du treng normalt sett ikkje endra denne." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Gamalt passort" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Skriv inn det gamle passordet ditt for å stadfesta at du eig denne kontoen." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nytt passord" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Eit innlegg med denne adressetittelen finst allereie." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Trå varsamt, du endrar nokon andre sine verk." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "La til vedlegg %s." + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Du kan berre enda din eigen profil." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Trå varsamt, du endrar nokon andre sin profil." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Lagra endring av profilen" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Lagra kontoinstellingar" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Du må stadfesta slettinga av kontoen din." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Du har allereie ei samling med namn «%s»." + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Ei samling med den nettadressa finst allereie for denne brukaren." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Du endrar ein annan brukar si samling. Trå varsamt." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Feil passord" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Endra passord" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Cannot link theme... no theme set\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "No asset directory for this theme\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "However, old link directory symlink found; removed.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "Kunne ikkje lenkja «%s»: %s eksisterer og er ikkje ei symlenkje\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "Hopper over «%s»: allereie satt opp.\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "Gamal lenkje funnen for «%s»; fjernar.\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "Finn ikkje CSRF-cookien. Dette er truleg grunna ein cookie-blokkar.<br/>\nSjå til at du tillet cookies for dette domenet." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Orsak, stør ikkje den filtypen :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "klarte ikkje køyra unoconv, sjekk logg-fil" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Skjedde noko gale med video transkodinga" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Stad" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Sjå på <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Godta" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Nekt" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Namn" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Namnet til OAuth-klienten" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Forklaring" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Dette vil vera synleg for brukarar som godtek applikasjonen din til å autentisera dei." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Type" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Confidential</strong> - Konfidensielt, på engelsk: The client can\n make requests to the GNU MediaGoblin instance that can not be\n intercepted by the user agent (e.g. server-side client).<br />\n<strong>Public</strong> - Open, på engelsk: The client can't make confidential\n requests to the GNU MediaGoblin instance (e.g. client-side\n JavaScript client)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Omdirigering URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "Omdirigerings-URI-en for programmene. Denne feltet <strong>krevst</strong> for opne (public) klientar." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Dette feltet krevst for opne (public) klientar" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Klienten {0} er registrert." + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "OAuth klient-tilkoplingar" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Dine OAuth-klientar" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Legg til" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Ugyldig fil for medietypen." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Fil" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Du må velja ei fil." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Johoo! Opplasta!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "La til samlinga «%s»." + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifiser epostadressa di." + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "Logg ut" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Logg inn" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "<a href=\"%(user_url)s\">%(user_name)s</a> sin konto" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Endra kontoinstellingar" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Verkprosesseringspanel" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Logg ut" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Legg til verk" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Lag ny samling" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Bilete av stressa goblin" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Nyaste verk" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Hald oppsyn med statusen for prosessering av verka dine her." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Verk under prosessesering" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Ingen verk vert prosessert" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Klarte ikkje prosessera desse opplasta filene:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Ingen feila filer." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Dei siste ti opplastningane" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Ingenting prossesert, enno." + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Lag nytt passord" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Set passord" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Gløymd passordet?" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Send instruksjonar" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Hei %(username)s,\n\nfor å endra MediaGoblin-passordet ditt, opna fylgjande URL i ein netlesar:\n\n <%(verification_url)s>\n\nDersom du mistenkjer dette er eit misstak, ignorer eposten og hald fram med å vera ein glad goblin!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Innlogging feila" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Har du ingen konto?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Lag ein!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Gløymd passordet?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Lag ein konto." + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Opprett" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Hei %(username)s,\n\nopna fylgjande netadresse i netlesaren din for å aktivera kontoen din:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Drive av <a href=\"http://mediagoblin.org\" title='Version %(version)s'>MediaGoblin</a>, eit <a href=\"http://gnu.org/\">GNU</a>-prosjekt." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Lisensiert med <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Kjeldekode</a> er tilgjengeleg." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Utforsk" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Heihei, velkomen til denne MediaGoblin-sida." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Denne sida køyrer <a href=\"http://mediagoblin.org\">MediaGoblin</a>, eit superbra program for å visa fram dine kreative verk." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Vil du leggja til eigne verk og innpel, so må du logga inn." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Har du ikkje ein enno? Det er enkelt!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Opprett ein konto på denne sida</a>\n eller\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set opp din eigen MediaGoblin-server</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Endrar vedlegg for %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Vedlegg" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Legg ved vedlegg" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Bryt av" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Lagra" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Endrar passordet til %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Lagra" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Sletta brukar '%(user_name)s' og alle relaterte verk og kommentarar?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Ja, slett kontoen min" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Slett permanent" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Endrar %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Endrar kontoinnstellingane til %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Endra passordet ditt." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Slett kontoen min" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Endrar %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Endrar profilen til %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Verk merka med: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Last ned" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Opphavleg" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Orsak, dette lydklippet fungerer ikkje fordi netlesaren din ikkje stør HTML5-lyd." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Du kan skaffa ein moderne netlesar som kan spela av dette lydklippet hjå <a href=\"http://opera.com/download\">http://opera.com/download</a>." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Opphavleg fil" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM-fil (Vorbis-kodek)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Bilete for %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF-fil" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Slå av/på rotering" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspektiv" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Front" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Topp" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Side" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Last ned modell" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Filformat" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Objekthøgd" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Orsak, denne videoen fungerer ikkje\nfordi netlesaren din ikkje stør\nHTML5 video." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Du kan skaffa deg ein moderne netlesar som kan spela denne videoen hjå <a href=http://opera.com>http://opera.com</a> eller <a href=\"http://getfirefox.com\">http://getfirefox.com</a>." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM fil (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Legg til ei samling" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Legg til verk" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (%(username)s si samling)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s av <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Endra" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Slett" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Vil du verkeleg sletta %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Fjerna %(media_title)s frå %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Fjern" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "%(username)s sine samlingar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> sine samlingar" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Hei %(username)s,\n%(comment_author)s kommenterte innlegget ditt (%(comment_url)s) hjå %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Verka til %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> sine verk merka <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> sine verk" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Ser på <a href=\"%(user_url)s\">%(username)s</a> sine verk" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Legg att innspel" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Legg til dette innspelet" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "%(formatted_time)s sidan" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Lagt til" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Oppretta" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Putt «%(media_title)s» i samling" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Legg til ei ny samling" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Sjå status for prosessering av verka dine her." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Dine ti siste opplastningar." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)s sin profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Fann ingen slik brukar" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Epostverifisering trengst." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Nesten ferdig. Du treng berre aktivera kontoen." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Ein epost med instruksjonar kjem straks." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "I tilfelle det ikkje skjer:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Send ein ny epost" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Dette brukarnamnet finst allereie, men det er ikkje aktivert." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Viss dette er deg, kan du <a href=\"%(login_url)s\">logga inn</a> for å få tilsendt ny epost med stadfestingslenkje." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Her kan du fortelja om deg sjølv." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Endra profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Brukaren har ikkje fylt ut profilen sin (enno)." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Sjå gjennom samlingar" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Sjå alle %(username)s sine verk" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Her kjem verka dine." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Ser ikkje ut til at det finst nokon verk her nett no." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(fjern)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "I samling" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Putt i samling" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr " " + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom-kjelde" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Alle rettar reservert" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Nyare" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Eldre →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Gå til side:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "nyare" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "eldre" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Merka med" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Klarte ikkje lesa biletefila." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Oops." + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Noko gjekk gale" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Ulovleg operasjon" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Orsak Dave, eg kan ikkje la deg gjera det!<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/tools/timesince.py:62 +msgid "year" +msgstr "år" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "månad" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "veke" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "dag" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "time" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "minutt" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Innspel" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Du kan bruka <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> til formatterring." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Eg er sikker eg vil sletta dette" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Eg er sikker på at eg vil fjerna dette frå samlinga" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Samling" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Vel --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Legg ved eit notat" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "kom med innspel på innlegget ditt" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Innspel er avslege" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Vops, innspelet ditt var tomt." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Innspelet ditt er lagt til." + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Sjekk filene dine og prøv omatt." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Du må velja eller laga ei samling" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "«%s» er allereie i samling «%s»" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "«%s» lagt til samling «%s»" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Du sletta verket." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Sletta ikkje verket." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Du er i ferd med å sletta ein annan brukar sine verk. Trå varsamt." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Du fjerna fila frå samlinga." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Fila var ikkje fjerna fordi du ikkje var sikker." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Du er i ferd med å fjerna ei fil frå ein annan brukar si samling. Trå varsamt." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Samlinga «%s» sletta" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Sletta ikkje samlinga." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Du er i ferd med å sletta ein annan brukar si samling. Trå varsamt." diff --git a/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..8b318329 --- /dev/null +++ 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 new file mode 100644 index 00000000..78ab219a --- /dev/null +++ b/mediagoblin/i18n/pl/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Daniel Koć <kocio@aster.pl>, 2012 +# Sergiusz Pawlowicz <transifex@pawlowicz.name>, 2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-28 13:51+0000\n" +"Last-Translator: Sergiusz Pawlowicz <transifex@pawlowicz.name>\n" +"Language-Team: Polish (http://www.transifex.com/projects/p/mediagoblin/language/pl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: pl\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Użytkownik" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Hasło" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Adres e-mail" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Nazwa konta lub adres poczty elektronicznej" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Użytkownik lub adres e-mail" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Nieprawidłowa nazwa konta albo niewłaściwy adres poczty elektronicznej." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Niniejsze pole nie jest przeznaczone na adres poczty elektronicznej." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Niniejsze pole wymaga podania adresu poczty elektronicznej." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Niestety rejestracja w tym serwisie jest wyłączona." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Niestety użytkownik o takiej nazwie już istnieje." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Niestety użytkownik z tym adresem e-mail już istnieje." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Twój adres e-mail został zweryfikowany. Możesz się teraz zalogować, wypełnić opis swojego profilu i wysyłać grafiki!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Nieprawidłowy klucz weryfikacji lub identyfikator użytkownika." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Musisz się zalogować żebyśmy wiedzieli do kogo wysłać e-mail!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Twój adres e-mail już został zweryfikowany!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Wyślij ponownie e-mail weryfikujący." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Jeśli ten adres poczty elektronicznej istnieje (uwzględniając wielkość liter!), wysłano na niego list z instrukcją, w jaki sposób możesz zmienić swoje hasło." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Nie potrafię znaleźć nikogo o tej nazwie użytkownika." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Wysłano e-mail z instrukcjami jak zmienić hasło." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Nie udało się wysłać e-maila w celu odzyskania hasła, ponieważ twoje konto jest nieaktywne lub twój adres e-mail nie został zweryfikowany." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Teraz możesz się zalogować używając nowego hasła." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Tytuł" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Opis tej pracy" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Możesz formatować tekst za pomocą składni \n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Znaczniki" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Rozdzielaj znaczniki przecinkami." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Slug" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Slug nie może być pusty" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Fragment adresu mediów zawierający tytuł. Zwykle nie ma potrzeby aby go zmieniać." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licencja" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Biogram" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Strona internetowa" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Ten adres zawiera błędy" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Ulubiona licencja" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "To będzie twoja domyślna licencja dla wgrywanych mediów." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Powiadamiaj mnie e-mailem o komentarzach do moich mediów" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Tytuł nie może być pusty" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Opis tej kolekcji" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Część adresu zawierająca tytuł. Zwykle nie musisz tego zmieniać." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Stare hasło" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Wprowadź swoje stare hasło aby udowodnić, że to twoje konto." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nowe hasło" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Adres z tym slugiem dla tego użytkownika już istnieje." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Edytujesz media innego użytkownika. Zachowaj ostrożność." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Dodałeś załącznik %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Masz możliwość edycji tylko własnego profilu." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Edytujesz profil innego użytkownika. Zachowaj ostrożność." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Zapisano zmiany profilu" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Zapisano ustawienia konta" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Musisz potwierdzić, że chcesz skasować swoje konto." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Kolekcja \"%s\" już istnieje!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Kolekcja tego użytkownika z takim slugiem już istnieje." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Edytujesz kolekcję innego użytkownika. Zachowaj ostrożność." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Nieprawidłowe hasło" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Twoje hasło zostało zmienione" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Nie można podlinkować motywu... nie wybrano motywu\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Brak katalogu danych dla tego motywu\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Znaleziono stary odnośnik symboliczny do katalogu; usunięto.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "Nie mogę zrobić odnośnika \"%s\": %s istnieje i nie jest odnośnikiem\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "Opuszczam \"%s\"; już jest gotowe.\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "Znaleziono stary odnośnik dla \"%s\"; usuwam.\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "Ciasteczko CSFR nie jest dostępne. Najprawdopodobniej stosujesz jakąś formę blokowania ciasteczek.<br/>Upewnij się, że nasz serwer może zakładać ciasteczka w twojej przeglądarce." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "NIestety, nie obsługujemy tego typu plików :-(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "nie dało się uruchomić unoconv, sprawdź log" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Konwersja wideo nie powiodła się" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Położenie" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Zobacz na <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Zezwól" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Odrzuć" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nazwa" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Nazwa klienta OAuth" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Opis" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "To będzie widoczne dla użytkowników, pozwalając⏎ twojej aplikacji uwierzytelniać się jako oni." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Typ" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Confidential</strong> - Klient może wysyłać żądania\n do instancji GNU MediaGoblin, która nie może zostać\n przechwycona przez agenta (np. klient po stronie serwera).<br />\n <strong>Public</strong> - Klient nie może wysyłać poufnych\n żądań do instakcji GNU MediaGoblin (np. skrypt JavaScript\n po stronie klienta)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Przekierowanie URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "Przekierowanie URI dla aplikacji, to pole\n jest <strong>wymagane</strong> dla publicznych klientów." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "To pole jest wymagane dla klientów publicznych" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Klient {0} został zarejestrowany!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "Połączenia do OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Twoi klienci OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Dodaj" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Niewłaściwy plik dla tego rodzaju mediów." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Plik" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Musisz podać plik." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Hura! Wysłano!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Kolekcja \"%s\" została dodana!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Zweryfikuj swój adres e-mail!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "wyloguj się" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Zaloguj się" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "konto <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Zmień ustawienia konta" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Panel przetwarzania mediów" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Wyloguj się" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Dodaj media" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Utwórz nową kolekcję" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Grafika zestresowanego goblina" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Najnowsze media" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Tu możesz śledzić stan przetwarzania mediów na tym serwerze." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Przetwarzane media" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Żadne media nie są obecnie przetwarzane" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "NIe udało się przesłać tych plików:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Brak nieprzetworzonych wpisów!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Ostatnie 10 udanych wysyłek" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Na razie nie przetworzono żadnego wpisu!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Podaj swoje nowe hasło" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Podaj hasło" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Odtwórz hasło" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Wyślij instrukcje" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Cześć %(username)s,\n\naby zmienić twoje hasło dla GNU MediaGoblin, otwórz następującą stronę w swojej przeglądarce:\n\n%(verification_url)s\n\nJeśli sądzisz, że to pomyłka, po prostu zignoruj tę wiadomość i bądź nadal szczęśliwym goblinem!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Logowanie nie powiodło się!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Nie masz jeszcze konta?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Utwórz je tutaj!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Zapomniałeś hasła?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Utwórz konto!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Utwórz" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Cześć %(username)s,\n\naby aktywować twoje konto GNU MediaGoblin, otwórz następującą stronę w swojej przeglądarce:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Napędzane przez oprogramowanie <a href=\"http://mediagoblin.org/\" title='w wersji %(version)s'>MediaGoblin</a>, będące częścią projektu <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Opublikowane na licencji <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. Dostępny jest <a href=\"%(source_link)s\">kod źródłowy</a>." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Odkrywaj" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Cześć, witaj na stronie MediaGoblin!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Ten serwis działa w oparciu o <a href=\"http://mediagoblin.org\">MediaGoblin</a>, świetne oprogramowanie do publikowania mediów." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Aby dodawać swoje pliki, komentować i wykonywać inne czynności, możesz się zalogować na swoje konto MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Jeszcze go nie masz? To proste!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Załóż konto na tym serwerze</a>\n albo\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Uruchom MediaGoblin na swoim własnym serwerze</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Logo MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Edycja załączników do %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Załączniki" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Dodaj załącznik" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Anuluj" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Zapisz zmiany" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Zmieniam hasło użytkownika %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Zachowaj" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Czy naprawdę skasować użytkownika '%(user_name)s' oraz usunąć wszystkie jego pliki i komentarze?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Tak, naprawdę chcę skasować swoje konto" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Usuń na stałe" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Edytowanie %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Zmiana ustawień konta %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Zmień swoje hasło." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Usuń moje konto" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Edycja %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Edycja profilu %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Media ze znacznikami: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Pobierz" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Oryginał" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Niestety, ten plik dźwiękowy nie zostanie odtworzony, \n\tponieważ ta przeglądarka nie obsługuje znaczników \n\tdźwięku w HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Proszę pobrać przeglądarkę, która obsługuje \n\tdźwięk w HTML5, pod adresem <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Oryginalny plik" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "plik WebM (kodek Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Grafika dla %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "Plik PDF" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Obróć" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspektywa" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Początek" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Góra" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Krawędź" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Pobierz model" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Format pliku" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Wysokość obiektu" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Niestety ten materiał nie będzie widoczny⏎, ponieważ twoja przeglądarka nie⏎ osbługuje formatu HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Możesz pobrać porządną przeglądarkę, która jest w stanie odtworzyć ten materiał filmowy, ze strony <a href=\"http://getfirefox.com/\">⏎ http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "plik WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Dodaj kolekcję" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Dodaj swoje media" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (kolekcja użytkownika %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s użytkownika <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Edytuj" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Usuń" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Na pewno usunąć %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Na pewno usunąć %(media_title)s z %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Usuń" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "kolekcja użytkownika %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "kolekcje użytkownika <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Witaj %(username)s,\n%(comment_author)s skomentował twój wpis (%(comment_url)s) na %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Media użytkownika %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "pliki użytkownika <a href=\"%(user_url)s\">%(username)s</a> z tagiem <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "media użytkownika <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Przeglądanie mediów użytkownika <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Dodaj komentarz" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Dodaj komentarz" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "%(formatted_time)s temu" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Dodano" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Utworzono" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Dodaj “%(media_title)s” do kolekcji" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Dodaj nową kolekcję" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Tutaj możesz śledzić stan mediów przesyłanych do twojej galerii." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Ostatnie 10 twoich udanych wysyłek" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profil użytkownika %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Niestety, nie znaleziono takiego użytkownika." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Wymagana weryfikacja adresu e-mail." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Prawie gotowe! Twoje konto oczekuje na aktywację." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Za kilka chwil powinieneś otrzymać e-mail z instrukcjami jak to zrobić." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Jeśli nie nadejdzie:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Wyślij ponownie e-mail weryfikujący" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Ktoś zarejestrował konto o tej nazwie, ale nadal oczekuje ono na aktywację." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Jeśli jesteś tą osobą, ale zgubiłeś swój e-mail weryfikujący, to możesz się <a href=\"%(login_url)s\">zalogować</a> i wysłać go ponownie." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "W tym miejscu można się przedstawić innym." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Edytuj profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Ten użytkownik nie wypełnił (jeszcze) opisu swojego profilu." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Przeglądaj kolekcje" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Zobacz wszystkie media użytkownika %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Tu będą widoczne twoje media, ale na razie niczego tu jeszcze nie ma." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Tu nie ma jeszcze żadnych mediów..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(usuń)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Znajduje się w kolekcji " + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Dodaj do kolekcji" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "ikona kanału" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Kanał Atom" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Wszystkie prawa zastrzeżone" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Nowsze" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Starsze →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Idź do strony:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "nowsze" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "starsze" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Znaczniki:" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Nie udało się odczytać pliku grafiki." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Ups!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Wystąpił błąd" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Operacja niedozwolona" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Misiaczku, nie możesz tego uczynić!</p><p>Próbowałeś wykonać działanie, do którego nie masz uprawnień. Czy naprawdę chciałeś skasować znowu wszystkie konta?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Wygląda na to, że nic tutaj nie ma!</p><p>Jeśli jesteś pewny, że adres jest prawidłowy, być może strona została skasowana lub przeniesiona." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "rok" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "miesiąc" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "tydzień" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "dzień" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "godzina" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "minuta" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Komentarz" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Możesz formatować przy pomocy składni <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Na pewno chcę to usunąć" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Na pewno chcę usunąć ten element z kolekcji" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Kolekcja" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- wybierz --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Dodaj notatkę" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "komentarze do twojego wpisu" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Komentowanie jest wyłączone." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Ups, twój komentarz nie zawierał treści." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Twój komentarz został opublikowany!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Sprawdź swoje wpisy i spróbuj ponownie." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Musisz wybrać lub dodać kolekcję" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" już obecne w kolekcji \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" dodano do kolekcji \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Media zostały usunięte." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Media nie zostały usunięte ponieważ nie potwierdziłeś, że jesteś pewien." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Za chwilę usuniesz media innego użytkownika. Zachowaj ostrożność." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Element został usunięty z kolekcji." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Ten element nie został usunięty, ponieważ nie zaznaczono, że jesteś pewien." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Zamierzasz usunąć element z kolekcji innego użytkownika. Zachowaj ostrożność." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Usunięto kolekcję \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Ta kolekcja nie została usunięta, ponieważ nie zaznaczono, że jesteś pewien." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Zamierzasz usunąć kolekcję innego użytkownika. Zachowaj ostrożność." diff --git a/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..5e83a7f2 --- /dev/null +++ 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 new file mode 100644 index 00000000..fecb844c --- /dev/null +++ b/mediagoblin/i18n/pt_BR/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1256 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# osc <snd.noise@gmail.com>, 2013 +# Rafael Ferreira <rafael.f.f1@gmail.com>, 2013 +# osc <snd.noise@gmail.com>, 2011 +# ufa <ufa@technotroll.org>, 2011 +# Canopus <viniciussm@rocketmail.com>, 2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/mediagoblin/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Nome de Usuário" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Senha" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Endereço de email" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Nome de usuário ou email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Nome de usuário ou email inválido." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Este campo não aceita endereços de email." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Este campo requer um endereço de email." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Desculpa, o registro está desativado neste momento." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Desculpe, um usuário com este nome já existe." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Desculpe, um usuário com esse email já está cadastrado" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "O seu endereço de e-mail foi verificado. Você pode agora fazer login, editar seu perfil, e enviar imagens!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "A chave de verificação ou nome usuário estão incorretos." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Você precisa entrar primeiro para sabermos para quem mandar o email!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Você já verificou seu email!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "O email de verificação foi enviado novamente." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Se esse endereço de email (sensível a maiúsculo/minúsculo!) estiver registrado, um email será enviado com instruções para alterar sua senha." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Não foi possível encontrar alguém com esse nome de usuário." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Um email foi enviado com instruções para trocar sua senha." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Não foi possível enviar o email de recuperação de senha, pois seu nome de usuário está inativo ou o email da sua conta não foi confirmado." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Agora você pode entrar usando sua nova senha." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Título" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Descrição desse trabalho" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Você pode usar\n<a href=\"http://daringfireball.net/projects/markdown/basics\">\nMarkdown</a> para formatação." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etiquetas" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Separe as etiquetas com vírgulas." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Arquivo" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "O arquivo não pode estar vazio" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "A parte do título do endereço dessa mídia. Geralmente você não precisa mudar isso." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licença" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Biografia" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Website" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Este endereço contém erros" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Licença preferida" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Esta será sua licença padrão nos formulários de envio." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Me enviar um email quando outras pessoas comentarem em minhas mídias" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "O título não pode ficar vazio" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Descrição desta coleção" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "A parte do título do endereço dessa coleção. Geralmente você não precisa mudar isso." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Senha antiga" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Digite sua senha antiga para provar que esta conta é sua." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nova senha" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Uma entrada com esse arquivo já existe para esse usuário" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Você está editando a mídia de outro usuário. Tenha cuidado." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Você adicionou o anexo %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Você só pode editar o seu próprio perfil." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Você está editando um perfil de usuário. Tenha cuidado." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "As mudanças no perfil foram salvas" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "As mudanças na conta foram salvas" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Você precisa confirmar a exclusão da sua conta." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Você já tem uma coleção chamada \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Já existe uma coleção com este arquivo para este usuário." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Você está editando a coleção de um outro usuário. Prossiga com cuidado." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Senha errada" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Não é possível fazer link de tema... nenhum tema definido\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "Cookie CSFR não está presente. Isso é provavelmente o resultado de um bloqueador de cookies ou algo do tipo.<br/>Tenha certeza de autorizar este domínio a configurar cookies." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Desculpe, não tenho suporte a este tipo de arquivo :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Conversão do vídeo falhou" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Localização" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Ver no <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Permitir" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Negar" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nome" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "O nome do cliente OAuth" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Descrição" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Tipo" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Redirecionar URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Este campo é necessário para clientes públicos" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "O cliente {0} foi registrado!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Seus clientes OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Adicionar" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Arquivo inválido para esse tipo de mídia" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Arquivo" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Você deve fornecer um arquivo." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Eba! Enviado!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Coleção \"%s\" adicionada!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifique seu email!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "sair" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Entrar" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Conta de <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Mudar configurações da conta" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Painel de processamento de mídia" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Sair" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Adicionar mídia" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Criar nova coleção" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Imagem do goblin se estressando" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Mídia mais recente" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Mídia em processo" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Nenhuma mídia em processo" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Esses envios não foram processados:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Nenhuma entrada falhou!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Últimos 10 envios bem sucedidos" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Ainda não há entradas processadas!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Defina a sua nova senha" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Definir senha" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Recuperar senha" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Mandar instruções" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Olá %(username)s,\n\npara alterar sua senha do GNU MediaGoblin, abra a seguinte URL\nno seu navegador web:\n\n%(verification_url)s\n\nSe você acha que isso é um erro, desconsidere esse email e continue sendo um goblin feliz" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Autenticação falhou" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Ainda não tem conta?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Crie uma aqui!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Esqueceu sua senha?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Criar uma conta!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Criar" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Olá %(username)s,\n\nPara ativar sua conta GNU MediaGoblin, visite este endereço no seu navegador:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Fornecido pelo <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, um projeto <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Lançado sob a <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Código fonte</a> disponível." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Explorar" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Olá, bem-vindo a este site MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Este site roda o <a href=\"http://mediagoblin.org\">MediaGoblin</a>, um programa excelente para hospedar, gerenciar e compartilhar mídia." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Para adicionar sua própria mídia, publicar comentários e mais outras coisas, você pode entrar com sua conta MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr " Ainda não tem uma conta? É facil!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Logo MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Editando os anexos de %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Anexos" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Adicionar anexo" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Cancelar" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Salvar mudanças" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Realmente deletar o usuário '%(user_name)s' e todas as mídias e comentários associados?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Sim, realmente deletar minha conta" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Deletar permanentemente" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Editando %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Alterando as configurações da conta de %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Deletar minha conta" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Editando %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Editando perfil de %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Etiquetas desta mídia: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Baixar" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Desculpe, este áudio não irá reproduzir porque \n »seu navegador não oferece suporte a áudio \n »HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Você pode obter um navegador moderno\n »capaz de reproduzir o áudio em <a href=\"http://getfirefox.com\">\n » http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Arquivo original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "Arquivo WebM (codec Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Imagem para %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Alternar Rotação" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspectiva" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Frente" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Cima" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Lado" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Baixar o modelo" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Formato de Arquivo" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Altura do Objeto" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Desculpe, este vídeo não irá reproduzir porque\n seu navegador não suporta vídeo\n HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Você pode obter um navegador moderno\n capaz de reproduzir este vídeo em <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "Arquivo WebM (640p, VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Adicionar uma coleção" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Adicionar sua mídia" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (Coleção de %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Editar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Apagar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Realmente apagar %(title)s ?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Realmente remover %(media_title)s de %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Apagar" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Coleções de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Coleções de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Olá %(username)s,\n %(comment_author)s comentou na sua publicação (%(comment_url)s) em %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Mídia de %(username)s's" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Mídias de <a href=\"%(user_url)s\">%(username)s</a> com a etiqueta <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Mídia de <a href=\"%(user_url)s\"> %(username)s </a> " + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Vendo mídia de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Adicionar um comentário" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Adicionar este comentário" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Adicionar \"%(media_title)s\" a uma coleção" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Adicionar uma nova coleção" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Você pode verificar como a mídia esta sendo processada para sua galeria aqui" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Seus últimos 10 envios bem sucedidos" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Perfil de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Desculpe, esse usuário não foi encontrado." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Verificação de email necessária" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Quase pronto! Sua conta ainda precisa ser ativada." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Um email deve chegar em instantes com instruções de como fazê-lo." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Caso contrário:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Reenviar email de verificação" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Alguém registrou uma conta com esse nome de usuário, mas ainda precisa ser ativada." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Se você é essa pessoa, mas você perdeu seu e-mail de verificação, você pode <a href=\"%(login_url)s\">efetuar login</a> e reenviá-la." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Aqui é o lugar onde você fala de si para os outros." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Editar perfil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Esse usuário não preencheu seu perfil (ainda)." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Ver coleções" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Ver todas as mídias de %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Aqui é onde sua mídia vai aparecer, mas parece que você não adicionou nada ainda." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Parece que ainda não há nenhuma mídia por aqui..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(apagar)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Adicionar a uma coleção" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "ícone feed" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Todos os direitos reservados" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Novos" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Antigos →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Ir a página:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "mais nova" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "mais antiga" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Etiquetas" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Não foi possível ler o arquivo de imagem." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Oops" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Um erro ocorreu" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Operação não permitida" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Me desculpe Dave, não posso deixar você fazer isso!</p><p>Você tentou executar uma função sem autorização. Por acaso estava novamente tentando deletar todas as contas de usuários?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Parece que não há uma página com este endereço. Desculpe!</p><p>Se você tem certeza que este endereço está correto, talvez a página que esteja procurando tenha sido movida ou deletada." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Comentário" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Você pode usar <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> para formatação." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Eu tenho certeza de que quero apagar isso" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Tenho certeza que quero remover este item da coleção" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Coleção" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Selecionar --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Incluir uma nota" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "comentou na sua publicação" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Ops, seu comentário estava vazio." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Seu comentário foi postado!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Por favor, verifique suas entradas e tente novamente." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Você deve selecionar ou adicionar uma coleção" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" já está na coleção \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" adicionado à coleção \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Você deletou a mídia." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "A mídia não foi apagada porque você não marcou que tinha certeza." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Você vai apagar uma mídia de outro usuário. Tenha cuidado." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Você deletou o item da coleção." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "O item não foi apagado porque você não marcou que tinha certeza." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Você está prestes a remover um item da coleção de um outro usuário. Prossiga com cuidado." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Você deletou a coleção \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "A coleção não foi apagada porque você não marcou que tinha certeza." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Você está prestes a deletar a coleção de um outro usuário. Prossiga com cuidado." diff --git a/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..8cfdf339 --- /dev/null +++ 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 new file mode 100644 index 00000000..af2d94d6 --- /dev/null +++ b/mediagoblin/i18n/ro/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# George Pop <gapop@hotmail.com>, 2011 +# George Pop <gapop@hotmail.com>, 2011-2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 20:40+0000\n" +"Last-Translator: George Pop <gapop@hotmail.com>\n" +"Language-Team: Romanian (http://www.transifex.com/projects/p/mediagoblin/language/ro/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ro\n" +"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Nume de utilizator" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Parolă" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Adresa de e-mail" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Numele de utilizator sau adresa de e-mail" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Numele de utilizator sau adresa de e-mail" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Nume de utilizator sau adresă de e-mail nevalidă." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Această rubrică nu este pentru adrese de e-mail." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Această rubrică trebuie completată cu o adresă de e-mail." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Ne pare rău, dar înscrierile sunt dezactivate pe acest server." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Ne pare rău, există deja un utilizator cu același nume." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Există deja un utilizator înregistrat cu această adresă de e-mail." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Adresa ta de e-mail a fost verificată. Poți să te autentifici, să îți completezi profilul și să trimiți imagini!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Cheie de verificare sau user ID incorect." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Trebuie să fii autentificat ca să știm cui să trimitem mesajul!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Adresa ta de e-mail a fost deja verificată!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "E-mail-ul de verificare a fost retrimis." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Dacă adresa de e-mail este în baza noastră de date, atunci se va trimite imediat un mesaj cu instrucțiuni pentru schimbarea parolei. Țineți cont de litere mari / litere mici la introducerea adresei!" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Nu există nimeni cu acest nume de utilizator." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "S-a trimis un e-mail cu instrucțiuni pentru schimbarea parolei." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "E-mailul pentru recuperarea parolei nu a putut fi trimis deoarece contul tău e inactiv sau adresa ta de e-mail nu a fost verificată." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Acum te poți autentifica cu noua parolă." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titlu" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Descrierea acestui fișier" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Poți folosi\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> pentru formatare." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Cuvinte-cheie" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Desparte cuvintele-cheie prin virgulă." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Identificator" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Identificatorul nu poate să lipsească" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Partea corespunzătoare titlului din adresa acestui fișier media. De regulă poate fi lăsată nemodificată." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licența" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Biografie" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Sit Web" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Această adresă prezintă erori" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Licența preferată" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Aceasta va fi licența implicită pe formularele de upload." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Trimite-mi un e-mail când alții comentează fișierele mele" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Titlul nu poate să fie gol" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Descriere pentru această colecție" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Partea din adresa acestei colecții care corespunde titlului. De regulă nu e necesar să faci o modificare." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Vechea parolă" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Introdu vechea parolă pentru a demonstra că ești titularul acestui cont." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Noua parolă" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Există deja un entry cu același identificator pentru acest utilizator." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Editezi fișierul unui alt utilizator. Se recomandă prudență." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Ai anexat %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Nu poți modifica decât propriul tău profil." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Editezi profilul unui utilizator. Se recomandă prudență." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Modificările profilului au fost salvate" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Setările pentru acest cont au fost salvate" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Trebuie să confirmi ștergerea contului tău." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Ai deja o colecție numită \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "O colecție cu același slug există deja pentru acest utilizator." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Lucrezi pe colecția unui alt utilizator. Se recomandă prudență." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Parolă incorectă" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Parola a fost schimbată cu succes" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Tema nu poate fi atașată... nu există o temă selectată\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Nu există un folder de elemente pentru această temă\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "A fost însă găsit un symlink către vechiul folder; s-a șters.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "Nu s-a putut crea link pentru \"%s\": %s există deja și nu este symlink\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "S-a omis \"%s\"; configurat deja.\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "Există deja un link pentru \"%s\"; va fi șters.\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "Lipsește cookie-ul CSRF. Probabil că blocați cookie-urile.<br/>Asigurați-vă că există permisiunea setării cookie-urilor pentru acest domeniu." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Scuze, nu recunosc acest tip de fișier :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "unoconv nu poate fi executat; verificați log-ul" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Transcodarea video a eșuat" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Locul" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Vezi pe <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Permite" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Refuză" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Nume" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Numele clientului OAuth" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Descriere" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Aceste informații vor fi vizibile pentru utilizatorii\n care permit aplicației tale să se autentifice în numele lor." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Tip" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Confidențial</strong> - Client poate\n trimite cereri către instanța GNU MediaGoblin care nu pot fi\n interceptate de către user agent (de ex., clientul de pe server).<br />\n <strong>Public</strong> - Clientul nu poate trimite cereri confidențiale\n către instanța GNU MediaGoblin (de ex., un client\n JavaScript)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "URI redirectare" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "URI-ul de redirectare pentru aplicații, această rubrică\n este <strong>obligatorie</strong> pentru clienții publici." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Această rubrică este obligatorie pentru clienții publici" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Clientul {0} a fost înregistrat!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "Conexiuni client OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Clienții tăi OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Adaugă" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Formatul fișierului nu corespunde cu tipul de media selectat." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Fișier" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Trebuie să selectezi un fișier." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Ura! Trimis!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Colecția \"%s\" a fost creată!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifică adresa de e-mail!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "Ieșire" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Autentificare" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Contul lui <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Modifică setările contului" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Panou de procesare media" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Ieșire" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Trimite fișier" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Creează colecție nouă" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Imagine cu un goblin stresat" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Cele mai recente fișiere" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Aici poți urmări starea fișierelor aflate în curs de procesare pe acest server." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Fișiere în curs de procesare" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Niciun fișier în curs de procesare" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Aceste fișiere nu au putut fi procesate:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Niciun entry cu erori!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Ultimele 10 upload-uri reușite" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Nu există încă niciun entry procesat!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Stabilește noua parolă" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Stabilește parola" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Recuperează parola" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Trimite instrucțiuni" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Bună, %(username)s\n\nPentru a schimba parola ta la GNU MediaGoblin, accesează adresa următoare:\n\n%(verification_url)s\n\nDacă ai primit acest mesaj din greșeală, ignoră-l și fii mai departe un goblin fericit!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Autentificare eșuată!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Nu ai un cont?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Creează-l aici!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Ai uitat parola?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Creează un cont!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Creează" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Bună, %(username)s,\n\npentru activarea contului tău la GNU MediaGoblin, accesează adresa următoare:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Construit cu <a href=\"http://mediagoblin.org/\" title='Versiunea %(version)s'>MediaGoblin</a>, un proiect <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Publicat sub licența <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Codul sursă</a> este disponibil." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Explorează" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Salut, bine ai venit pe acest site MediaGoblin!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Acest site folosește <a href=\"http://mediagoblin.org\">MediaGoblin</a>, un software excepțional pentru găzduirea fișierelor media." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Pentru a adăuga fișierele tale și pentru a comenta te poți autentifica cu contul tău MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Încă nu ai unul? E simplu!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Creați un cont pe acest site</a>\n sau\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Instalați MediaGoblin pe serverul dvs.</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "logo MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Editare anexe la %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Anexe" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Atașează" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Anulare" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Salvează modificările" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Se modifică parola pentru %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Salvează" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Sigur dorești ștergerea utilizatorului '%(user_name)s' și a fișierelor/comentariilor acestuia?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Da, doresc ștergerea contului meu" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Șterge definitiv" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Editare %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Se modifică setările contului pentru userul %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Modifică parolă." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Șterge contul meu" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Editare %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Editare profil %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Fișier etichetat cu cuvintele-cheie: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Download" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Ne pare rău, această înregistrare audio nu poate fi redată, deoarece \n\tbrowserul tău nu este compatibil cu funcția audio din HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Poți lua un browser modern \n\tcapabil să redea această înregistrare de la <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Fișierul original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "Fișier WebM (codec Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Imagine pentru %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "Fișier PDF" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Rotire" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspectivă" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Din față" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "De sus" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Lateral" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Descarcă modelul" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Formatul fișierului" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Înălțimea obiectului" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Ne pare rău, dar această înregistrare video nu va funcționa deoarece browser-ul dvs. nu este compatibil cu HTML5 video." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Puteți obține un browser Web modern care poate reda această înregistrare de la <a href=\"http://getfirefox.com\">http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "Fișier WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Creează o colecție" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Adaugă fișierele tale media" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (colecție a lui %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s de <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Editare" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Șterge" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Sigur dorești să ștergi %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Sigur dorești să ștergi %(media_title)s din %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Șterge" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Colecțiile utilizatorului %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Colecțiile utilizatorului <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Bună, %(username)s,\n%(comment_author)s a făcut un comentariu la postarea ta (%(comment_url)s) de la %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Fișierele lui %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Fișierele lui <a href=\"%(user_url)s\">%(username)s</a> cu cuvântul-cheie <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Fișierele media ale lui <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "<p>❖ Fișierele media ale lui <a href=\"%(user_url)s\">%(username)s</a></p>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Adaugă un comentariu" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Trimite acest comentariu" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "în urmă cu %(formatted_time)s" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Adăugat" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Creat" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Adaugă „%(media_title)s” la o colecție" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Creează o nouă colecție" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Aici poți urmări stadiul procesării fișierelor media din galeria ta." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Ultimele tale 10 upload-uri reușite" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profil %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Ne pare rău, nu am găsit utilizatorul căutat." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Este necesară verificarea adresei de e-mail" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Aproape gata! Mai trebuie doar să activezi contul." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Vei primi în scurt timp un e-mail cu instrucțiuni." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Dacă nu-l primești:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Retrimite mesajul de verificare" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Cineva a înregistrat un cont cu acest nume de utilizator, dar contul nu a fost încă activat." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Dacă tu ești persoana respectivă și nu mai ai e-mail-ul de verificare, poți să te <a href=\"%(login_url)s\">autentifici</a> pentru a-l retrimite." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Aici poți spune altora ceva despre tine." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Editare profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Acest utilizator nu și-a completat (încă) profilul." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Vizitează colecțiile" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Vezi toate fișierele media ale lui %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Aici vor apărea fișierele tale media, dar se pare că încă nu ai trimis nimic." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Nu pare să existe niciun fișier media deocamdată..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(șterge)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Din colecția" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Adaugă la o colecție" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "icon feed" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "feed Atom" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Toate drepturile rezervate" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Mai noi" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Mai vechi →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Salt la pagina:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "mai noi" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "mai vechi" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Etichetat cu cuvintele-cheie" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Fișierul cu imaginea nu a putut fi citit." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Hopa!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "S-a produs o eroare" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Operația nu este permisă" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Îmi pare rău, Dave, nu te pot lăsa să faci asta!</p><p>Ai încercat să faci o operație nepermisă. Ai încercat iar să ștergi toate conturile utilizatorilor?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Nu există nicio pagină la această adresă.</p><p>Dacă sunteți sigur că adresa este corectă, poate că pagina pe care o căutați a fost mutată sau ștearsă." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "anul" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "luna" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "săptămâna" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "ziua" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "ora" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "minutul" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Comentariu" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Poți folosi <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> pentru formatare." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Sunt sigur că doresc să șterg" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Sunt sigur(ă) că vreau să șterg acest articol din colecție" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Colecție" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Selectează --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Adaugă o notiță" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "a făcut un comentariu la postarea ta" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Comentariile sunt dezactivate." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Hopa, ai uitat să scrii comentariul." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Comentariul tău a fost trimis!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Verifică datele și încearcă din nou." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Trebuie să alegi sau să creezi o colecție" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" este deja în colecția \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" a fost adăugat la colecția \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Ai șters acest fișier" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Fișierul nu a fost șters deoarece nu ai confirmat că ești sigur." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Urmează să ștergi fișierele media ale unui alt utilizator. Se recomandă prudență." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Ai șters acest articol din colecție." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Articolul nu a fost șters pentru că nu ai confirmat că ești sigur(ă)." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Urmează să ștergi un articol din colecția unui alt utilizator. Se recomandă prudență." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Ai șters colecția \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Colecția nu a fost ștearsă pentru că nu ai confirmat că ești sigur(ă)." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Urmează să ștergi colecția unui alt utilizator. Se recomandă prudență." diff --git a/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..ed28ff43 --- /dev/null +++ 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 new file mode 100644 index 00000000..d0ff7bdd --- /dev/null +++ b/mediagoblin/i18n/ru/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# aleksejrs <deletesoftware@yandex.ru>, 2013 +# aleksejrs <deletesoftware@yandex.ru>, 2011-2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-01 21:08+0000\n" +"Last-Translator: aleksejrs <deletesoftware@yandex.ru>\n" +"Language-Team: Russian (http://www.transifex.com/projects/p/mediagoblin/language/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: ru\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Логин" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Пароль" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Адрес электронной почты" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Имя пользователя или адрес электронной почты" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Имя пользователя или адрес электронной почты" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Это поле не для адреса электронной почты." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Это поле — для адреса электронной почты." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Извините, на этом сайте регистрация запрещена." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Извините, пользователь с этим именем уже зарегистрирован." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Сожалеем, но на этот адрес электронной почты уже зарегистрирована другая учётная запись." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Ваш адрес электронной почты потвержден. Вы теперь можете войти и начать редактировать свой профиль и загружать новые изображения!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Неверный ключ проверки или идентификатор пользователя" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Вам надо представиться, чтобы мы знали, кому отправлять сообщение!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Вы уже потвердили свой адрес электронной почты!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Переслать сообщение с подтверждением аккаунта." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Если с этим адресом электронной почты (сравниваемым чувствительно к регистру символов!) есть учётная запись, то на него отправлено сообщение с указаниями о том, как сменить пароль." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Не найдено никого с таким именем пользователя." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Вам отправлено электронное письмо с инструкциями по смене пароля." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Мы не можем отправить сообщение для восстановления пароля, потому что ваша учётная запись неактивна, либо указанный в ней адрес электронной почты не был подтверждён." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Теперь вы можете войти, используя ваш новый пароль." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Название" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Описание этого произведения" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Для разметки можете использовать язык\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a>." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Метки" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "(через запятую)" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Отличительная часть адреса" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Отличительная часть адреса необходима" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Часть адреса этого файла, производная от его названия. Её обычно не требуется изменять." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Лицензия" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Биография" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Сайт" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Этот адрес содержит ошибки" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Предпочитаемая лицензия" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Она будет лицензией по умолчанию для ваших загрузок" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Уведомлять меня по e-mail о комментариях к моим файлам" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Название не может быть пустым" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Описание этой коллекции" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Отличительная часть адреса этой коллекции, основанная на названии. Обычно не нужно её изменять." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Старый пароль" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Введите свой старый пароль в качестве доказательства, что это ваша учётная запись." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Новый пароль" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "У этого пользователя уже есть файл с такой отличительной частью адреса." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Вы редактируете файлы другого пользователя. Будьте осторожны." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Вы добавили сопутствующий файл %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Вы можете редактировать только свой собственный профиль." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Вы редактируете профиль пользователя. Будьте осторожны." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Изменения профиля сохранены" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Настройки учётной записи записаны" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Вам нужно подтвердить, что вы хотите удалить свою учётную запись." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "У вас уже есть коллекция с названием «%s»!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "У этого пользователя уже есть коллекция с такой отличительной частью адреса." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Вы редактируете коллекцию другого пользователя. Будьте осторожны." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Неправильный пароль" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Ваш пароль сменён успешно" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Невозможно привязать тему… не выбрано существующей темы\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "У этой темы отсутствует каталог с элементами оформления\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Однако найдена (и удалена) старая символическая ссылка на каталог.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Увы, я не поддерживаю этот тип файлов :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Перекодировка видео не удалась" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "На карте" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Посмотреть на <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Описание" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Его увидят пользователи, разрешающие вашему приложению действовать от их имени." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Тип" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Клиент {0} зарегистрирован!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Добавить" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Неправильный формат файла." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Файл" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Вы должны загрузить файл." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Ура! Файл загружен!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Коллекция «%s» добавлена!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Подтвердите ваш адрес электронной почты!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "завершение сеанса" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Войти" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Учётная запись <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Изменить настройки учётной записи" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Панель обработки файлов" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Завершение сеанса" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Добавить файлы" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Создать новую коллекцию" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Изображение нервничающего гоблина" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Самые новые файлы" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Здесь вы можете следить за состоянием обработки файлов для данного сайта." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Обработка файлов в процессе" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Нету файлов для обработки" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Обработка этих файлов вызвала ошибку:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Неудавшихся задач нет!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Последние 10 удавшихся загрузок" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Выполненных задач пока нет!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Введите свой новый пароль" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Установить пароль" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Сброс пароля" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Отправить инструкцию" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Привет, %(username)s,\n\nчтобы сменить свой пароль от GNU MediaGoblin, откройте\nследующий URL вашим веб‐браузером:\n\n%(verification_url)s\n\nЕсли вы думаете, что это какая‐то ошибка, то игнорируйте\nэто сообщение и продолжайте быть счастливым гоблином!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Авторизация неуспешна!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Ещё нету аккаунта?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Создайте здесь!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Забыли свой пароль?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Создать аккаунт!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Создать" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Привет, %(username)s!\n\nЧтобы активировать свой аккаунт в GNU MediaGoblin, откройте в своём веб‐браузере следующую ссылку:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Работает на <a href=\"http://mediagoblin.org/\" title='Версии %(version)s'>MediaGoblin</a>, проекте <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Он опубликован на условиях <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. Доступны <a href=\"%(source_link)s\">исходные тексты</a>." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Смотреть" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Привет! Добро пожаловать на наш MediaGoblin’овый сайт!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Этот сайт работает на <a href=\"http://mediagoblin.org\">MediaGoblin</a>, необыкновенно замечательном ПО для хостинга мультимедийных файлов." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Для добавления собственных файлов, комментирования и т. п. вы можете представиться с помощью вашей MediaGoblin’овой учётной записи." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "У вас её ещё нет? Не проблема!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Символ MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Добавление сопутствующего файла для %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Сопутствующие файлы" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Добавить сопутствующий файл" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Отмена" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Сохранить изменения" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Смена пароля %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Сохранить" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "На самом деле удалить аккаунт «%(user_name)s» и все связанные файлы и комментарии?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Да, на самом деле удалить мою учётную запись" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Удалить безвозвратно" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Редактирование %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Настройка учётной записи %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Сменить пароль" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Удалить мою учётную запись" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Редактирование %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Редактирование профиля %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Файлы с меткой: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Скачать" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Оригинал" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Сожалеем, этот аудиоролик не проиграется, ⏎\n» потому что ваш браузер не поддерживает ⏎\n» аудио в соответствии со стандартом HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Вы можете скачать современный браузер, \n\tспособный проиграть это аудио, с <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Исходный файл" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM‐файл (кодек — Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Изображение «%(media_title)s»" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF-файл" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Перспектива" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Спереди" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Сверху" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Сбоку" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Скачать модель" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Формат файла" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Высота объекта" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Сожалеем, этот ролик не проиграется, ⏎\nпотому что ваш браузер не поддерживает ⏎\nвидео в соответствии со стандартом HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Вы можете скачать современный браузер, способный воспроизводить это видео, с <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM-файл (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Добавление коллекции" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Добавление ваших файлов" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (коллекция пользователя %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s пользователя <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Изменить" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Удалить" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Удалить %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "В самом деле исключить %(media_title)s из %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Исключить" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Коллекции %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Коллекции <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Привет, %(username)s.\nПользователь %(comment_author)s оставил комментарий к вашему файлу (%(comment_url)s) at %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Файлы %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Файлы <a href=\"%(user_url)s\">%(username)s</a> с меткой <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Файлы пользователя <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Просмотр файлов пользователя <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Добавить комментарий" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Добавить этот комментарий" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "%(formatted_time)s назад" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Добавлен" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Создан" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Добавление «%(media_title)s» в коллекцию" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Добавление новой коллекции" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Вы можете следить за статусом обработки файлов для вашей галереи здесь." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Ваши последние 10 удавшихся загрузок" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Профиль пользователя %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Извините, но такой пользователь не найден." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Нужно подтверждение почтового адреса" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Почти закончили! Теперь надо активировать ваш аккаунт." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Через пару мгновений на адрес вашей электронной почты должно прийти сообщение с дальнейшими инструкциями." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "А если нет, то:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Повторно отправить сообщение для подверждения адреса электронной почты" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Кто‐то создал аккаунт с этим именем, но его еще надо активировать." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Если это были вы, и если вы потеряли сообщение для подтверждения аккаунта, то вы можете <a href=\"%(login_url)s\">войти</a> и отправить его повторно." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Здесь вы можете рассказать о себе." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Редактировать профиль" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Этот пользователь не заполнил свой профайл (пока)." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Смотреть коллекции" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Смотреть все файлы %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Ваши файлы появятся здесь, когда вы их добавите." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Пока что тут файлов нет…" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(исключить)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "В коллекциях:" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Добавить в коллекцию" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "значок ленты" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "лента в формате Atom" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Все права сохранены" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Более новые" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Более старые →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Перейти к странице:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "более новые" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "более старые" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Метки" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Не удалось прочитать файл с изображением." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Ой!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Произошла ошибка" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Операция не позволяется" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "мин" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Комментировать" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Для разметки можете использовать язык <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Я уверен, что хочу удалить это" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Я уверен, что хочу исключить этот файл из коллекции" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Коллекция" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Выберите --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Примечание" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "оставил комментарий к вашему файлу" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Сожалеем: возможность комментирования отключена." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Ой, ваш комментарий был пуст." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Ваш комментарий размещён!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Пожалуйста, проверьте введённое и попробуйте ещё раз." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Необходимо выбрать или добавить коллекцию" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "«%s» — уже в коллекции «%s»" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "«%s» добавлено в коллекцию «%s»" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Вы удалили файл." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Файл не удалён, так как вы не подтвердили свою уверенность галочкой." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Вы на пороге удаления файла другого пользователя. Будьте осторожны." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Вы исключили файл из коллекции." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Файл не исключён из коллекции, так как вы не подтвердили своё намерение отметкой." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Вы на пороге исключения файла из коллекции другого пользователя. Будьте осторожны." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Вы удалили коллекцию «%s»" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Коллекция не удалена, так как вы не подтвердили своё намерение отметкой." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Вы на пороге удаления коллекции другого пользователя. Будьте осторожны." diff --git a/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..fd48a37f --- /dev/null +++ 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 new file mode 100644 index 00000000..e4d1bacc --- /dev/null +++ b/mediagoblin/i18n/sk/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1257 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# martin <zatroch.martin@gmail.com>, 2013 +# martin <zatroch.martin@gmail.com>, 2012-2013 +# Morten Juhl-Johansen Zölde-Fejér <morten@writtenandread.net>, 2012 +# Olle Jonsson <olle.jonsson@gmail.com>, 2012 +# ttrudslev <tanja.trudslev@gmail.com>, 2012 +# martin <zatroch.martin@gmail.com>, 2011-2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-28 07:47+0000\n" +"Last-Translator: martin <zatroch.martin@gmail.com>\n" +"Language-Team: Slovak (http://www.transifex.com/projects/p/mediagoblin/language/sk/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: sk\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Používateľské meno" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Heslo" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Email adresse" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Použivateľské meno alebo e-mail" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Používateľské meno alebo e-mailová adresa" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Nesprávne používateľské meno alebo e-mailová adresa." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Toto pole neakceptuje e-mailové adresy." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Toto pole vyžaduje e-mailovú adresu." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Prepáč, registrácia na danej inštancii nie je povolená." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Prepáč, rovnaké používateľské meno už existuje." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Prepáč, rovnaká e-mailová adresa už bola použitá na vytvorenie účtu." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Tvoja e-mailová adresa bola overená. Teraz sa môžeš prihlásiť, upravovať profil a vkladať výtvory!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Overovací kľúč, prípadne používateľské meno je nesprávne." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Je potrebné prihlásiť sa, aby sme vedeli kam máme e-mail zaslať!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Už máš overenú e-mailovú adresu!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Opätovne zaslať overovací e-mail." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Pokiaľ daná e-mailová adresa (citlivá na veľkosť písma!) je registrovaná, e-mail z inštrukciami pre zmenu tvojho hesla bol zaslaný." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "Nemožno nájsť nikoho z daným používateľským menom." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "E-mailová správa z inštrukciami na zmenu tvojho hesla bola zaslaná." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Nebolo možné zaslať e-mail na opätovné získanie zabudnutého hesla, nakoľko tvoje používateľské meno je neaktívne, prípadne e-mailová adresa nebola úspešne overená." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Už môžeš použiť nové heslo pri prihlasovaní." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titulok" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Popis výtvoru" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Môžeš využiť\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> pre formátovanie príspevku." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Štítky" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Oddeľ štítky pomocou čiarky." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Unikátna časť adresy" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Unikátna časť adresy nesmie byť prázdna" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Titulná časť adresy daného média. Zmena poľa nepovinná." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Licencia" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Bio" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Webstránka" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Daná adresa obsahuje chybu" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Preferencia licencie" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Nasledovná licencia bude použitá ako východzia pre všetky tvoje výtvory." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Zašli mi e-mail keď ostatní okomentujú môj výtvor" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Titulok nesmie byť prázdny." + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Popis danej kolekcie" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Titulná časť adresy danej kolekcie. Zmena poľa nepovinná." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Staré heslo" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Vlož svoje staré heslo na dôkaz toho, že vlastníš daný účet." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Nové heslo" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Položku s rovnakou unikátnou časťou adresy už niekde máš." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Upravuješ výtvory iného používateľa. Pristupuj zodpovedne. " + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Príloha %s pridaná!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Môžeš upravovať iba svoj vlastný profil." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Upravuješ profil iného používateľa. Pristupuj zodpovedne. " + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Zmeny v profile uložené" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Nastavenia účtu uložené" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Potrebuješ potvrdiť odstránenie svojho účtu." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Už máš kolekciu nazvanú ako \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Kolekcia s týmto štítkom už máš." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Upravuješ kolekciu iného používateľa. Pristupuj zodpovedne. " + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Nesprávne heslo" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Tvoje heslo bolo úspešne zmenené" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Nemožno pripojiť tému... téma nenastavená\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Žiadny priečinok položiek pre túto tému\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Odstránené; hoci bol pôvodný symbolický odkaz adresára nájdený.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "Nemožno odkazovať na \"%s\": %s existuje a nie je symbolickým odkazom\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "Preskakujem \"%s\"; opakovane nastavené.\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "Nájdený starý odkaz pre \"%s\"; odstraňujem.\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "CSRF \"cookie\" neprítomný. Toto vidíš najskôr ako výsledok blokovania \"cookie\" súborov a pod.<br/>Uisti sa, že máš povolené ukladanie \"cookies\" pre danú doménu." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Prepáč, nepodporujem tento typ súborov =(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "beh unoconv zlyhal, preskúmajte log záznam" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Konvertovanie videa zlyhalo" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Poloha" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Zobraziť na <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Povoliť" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Zakázať" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Meno" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Meno v rámci OAuth klienta" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Popis" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Toto bude viditeľné pre používateľov,\n ktorí sa môžu identifikovať cez tvoju aplikáciu." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Typ" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Dôverné</strong> - Klient môže\nvytvárať požiadavky na inštanciu GNU MediaGoblin, ktoré nemôžu byť\nzachytené používateľským agentom (napr. klient na strane servera).<br />\n<strong>Verejné</strong> - Klient nemôže vytvárať dôverné\npožiadavky voči GNU MediaGoblin inštancii (napr. JavaScript-ový klient\n na klientskej strane)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "Presmerovacie URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "Presmerovacie URI pre aplikácie, toto pole\nje <strong>požadované</strong> pre verejných klientov." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Dané pole je požadované pre verejných klientov." + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Klient {0} bol registrovaný!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "OAuth klientské spojenia" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Tvoji autorizovaní OAuth klienti" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Pridať" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Nesprávny typ súboru pre dané médium." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Súbor" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Musíš poskytnúť súbor." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Skvelé! Pridané!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "Kolekcia \"%s\" pridaná!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Over si e-mailovú adresu!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "odhlásiť sa" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Prihlásiť sa" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Účet používateľa <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Zmeniť nastavenia účtu" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Sekcia spracovania výtvorov" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Odhlásiť sa" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Pridať výtvor" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Vytvoriť novú kolekciu" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Obrázok hysterického goblina" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Aktuálne výtvory" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Tu môžeš sledovať stav médií spracovávaných na danej inštancii." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Výtvory sa spracúvajú" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Žiadne výtvory v procese spracovania" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Nasledovné nahratia neprešli spracovaním:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Žiadne zlyhané položky!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "Posledných 10 úspešných nahratí" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Zatiaľ žiadne spracované položky!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Nastav svoje nové heslo" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Nastav heslo" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Obnoviť heslo" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Zaslať inštrukcie" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Ahoj %(username)s,\n\npre zmenu svojho hesla k GNU MediaGoblin účtu, otvor nasledujúci odkaz vo svojom prehliadači:\n\n%(verification_url)s\n\nPokiaľ si myslíš, že došlo k omylu, tak jednoducho ignoruj túto správu a buď šťastným goblinom!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Prihlásenie zlyhalo!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Ešte stále nemáš účet?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Vytvor si jeden tu!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Zabudnuté heslo?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Opret en konto!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Vytvoriť" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Ahoj %(username)s,\n\npre aktiváciu tvojho GNU MediaGoblin účtu, otvor nasledujúci odkaz vo\nsvojom prehliadači:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Poháňa nás <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, súčasť projektu <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Uvoľnené pod <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a href=\"%(source_link)s\">Zdrojový kód</a> plne dostupný." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Preskúmať" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Ahoj, vitaj na tejto MediaGoblin stránke!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Táto stránka používa <a href=\"http://mediagoblin.org\">MediaGoblin</a>, výnimočne skvelý kus softvéru na hostovanie médií." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Pre pridanie vlastných výtvorov, komentárov a viac.. sa prihlás zo svojim MediaGoblin účtom." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Har du ikke en endnu? Det er let!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">Vytvoriť účet na tejto stránke</a>\n alebo\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Nastaviť MediaGoblin na vlastnom serveri</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin logo" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Úprava príloh pre %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Prílohy" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Pridať prílohu" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Zrušiť" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Uložiť zmeny" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "Mením heslo používateľa %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Uložiť" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Skutočne odstrániť používateľa '%(user_name)s' a všetky pridružené výtvory/komentáre?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Áno, skutočne odstrániť môj účet" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Odstráňiť permanentne" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Úprava %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Mením nastavenia účtu používateľa %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Zmeniť svoje heslo." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Odstrániť môj účet" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Úprava %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Úprava profilu, ktorý vlastní %(username)s " + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Výtvory označené ako: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Stiahnuť" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Originál" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Prepáč, tento zvukový súbor nepôjde prehrať, \n\tnakoľko tvoj prehliadač nepodporuje HTML5 \n\taudio." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Môžeš získať moderný prehliadač, ktorý\n\ttento zvuk hravo prehrá <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Originálny súbor" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM súbor (Vorbis kodek)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Obrázok pre %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF súbor" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Zapnúť rotáciu" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspektíva" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Čelo" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Vrch" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Strana" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Stiahnuť model" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Súborový formát" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Výška objektu" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Prepáč, tento video súbor nepôjde prehrať, \n\tnakoľko tvoj prehliadač nepodporuje HTML5 \n\tvideo." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Môžeš získať moderný prehliadač, ktorý\n\ttento video súbor hravo prehrá na <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM súbor (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Pridať kolekciu" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Pridaj svoj výtvor" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (kolekcia používateľa %(username)s) " + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s od <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Upraviť" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Odstrániť" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Skutočne odstrániť %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Skutočne odstrániť %(media_title)s z %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Odstrániť" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Kolekcie používateľa %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Kolekcie používateľa <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Ahoj %(username)s,\npoužívateľ %(comment_author)s okmentoval tvoj príspevok (%(comment_url)s) na %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Výtvory, ktoré vlastní %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Výtvory používateľa <a href=\"%(user_url)s\">%(username)s</a> zo štítkom <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Výtvory, ktoré vlastní <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Prehliadanie výtvorov od <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Pridať komentár" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Pridať tento komentár" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "pred %(formatted_time)s " + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Pridané" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Vytvorené" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Pridať “%(media_title)s” do kolekcie" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Pridať novú kolekciu" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Tu môžeš sledovať priebeh spracovania výtvorov pre svoju galériu." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "Tvojich 10 posledných úspešných nahratí" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profil, ktorý vlastní %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Prepáč, daný používateľ nenájdený." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Nutné overenie e-mailovej adresy" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Takmer hotovo! Ešte je potrebné aktivovať tvoj účet." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "E-mail z inštrukciami ako na to by ti mal doraziť každú chvíľu." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "V prípade, že nie:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Opätovne zaslať overovací e-mail." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Účet s týmto používateľským menom je už registrovaný, avšak ešte stále neaktívny." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Pokiaľ si to ty, ale už nemáš uloženú kópiu overovacej správy, tak sa môžeš <a href=\"%(login_url)s\">prihlásiť</a> a preposlať si ju." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Na tomto mieste môžeš povedať o sebe ostatným." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Upraviť profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Dotyčný používateľ (zatiaľ) nevyplnil svoj profil." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Prehliadať kolekcie" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Zobraziť všetky výtvory, ktoré vlastní %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Všetky tvoje výtvory sa objavia práve tu, zatiaľ však nemáš nič pridané." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Pravdepodobne sa tu nenachádzajú žiadne výtvory..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(odstrániť)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Zahrnuté" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Pridať do kolekcie" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "ikona čítačky" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom čítačka" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Všetky práva vyhradené" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Novšie" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Staršie →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Prejsť na stránku:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "novšie" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "staršie" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Označené ako" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Nemožno prečítať súbor obrázka." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Hopla!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Vyskytla sa chyba" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Nepovolená operácia" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Prepáč Človeče, toto nesmieš!</p><p>Práve si chcel vykonať funkciu, na ktorú nemáš oprávnenie. Opäť si sa pokúšal odstrániť všetky používateľské účty?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Zdá sa, že na tejto adrese sa nič nenachádza. Prepáč!</p><p>Pokiaľ si si istý, že adresa je správna, možno bola hľadaná stránka presunutá, respektíve odstránená." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "rok" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "mesiac" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "týždeň" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "deň" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "hodina" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "minúta" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Komentár" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Môžeš využiť <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> pre formátovanie príspevku." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Jednoznačne to chcem odstrániť" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Skutočne chcem odstrániť danú položku z kolekcie" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Kolekcia" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Vybrať --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Pridať poznámku" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "okmentoval tvoj príspevok" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Prepáč, komentovanie je vypnuté." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Hopla, tvoj komentár bol prázdny." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Tvoj komentár bol pridaný!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Prosím skontroluj svoje položky a skús znova." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Musíš vybrať, prípadne pridať kolekciu" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" sa už nachádza v kolekcii \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s pridané do kolekcie \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Výtvor bol tebou odstránený." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Výtvor nebol odstránený, nakoľko chýbalo tvoje potvrdenie." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Chystáš sa odstrániť výtvory niekoho iného. Pristupuj zodpovedne. " + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "Položka bola z kolekcie odstránená." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Položka nebola odstránená, nakoľko políčko potvrdenia nebolo označné." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Chystáš sa odstrániť položku z kolekcie iného používateľa. Pristupuj zodpovedne. " + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "Kolekcia \"%s\" bola úspešne odstránená." + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Kolekcia nebola odstránená, nakoľko políčko potrvdenia nebolo označené." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Chystáš sa odstrániť kolekciu iného používateľa. Pristupuj zodpovedne. " diff --git a/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..199e761c --- /dev/null +++ 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 new file mode 100644 index 00000000..35635acf --- /dev/null +++ b/mediagoblin/i18n/sl/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1252 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Jure Repinc <jlp@holodeck1.com>, 2011 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Slovenian (http://www.transifex.com/projects/p/mediagoblin/language/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Uporabniško ime" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Geslo" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "E-poštni naslov" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Oprostite, prijava za ta izvod ni omogočena." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Oprostite, uporabnik s tem imenom že obstaja." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Vaš e-poštni naslov je bil potrjen. Sedaj se lahko prijavite, uredite svoj profil in pošljete slike." + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Potrditveni ključ ali uporabniška identifikacija je napačna" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Ponovno pošiljanje potrditvene e-pošte." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Naslov" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Oznake" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Oznaka" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Oznaka ne sme biti prazna" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Biografija" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Spletna stran" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Vnos s to oznako za tega uporabnika že obstaja." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Urejate vsebino drugega uporabnika. Nadaljujte pazljivo." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Urejate uporabniški profil. Nadaljujte pazljivo." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Za vrsto vsebine je bila podana napačna datoteka." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Datoteka" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Podati morate datoteko." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Juhej! Poslano." + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Prijava" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Podokno obdelovanja vsebine" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Dodaj vsebino" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Vsebina v obdelavi" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "V obdelavi ni nobene vsebine" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Teh vsebin ni bilo moč obdelati:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Prijava ni uspela." + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Še nimate računa?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Ustvarite si ga." + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Ustvarite račun." + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Ustvari" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Pozdravljeni, %(username)s\n\nZa aktivacijo svojega računa GNU MediaGoblin odprite\nnaslednji URL v svojem spletnem brskalniku:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Logotip MediaGoblin" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Prekliči" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Shrani spremembe" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Urejanje %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Urejanje profila – %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Vsebina uporabnika <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Tu lahko spremljate stanje vsebin, ki so v obdelavi za vašo galerijo." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profil – %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Oprostite, tega uporabnika ni bilo moč najti." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Potrebna je potrditev prek e-pošte" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Skoraj ste zaključili. Svoj račun morate le še aktivirati." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "V kratkem bi morali prejeti e-pošto z navodili, kako to storiti." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Če je ne prejmete:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Ponovno pošlji potrditveno e-pošto" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Nekdo je s tem uporabniškim imenom že registriral račun, vendar mora biti še aktiviran." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Če ste ta oseba vi, a ste izgubili potrditveno e-pošto, se lahko <a href=\"%(login_url)s\">prijavite</a> in jo ponovno pošljete." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Na tem mestu lahko drugim poveste nekaj o sebi." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Uredi profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Ta uporabnik še ni izpolnil svojega profila." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Prikaži vso vsebino uporabnika %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Tu bo prikazana vaša vsebina, a trenutno še niste dodali nič." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Videti je, da tu še ni nobene vsebine ..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "Ikona vira" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Ikona Atom" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Opa!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..0f113dcb --- /dev/null +++ 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 new file mode 100644 index 00000000..aabf18db --- /dev/null +++ b/mediagoblin/i18n/sq/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Besnik <besnik@programeshqip.org>, 2012-2013 +# FIRST AUTHOR <EMAIL@ADDRESS>, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Albanian (http://www.transifex.com/projects/p/mediagoblin/language/sq/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: sq\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Emër përdoruesi" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Fjalëkalim" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Adresë email" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Emër përdoruesi ose email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "Emër përdoruesi ose adresë email e pavlefshme." + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "Kjo fushë nuk është për adresa email." + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "Kjo fushë lyp një adresë email." + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Na njdeni, regjistrimi në këtë instancë të shërbimit është i çaktivizuar." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Na ndjeni, ka tashmë një përdorues me këtë emër." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Na ndjeni, ka tashmë një përdorues me këtë adresë email." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Adresa juaj email u verifikua. Tani mund të bëni hyrjen, të përpunoni profilin tuaj, dhe të parashtroni figura!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Kyçi i verifikimit ose id-ja e përdoruesit është e pasaktë" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Duhet të jeni i futur, që ta dimë kujt t'ia çojmë email-in!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Thuajse e keni verifikuar adresën tuaj email!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Ridërgoni email-in tuaj të verifikimit." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "Nëse ajo adresë email (siç është shkruajtur!) është e regjistruar, është dërguar një email me udhëzime se si të ndryshoni fjalëkalimin tuaj." + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "S'u gjet dot dikush me atë emër përdoruesi." + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Është dërguar një email me udhëzime se si të ndryshoni fjalëkalimin tuaj." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Email-i i ricaktimit të fjalëkalimit nuk u dërgua dot, ngaqë emri juaj i përdoruesit nuk është aktivizuar ose adresa email e llogarisë suaj nuk është verifikuar." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Tani mun të hyni duke përdorur fjalëkalimin tuaj të ri." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titull" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Përshkrim i kësaj pune" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "Mund të përdorni\n <a href=\"http://daringfireball.net/projects/markdown/basics\">\n Markdown</a> për formatim." + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etiketa" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Ndajini etiketat me presje." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Identifikues" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Identifikuesi s'mund të jetë i zbrazët" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "Titulli i adresës së kësaj medie. Zakonisht nuk keni nevojë ta ndryshoni këtë." + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "Leje" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Jetëshkrim" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Site Web" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "Kjo adresë përmban gabime" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "Parapëlqime licence" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "Kjo do të jetë licenca juaj parazgjedhje për forma ngarkimesh." + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Dërgomë email kur të tjerët komentojnë te media ime" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "Titulli s'mund të jetë i zbrazët" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "Përshkrim i këtij koleksioni" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "Pjesa titull e adresës së këtij koleksioni. Zakonisht nuk keni pse e ndryshoni këtë." + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Fjalëkalimi i vjetër" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "Jepni fjalëkalimin tuaj të vjetër që të provohet se këtë llogari e zotëroni ju." + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Fjalëkalimi i ri" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Ka tashmë një zë me atë identifikues për këtë përdorues." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Po përpunoni media të një tjetër përdoruesi. Hapni sytë." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "Shtuat bashkangjitjen %s!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "Mund të përpunoni vetëm profilin tuaj." + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Po përpunoni profilin e një përdoruesi. Hapni sytë." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Ndryshimet e profilit u ruajtën" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Rregullimet e llogarisë u ruajtën" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "Lypset të ripohoni fshirjen e llogarisë suaj." + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "Keni tashmë një koleksion të quajtur \"%s\"!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "Ka tashmë një koleksion me atë identifikues për këtë përdorues." + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "Po përpunoni koleksionin e një tjetër përdoruesi. Hapni sytë." + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Fjalëkalim i gabuar" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "Nuk krijohet dot lidhje për te tema... nuk ka temë të caktuar\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "Nuk ka drejtori asetesh për këtë temë\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "Sidoqoftë, u gjet simlidhje e vjetër drejtorie lidhjesh; u hoq.\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "Pa cookie CSRF të pranishme. Ka shumë të ngjarë që të jetë punë e një bllokuesi cookie-sh ose të tillë.<br/>Sigurohuni që të lejoni depozitim cookie-sh për këtë përkatësi." + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Na ndjeni, nuk e mbullojmë këtë lloj kartele :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "Ndërkodimi i videos dështoi" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "Vend" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "Shiheni te <a href=\"%(osm_url)s\">OpenStreetMap</a>" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "Lejoje" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "Mohoje" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "Emër" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "Emri i klientit OAuth" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "Përshkrim" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "Kjo do të jetë e dukshme për përdoruesit,\n duke i lejuar kështu zbatimit tuaj\n të kryejë mirëfilltësim si të qe njëri prej tyre." + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Lloj" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>Konfidenciale</strong> - Kklienti mund\n të bëjë kërkesa te instanca GNU MediaGoblin që nuk mund\n të përgjohen nga agjenti i përdoruesit (p.sh. klient te shërbyesi).<br />\n <strong>Publike</strong> - Klienti nuk mund të bëjë kërkesa\n konfidenciale te instanca GNU MediaGoblin (p.sh. klient\n JavaScript i vetë klientit)." + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "URI Ridrejtimi" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "URI ridrejtimi për zbatimin, kjo fushë\n është <strong>e domosdoshme</strong> për klientë publikë." + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "Kjo fushë është e domosdoshme për klientë publikë" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "Klienti {0} u regjistrua!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "Lidhje klienti OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "Klientët tuaj OAuth" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Shtoni" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Kartelë e gabuar e dhënë për llojin e medias." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Kartelë" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Duhet të jepni një kartelë." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Yhaaaaaa! U parashtrua!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "U shtua koleksioni \"%s\"!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifikoni email-in tuaj!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "dilni" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Hyni" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "Llogaria e <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Ndryshoni rregullime llogarie" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Paneli i përpunimit të medias" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Dilni" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Shtoni media" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "Krijoni koleksion të ri" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "Figurë e gungaçi duke bërë shtriqje" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Mediat më të reja" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "Këtu mund të ndiqni gjendjen e medias që po përpunohet në këtë instancë." + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Media në përpunim" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Pa media në përpunim" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "Nuk arritën të kryheshin këto ngarkime:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "Pa zëra të dështuar!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "10 ngarkimet e fundit të suksesshme" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "Ende pa zëra të përpunuar!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "Caktoni fjalëkalimin tuaj të ri" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "Caktoni fjalëkalim" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Rimerrni fjalëkalimin" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Dërgo udhëzime" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Njatjeta %(username)s,\n\nqë të ndryshoni fjalëkalimin tuaj për GNU MediaGoblin, hapeni URL-në vijuese në \nshfletuesin tuaj web:\n\n%(verification_url)s\n\nNëse mendoni se këtu ka gabim, thjesht shpërfilleni këtë email dhe vazhdoni të jeni\nnjë djallush i lumtur!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Hyrja dështoi!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Nuk keni ende një llogari?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Krijoni një këtu!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Harruat fjalëkalimin tuaj?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Krijoni një llogari!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Krijoje" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Njatjeta %(username)s,\n\nqë të aktivizoni llogarinë tuaj te GNU MediaGoblin hapeni URL-në vijuese te\nshfletuesi juaj web:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Bazuar në <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>, një projekt <a href=\"http://gnu.org/\">GNU</a>." + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "Hedhur në qarkullim sipas <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL-së</a>. <a href=\"%(source_link)s\">Kodi burim</a> është i passhëm." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Eksploroni" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Tungjatjeta juaj, mirë se vini te ky site MediaGoblin!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "Ky site përdor <a href=\"http://mediagoblin.org\">MediaGoblin</a>, një program jashtëzakonisht i shkëlqyer për strehim mediash." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "Për të shtuar media tuajën, për të bërë komente, dhe të tjera, mund të hyni përmes llogarisë suaj MediaGoblin." + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Nuk keni ende një të tillë? Është e lehtë!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "Logoja e MediaGoblin-it" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "Po përpunohen bashkangjitjet për %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "Bashkangjitje" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "Shtoni bashkangjitje" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Anuloje" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Ruaji ndryshimet" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Të fshihet vërtet përdoruesi '%(user_name)s' dhe krejt media/komentet përkatëse?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Po, fshijeni vërtet llogarinë time" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "Fshije përgjithmonë" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Po përpunohet %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "Po ndryshohen rregullimet e llogarisë %(username)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Fshije llogarinë time" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "Po përpunohet %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Po përpunohet profili i %(username)s" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Media e etiketuar me:: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "Shkarkojeni" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Origjinal" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "Na ndjeni, zëri s'do të funksionojë, ngaqë \n\tshfletuesi juaj s'mbulon audio HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "Një shfletues web modern që mund të luajë \n\taudion mund ta merrni te <a href=\"http://getfirefox.com\">\n\t http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Kartela origjinale" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "Kartelë WebM (kodek Vorbis)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "Figurë për %(media_title)s" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "Aktivizoni/Çaktivizoni Rrotullimin" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "Perspektivë" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "Ball" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "Krye" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "Anë" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "Shkarkojeni modelin" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Format Kartele" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "Lartësi Objekti" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "Na ndjeni, kjo video nuk do të punojë ngaqë\n shfletuesi juaj web nuk mbulon videot\n HTML5." + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "Mund të merrni një shfletues web modern që \n është në gjendje ta shfaqë këtë video, te <a href=\"http://getfirefox.com\">\n http://getfirefox.com</a>!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "Kartelë WebM (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "Shtoni një koleksion" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "Shtoni media tuajën" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (koleksione nga %(username)s)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s nga <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Përpunoni" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Fshije" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Të fshihet vërtet %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Të hiqet vërtet %(media_title)s nga %(collection_title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Hiqe" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "Koleksione të %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "Koleksione të <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "Tungjatjeta %(username)s,\n%(comment_author)s ka komentuar te postimi juaj (%(comment_url)s) në %(instance_name)s\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "Media nga %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "Media të <a href=\"%(user_url)s\">%(username)s</a> me etiketën <a href=\"%(tag_url)s\">%(tag)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "Media nga <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ Po shfletoni media nga <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Shtoni një koment" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Shtoje këtë koment" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "Shtojeni “%(media_title)s” te një koleksion" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "Shtoni një koleksion të ri" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Gjendjen e medias që po përpunohet për galerinë tuaj mund ta ndiqni nga këtu." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "10 ngarkimet tuaja më të suksesshme" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "Profili i %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Na ndjeni, nuk u gjet përdorues i tillë." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "Lypset verifikimi i email-it" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Pothuajse mbaruam! Llogaria juaj ende lyp aktivizimin." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Brenda pak çastesh duhet t'ju mbërrijë një email me udhëzime se si të krhyet kjo." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Në rast se jo:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Ridërgo email-in e verifikimit" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Dikush ka regjistruar një llogari me këtë emër përdoruesi, por ajo duhet aktivizuar." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Nëse jeni ju ai person, por keni humbur email-in tuaj të verifikimit, mund të <a href=\"%(login_url)s\">hyni</a> dhe ta ridërgoni." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Ja një vend t'i tregoni botës mbi veten." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Përpunoni profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Ky përdorues nuk e ka plotësuar (ende) profilin e vet." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "Shfletoni koleksionet" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Shihni krejt mediat nga %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Media juaj do të shfaqet këtu, por nuk duket të keni shtuar gjë ende." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Nuk duket ende të ketë ndonjë media këtu..." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(hiqe)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "Pjesë e koleksionit" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "Shtoje te një koleksion" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "ikonë prurjesh" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Prurje Atom" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Tërë të drejtat të rezervuara" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← Më të reja" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "Më të vjetra →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Shko te faqja:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "më të reja" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "më të vjetra" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "Etiketuar me" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "Nuk lexoi dot kartelën e figurës." + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Oooh!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "Ndodhi një gabim" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "Veprim i palejuar" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Më ndjeni or trim, nuk ju lë dot ta bëni këtë!</p><p>Provuat të kryeni një funksion që nuk lejohet. Keni provuar prapë të fshini krejt llogaritë e përdoruesve?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "Nuk duket se ka ndonjë faqe në këtë adresë. Na ndjeni!</p><p>Nëse jeni i sigurt se kjo adresë është e saktë, ndoshta faqja që po kërkoni është lëvizur ose fshirë." + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "Koment" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "Për formatime mund të përdorni <a href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a>." + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Jam i sigurt që dua të fshihet kjo" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "Jam i sigurt se dua që të hiqet ky objekt prek koleksioni" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "Koleksion" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "-- Përzgjidhni --" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "Përfshini një shënim" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "komentoi te postimi juaj" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Hmmm, komenti juaj qe i zbrazët." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Komenti juaj u postua!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "Ju lutemi, kontrolloni zërat tuaj dhe riprovoni." + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "Duhet të përzgjidhni ose shtoni një koleksion" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "\"%s\" gjendet tashmë te koleksioni \"%s\"" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "\"%s\" u shtua te koleksioni \"%s\"" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "E fshitë median." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Media nuk u fshi ngaqë nuk i vutë shenjë pohimit se jeni i sigurt." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Ju ndan një hap nga fshirja e medias të një tjetër përdoruesi. Hapni sytë." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "E fshitë objektin prej koleksionit." + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "Objekti nuk u fshi ngaqë, nuk pohuat se jeni të sigurt për këtë." + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "Ju ndan një hap nga fshirja e një objekti prej koleksionit të një përdoruesi tjetër. Hapni sytë." + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "E fshitë koleksionin \"%s\"" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "Koleksioni nuk u fshi ngaqë, nuk pohuat se jeni të sigurt për këtë." + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "Ju ndan një hap nga fshirja e koleksionit të një përdoruesi tjetër. Hapni sytë." diff --git a/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..5564d35d --- /dev/null +++ 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 new file mode 100644 index 00000000..fcf8a666 --- /dev/null +++ b/mediagoblin/i18n/sr/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1251 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Serbian (http://www.transifex.com/projects/p/mediagoblin/language/sr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: sr\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..3b961e60 --- /dev/null +++ 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 new file mode 100644 index 00000000..659de21b --- /dev/null +++ b/mediagoblin/i18n/sv/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1253 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# ingenman <simon@ingenmansland.se>, 2011 +# joar <transifex@wandborg.se>, 2011, 2012 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Swedish (http://www.transifex.com/projects/p/mediagoblin/language/sv/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: sv\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Användarnamn" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Lösenord" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "E-postadress" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Vi beklagar, registreringen är avtängd på den här instansen." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "En användare med det användarnamnet finns redan." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Det finns redan en användare med den e-postadressen." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "Din e-postadress är verifierad. Du kan nu logga in, redigera din profil och ladda upp filer!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Verifieringsnyckeln eller användar-IDt är fel." + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "Du måste vara inloggad för att vi ska kunna skicka meddelandet till dig." + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Du har redan verifierat din e-postadress!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Skickade ett nytt verifierings-email." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "Kunde inte skicka e-poståterställning av lösenord eftersom ditt användarnamn är inaktivt eller kontots e-postadress har inte verifierats." + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Titel" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "Beskrivning av verket" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Taggar" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "Sökvägsnamn" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "Sökvägsnamnet kan inte vara tomt" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "Presentation" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Hemsida" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Tidigare lösenord" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "Ett inlägg med det sökvägsnamnet existerar redan." + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Var försiktig, du redigerar någon annans inlägg." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Var försiktig, du redigerar en annan användares profil." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Fel lösenord" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Ogiltig fil för mediatypen." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Fil" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Du måste ange en fil" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Tjohoo! Upladdat!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "Verifiera din e-postadress" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Logga in" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Mediabehandlingspanel" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Lägg till media" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "Senast medier" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "Media under behandling" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "Ingen media under behandling" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "De här behandlingarna misslyckades:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "Återställ lösenord" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "Skicka instruktioner" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "Hej %(username)s,\n\nför att ändra ditt GNU MediaGoblin-lösenord, öppna följande länk i\ndin webbläsare:\n\n%(verification_url)s\n\nOm du misstänker att du fått detta epostmeddelanade av misstag, ignorera det och fortsätt vara ett glatt troll!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Inloggning misslyckades!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Har du inget konto än?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Skapa ett här!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Glömt ditt lösenord?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Skapa ett konto!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Skapa" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Hej %(username)s,\n\nöppna den följande webbadressen i din webbläsare för att aktivera ditt konto på GNU MediaGoblin:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Utforska" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "Hej, välkommen till den här MediaGoblin-sidan!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "Har du inte ett redan?" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin-logotyp" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "Avbryt" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Spara ändringar" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "Redigerar %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "Redigerar %(username)ss profil" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "Media taggat med: %(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Original" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Vill du verkligen radera %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "%(username)ss media" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a>s media" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Här kan du se status för mediabehandling av bilder i ditt galleri." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)ss profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Ledsen, hittar ingen sådan användare." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "E-postadressverifiering krävs." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Nästan klar! Ditt konto behöver bara aktiveras." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Ett e-postmeddelande med instruktioner kommer att hamna hos dig inom kort." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "Om det inte skulle göra det:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Skicka ett nytt e-postmeddelande" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "Någon har redan registrerat ett konto med det här användarnamnet men det har inte aktiverats." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Om det är du som är den personen och har förlorat ditt e-postmeddelande med detaljer om hur du verifierar ditt konto så kan du <a href=\"%(login_url)s\">logga in</a> och begära ett nytt." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "Här kan du berätta för andra om dig själv." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Redigera profil" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "Den här användaren har inte fyllt i sin profilsida ännu." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "Se all media från %(username)s" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "Här kommer din media att dyka upp, du verkar inte ha lagt till någonting ännu." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "Det verkar inte finnas någon media här ännu." + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "feed-ikon" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom-feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Ojoj!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Jag är säker på att jag vill radera detta" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Du tänker radera en annan användares media. Var försiktig." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..6e7ebd21 --- /dev/null +++ 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 new file mode 100644 index 00000000..b0bf1aa1 --- /dev/null +++ b/mediagoblin/i18n/te/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1252 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# వీవెన్ <veeven@gmail.com>, 2011 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Telugu (http://www.transifex.com/projects/p/mediagoblin/language/te/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: te\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "వాడుకరి పేరు" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "సంకేతపదం" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "ఈమెయిలు చిరునామా" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "శీర్షిక" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "ప్రవేశం విఫలమయ్యింది!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "మీకు ఇంకా ఖాతా లేదా?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "మీ సంకేతపదాన్ని మర్చిపోయారా?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "రద్దుచేయి" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "మార్పులను భద్రపరచు" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..aba1b791 --- /dev/null +++ b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.po new file mode 100644 index 00000000..c82bd819 --- /dev/null +++ b/mediagoblin/i18n/tr/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,770 @@ +# Translations template for PROJECT. +# Copyright (C) 2012 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2012-08-05 10:01-0500\n" +"PO-Revision-Date: 2011-08-07 03:51+0000\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: tr\n" +"Plural-Forms: nplurals=1; plural=0\n" + +#: mediagoblin/auth/forms.py:25 mediagoblin/auth/forms.py:41 +msgid "Username" +msgstr "" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +msgid "Password" +msgstr "" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "" + +#: mediagoblin/auth/forms.py:51 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/forms.py:58 +msgid "Incorrect input" +msgstr "" + +#: mediagoblin/auth/views.py:55 +msgid "Sorry, registration is disabled on this instance." +msgstr "" + +#: mediagoblin/auth/views.py:75 +msgid "Sorry, a user with that name already exists." +msgstr "" + +#: mediagoblin/auth/views.py:79 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "" + +#: mediagoblin/auth/views.py:263 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:273 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:285 +msgid "Couldn't find someone with that username or email." +msgstr "" + +#: mediagoblin/auth/views.py:333 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/submit/forms.py:28 +msgid "Title" +msgstr "" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/submit/forms.py:32 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:64 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:67 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/forms.py:72 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/views.py:64 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:181 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:197 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:226 mediagoblin/edit/views.py:246 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:251 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/gmg_commands/theme.py:58 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/theme.py:71 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/theme.py:74 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/media_types/__init__.py:60 +#: mediagoblin/media_types/__init__.py:120 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:35 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/processing/__init__.py:138 +msgid "Invalid file given for media type." +msgstr "" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "" + +#: mediagoblin/submit/views.py:56 +msgid "You must provide a file." +msgstr "" + +#: mediagoblin/submit/views.py:163 +msgid "Woohoo! Submitted!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/404.html:22 +msgid "Image of 404 goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/404.html:23 +msgid "Oops!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/404.html:24 +msgid "There doesn't seem to be a page at this address. Sorry!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/404.html:26 +msgid "" +"If you're sure the address is correct, maybe the page you're looking for has" +" been moved or deleted." +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:50 +msgid "MediaGoblin logo" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:60 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:66 +msgid "+ Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "View your profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:69 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:74 +#: mediagoblin/templates/mediagoblin/auth/login.html:32 +#: mediagoblin/templates/mediagoblin/auth/login.html:50 +msgid "Log in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:88 +msgid "" +"Powered by <a href=\"http://mediagoblin.org\">MediaGoblin</a>, a <a " +"href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:91 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:24 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:26 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:28 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:29 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:31 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:40 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:22 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:22 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:25 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:28 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:54 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:52 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:55 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:86 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:82 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:88 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:108 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:103 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:32 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:35 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:27 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:30 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:35 +msgid "Logging in failed!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:40 +msgid "Don't have an account yet?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:41 +msgid "Create one here!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:47 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:32 +msgid "Create an account!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:29 +#, python-format +msgid "Editing %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:36 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Cancel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:37 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:35 +msgid "Save changes" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:34 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:29 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/image.html:23 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:52 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +#: mediagoblin/templates/mediagoblin/media_displays/image.html:27 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:56 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:40 +msgid "" +"Sorry, this video will not work because \n" +"\t your web browser does not support HTML5 \n" +"\t video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:43 +msgid "" +"You can get a modern web browser that \n" +"\t can play this video at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:59 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:26 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:37 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:46 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:73 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:87 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:91 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:102 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:109 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:113 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +msgid "at" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#, python-format +msgid "" +"<h3>Added on</h3>\n" +" <p>%(date)s</p>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:167 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:183 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:188 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:50 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:25 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:85 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:101 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:118 +msgid "Edit profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:106 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:125 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:138 +#, python-format +msgid "View all of %(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:151 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:157 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:163 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:72 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:25 +msgid "Location" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/geolocation_map.html:38 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:78 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/user_pages/forms.py:30 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/lib.py:56 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:160 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:166 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:200 +msgid "" +"Some of the files with this entry seem to be missing. Deleting anyway." +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:212 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:220 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..4341870b --- /dev/null +++ b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po new file mode 100644 index 00000000..4155520f --- /dev/null +++ b/mediagoblin/i18n/tr_TR/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1252 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# Caner BAŞARAN <basaran.caner@gmail.com>, 2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-06 15:44+0000\n" +"Last-Translator: Caner BAŞARAN <basaran.caner@gmail.com>\n" +"Language-Team: Turkish (Turkey) (http://www.transifex.com/projects/p/mediagoblin/language/tr_TR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: tr_TR\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "Kullanıcı adı" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "Parola" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "E-posta adresi" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "Kullanıcı adı veya E-posta" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "Kullanıcı adı ya da e-posta" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "Üzgünüz, bu durumda kayıt devre dışıdır." + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "Maalesef, bu isimde bir kullanıcı mevcut." + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "Üzgünüz, bu e-posta adresine sahip bir kullanıcı zaten var." + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "E-posta adresiniz doğrulandı. Şimdi giriş yapabilir, profilinizi düzenleyip ve yeni görüntüleri gönderebilirsiniz!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "Doğrulama anahtarı veya kullanıcı kimliği yanlış" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "Zaten e-posta adresinizi doğruladınız!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "Doğrulama e-postasını tekrar yolla." + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "Parolanızı nasıl değiştireceğinizle ilgili adımları anlatan bir e-posta gönderildi." + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "Şimdi yeni parolanızı giriş için kullanabilirsiniz." + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "Başlık" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "Etiketler" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "Etikerleri virgül ile ayırın." + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "Web sitesi" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "Medyama birisi yorum yazdığında bana e-posta at" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "Eski parola" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "Yeni parola" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "Başka bir kullanıcının medyasını düzenlerken dikkatli davranın." + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "Başka bir kullanıcının profilini düzenlerken dikkatli davranın." + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "Profil değişiklikleri kaydedildi" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "Hesap ayarları kaydedildi" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "Yanlış parola" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "Parolanız başarılı bir şekilde değiştirildi" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "Üzgünüz, bu tip dosyaları desteklemiyoruz :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "Tür" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "Ekle" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "Bu medya türü için geçersiz dosya türü." + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "Dosya" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "Bir dosya sağlamanız gerekir." + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "Hoooop! Gönderildi!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "E-postanızı doğrulayın!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "çıkış" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "Giriş" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "Hesap ayarlarını değiştir" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "Madya işlem paneli" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "Çıkış" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "Medya ekle" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "Giriş başarısız!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "Hala hesabınız yok mu?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "Şimdi oluşturun!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "Parolanı mı unuttun?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "Hesap oluştur!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "Oluştur" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "Merhaba %(username)s,\n\nGNU MediaGoblin hesabınızı etkinleştirmek için, lütfen aşağıdaki\nURL(bağlantı)'yı Web tarayıcınızda açın:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "Keşfet" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin logo" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "İptal" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "Değişiklikleri kaydet" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "Kaydet" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "Gerçekten '%(user_name)s' kullanıcısını ve ilgili tüm medya/yorumları silmek istiyor musun?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "Evet, gerçekten hesabımı silmek istiyorum" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "%(media_title)s düzenleme" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "Parolanızı değiştirin." + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "Hesabımı sil" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "%(username)s profilini düzenleme" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "İndir" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "Özgün" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "Özgün dosya" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF dosya" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "Dosya Biçimi" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "Düzenle" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "Si" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "Gerçekten %(title)s silmek istiyor musun?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "Gerçekten %(collection_title)s %(media_title)s kaldırmak istiyor musun?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "Kaldır" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "%(username)s medyası" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> medyası" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "Bir yorum ekle" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "Bu yorumu ekle" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "%(formatted_time)s önce" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "Eklendi" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "Oluşturuldu" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "Burada galerinizdeki işlenmekte olan medyanın durumunu takip edebilirsiniz." + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)s profili" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "Üzgünüz, böyle bir kullanıcı bulunamadı." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "E-posta doğrulaması gerekli" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "Neredeyse bitti! Hesabınızı etkinleştirmeniz gerekiyor." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "Bunun nasıl yapılacağı ile ilgili talimatlar, birkaç dakika içinde size e-posta ulaşacak." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "Doğrulama e-postası tekrar yolla" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "Doğrulama e-postasını kaybettiyseniz, <a href=\"%(login_url)s\">giriş</a> yapabilir ve yeniden yollayabilirsiniz." + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "Profil düzenle" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "%(username)s tüm medyasını göster" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(kaldır)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "besleme simgesi" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom besleme" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "Tüm hakları saklıdır" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "Sayfaya git:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "Amaninnn boo!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "yıl" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "ay" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "hafta" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "gün" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "saat" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "dakika" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "Bunu silmek için eminim" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "Maalesef, yorum devre dışı." + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "Amaninnn boo, yorumunuz boştu." + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "Yorumunuz gönderildi!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "Medyayı sildiniz." + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "Medya silinmedi çünkü emin olduğunuzu onaylamadınız." + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "Başka bir kullanıcının medyasını silerken dikkatli davranın." + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..1ed5a4f1 --- /dev/null +++ b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.mo diff --git a/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po new file mode 100644 index 00000000..4bb714fe --- /dev/null +++ b/mediagoblin/i18n/zh_CN/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1256 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# <chc@citi.sinica.edu.tw>, 2011 +# cwebber <cwebber@dustycloud.org>, 2013 +# m13253 <m13253@hotmail.com>, 2013 +# medicalwei <medicalwei@gmail.com>, 2012 +# m13253 <m13253@hotmail.com>, 2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-16 11:06+0000\n" +"Last-Translator: m13253 <m13253@hotmail.com>\n" +"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/mediagoblin/language/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "用户名" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "密码" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "电子邮件地址" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "用户名或电子邮件" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "用户名或电子邮件" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "无效用户名或电子邮件地址。" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "此字段不能填写电子邮件地址。" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "此字段需填写电子邮件地址。" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "抱歉,本站已暂停注册。" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "抱歉,该用户名已存在。" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "抱歉,已有用户用该电子邮件注册。" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "您的电子邮件地址已认证。您现在可以登录、修改个人资料并上传图片了!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "验证码错误或用户 ID 错误" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "您必须登录以便让我们知道将电子邮件发给谁" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "您已经认证过电子邮件地址了!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "重发认证邮件。" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "若该邮件地址(区分大小写)已被注册,则密码修改说明已通过电子邮件送达。" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "找不到有该用户名的人。" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "密码修改说明已通过电子邮件送达。" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "无法发送密码找回邮件,因为您的用户名未激活或者您账户的电子邮件地址未认证。" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "您现在可以用新的密码来登录了!" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "标题" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "该作品的描述" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "您可以用 <a href=\"http://wowubuntu.com/markdown/\">Markdown</a> 来排版。" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "标签" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "用逗号分隔标签。" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "简称" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "简称不能为空" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "该媒体网址的标题部份。通常不需要修改。" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "许可证" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "个性签名" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "网站" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "本网址出错了" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "许可证偏好" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "这将是您上传界面的默认许可证。" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "当有人对我的媒体评论时给我电子邮件" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "标题不能是空的" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "这个合集的描述" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "此合集网址的标题部份,通常不需要修改。" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "旧的密码" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "输入您的旧密码来证明您拥有这个账户。" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "新密码" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "这个简称已经被别人用了" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "您正在修改别人的媒体,请小心操作。" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "您加上了附件“%s”!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "您只能修改自己的个人资料" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "您正在修改别人的个人资料,请小心操作。" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "个人资料已修改" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "账户设置已保存" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "您需要确认删除您的账户。" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "您已经有一个称做“%s”的合集了!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "该用户已经有使用该简称的合集了。" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "您正在修改别人的合集,请小心操作。" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "密码错误" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "您的密码已成功修改" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "无法链接到主题……未设置主题\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "此主题没有素材目录\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "但是旧的目录链接已经找到并移除。\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "无法链接到“%s”:“%s”已存在且不是链接\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "跳过“%s”;已设置过了。\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "“%s”的旧链接已经找到并移除。\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "CSRF cookie 不存在。很可能是由类似 cookie 屏蔽器造成的。<br />请允许本域名的 cookie 设定。" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "抱歉,我不支持这样的文件格式 :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "无法运行 unoconv,请检查日志" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "视频转码失败" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "位置" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "在 <a href=\"%(osm_url)s\">OpenStreetMap</a> 上观看" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "允许" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "拒绝" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "名称" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "OAuth client 的名称" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "描述" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "本描述将会被进行应用程序认证的用户看到。" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "类型" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>秘密</strong> — OAuth client 可以对 GNU MediaGoblin 站点发送不被用户代理拦截的请求(例如服务端上的 client)。\n<strong>公开</strong> — OAuth client 无法对 GNU MediaGoblin 站点发送秘密的请求(例如客户端的 JavaScript client)。" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "重定向 URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "此应用程序的重定向 URI,本字段在公开类型的 OAuth client 为<strong>必填</strong>。" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "本字段在公开类型的 OAuth client 为必填" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "OAuth client {0} 注册完成!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "OAuth client 连接" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "您的 OAuth client" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "增加" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "提供文件的媒体类型错误。" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "文件" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "您必须提供一个文件" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "啊哈!已提交!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "合集“%s”已新增!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "确认您的电子邮件!" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "登出" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "登录" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "<a href=\"%(user_url)s\">%(user_name)s</a> 的账户" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "更改账户设置" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "媒体处理面板" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "登出" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "新增媒体" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "新增合集" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "满脸问号的哥布林" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "最新的媒体" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "此处您可以追踪本站点处理媒体的状态。" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "媒体处理中" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "没有正在处理中的媒体" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "无法处理这些上传内容:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "没有失败的纪录!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "最近 10 次成功上传的纪录" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "现在还没有处理的纪录!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "设置您的新密码" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "设置新密码" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "找回密码" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "发送找回密码说明" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "%(username)s 您好:\n\n要修改 GNU MediaGoblin 的密码,请在您的浏览器中打开下面的网址:\n\n%(verification_url)s\n\n如果您认为这个是个误会,请忽略此封信件,继续当个快乐的哥布林!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "登录失败!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "还没有账户吗?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "在这里建立一个吧!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "忘了密码吗?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "建立一个账户!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "建立" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "%(username)s 您好:\n\n要启动 GNU MediaGoblin 账户,请在您的浏览器中打开下面的网址:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "Powered by <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>,一个 <a href=\"http://gnu.org/\">GNU</a> 项目。" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "以 <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a> 授权发布。备有<a href=\"%(source_link)s\">源代码</a>。" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "探索" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "嘿!欢迎来到 MediaGoblin 站! " + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "本站使用 <a href=\"http://mediagoblin.org\">MediaGoblin</a>——与众不同的媒体分享网站。" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "您可以登录您的 MediaGoblin 账户以上传媒体、张贴评论等等。" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "没有账户吗?开账户很简单!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">在本站创建帐户</a>\n 或者\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">在您自己的服务器上搭建 MediaGoblin</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin 标志" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "编辑 %(media_title)s 的附件" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "附件" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "新增附件" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "取消" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "保存更改" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "修改 %(username)s 的密码" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "保存" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "真的要删除用户 %(user_name)s 及所有相关媒体和评论吗?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "是的,真的删除我的账户" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "永久删除" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "编辑 %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "正在改变 %(username)s 的账户设置" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "修改您的密码。" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "删除我的帐户" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "编辑 %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "编辑 %(username)s 的个人资料" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "此媒体被标记为:%(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "下载" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "源文件" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "抱歉,此声音无法播放,因为您的浏览器不支持 HTML5 音频。" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此声音的浏览器!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "源文件" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM 文件(Vorbis 编码)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "%(media_title)s 的照片" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF 文件" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "切换旋转" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "透视" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "正面" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "顶面" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "侧面" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "下载模型" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "文件格式" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "对象高度" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "抱歉,此视频无法播放,因为您的浏览器不支持 HTML5 视频。" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此视频的浏览器!" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM 文件(640p;VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "新增合集" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "加入您的媒体" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (%(username)s 的合集)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "编辑" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "删除" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "真的要删除 %(title)s 吗?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "确定要从 %(collection_title)s 移除 %(media_title)s 吗?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "移除" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "%(username)s 的合集" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的合集" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "%(username)s 您好:\n%(comment_author)s 在 %(instance_name)s 对您的内容 (%(comment_url)s) 张贴评论\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "%(username)s的媒体" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的有 <a href=\"%(tag_url)s\">%(tag)s</a> 标签的媒体" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的媒体" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ 浏览 <a href=\"%(user_url)s\">%(username)s</a> 的媒体" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "新增评论" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "增加评论" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "%(formatted_time)s前" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "已增加" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "已创建" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "把“%(media_title)s”加入合集" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "新增新的合集" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "您可以在这里追踪您的艺廊中媒体处理的状态。" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "您的最近 10 次成功上传的纪录" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)s 的个人资料" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "抱歉,找不到该用户。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "需要认证电子邮件地址" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "快完成了!但您需要激活您的账户。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "账户激活说明将在稍后送达。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "如果仍然无法认证,您可以:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "重发认证邮件" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "有人用注册了该账户,但是该账户需要被启用。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "如果您就是本人但是未收到认证信,您可以<a href=\"%(login_url)s\">登录</a>然后重发一次。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "这个地方能让您向他人介绍自己。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "编辑个人资料" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "这个用户(还)没有填写个人资料。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "浏览合集" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "查看 %(username)s 的全部媒体" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "此处是您的媒体会出现的地方,但是似乎还没有加入任何东西。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "那里好像还没有任何的媒体……" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "(移除)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "合集于" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "添加到合集" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "feed 图标" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "版权所有" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← 更新的" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "更旧的 →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "跳到页数:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "更新的" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "更旧的" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "标签" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "无法读取图片文件。" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "糟糕!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "发生错误" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "操作不允许" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "对不起老兄,我不能让你这样做!</p><p>您正在试着操作不允许您使用的功能。您难道想打算删除所有用户账户吗?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "不好意思,看起来这个网址上没有网页。</p><p>如果您确定这个网址是正确的,您在寻找的页面可能已经移动或是被删除了。" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "年" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "月" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "周" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "日" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "小时" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "分钟" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "评论" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "您可以用 <a href=\"http://wowubuntu.com/markdown/\">Markdown</a> 来排版。" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "我确定我要删除这个媒体" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "我确定我要从合集中移除此项目" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "合集" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "— 请选择 —" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "加注" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "在您的内容张贴评论" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "抱歉,不开放评论。" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "啊,您的评论是空的。" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "您的评论已经张贴完成!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "请检查项目并重试。" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "您需要选择或是新增一个合集" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "“%s”已经在“%s”合集" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "“%s”加入“%s”合集" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "您已经删除此媒体。" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "由于您没有勾选确认,该媒体没有被移除。" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "您正在删除别人的媒体,请小心操作。" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "您已经从该合集中删除该项目。" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "由于您没有勾选确认,该项目没有被移除。" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "您正在从别人的合集中删除项目,请小心操作。" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "您已经删除“%s”合集。" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "由于您没有勾选确认,该合集没有被移除。" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "您正在删除别人的合集,请小心操作。" diff --git a/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..c234ff00 --- /dev/null +++ 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 new file mode 100644 index 00000000..a7ee8db6 --- /dev/null +++ b/mediagoblin/i18n/zh_TW.Big5/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1251 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-05-27 18:54+0000\n" +"Last-Translator: cwebber <cwebber@dustycloud.org>\n" +"Language-Team: Chinese (Taiwan) (Big5) (http://www.transifex.com/projects/p/mediagoblin/language/zh_TW.Big5/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: zh_TW.Big5\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "" diff --git a/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mo b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.mo Binary files differnew file mode 100644 index 00000000..4b7a2398 --- /dev/null +++ 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 new file mode 100644 index 00000000..05ecd4b5 --- /dev/null +++ b/mediagoblin/i18n/zh_TW/LC_MESSAGES/mediagoblin.po @@ -0,0 +1,1256 @@ +# Translations template for PROJECT. +# Copyright (C) 2013 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# +# Translators: +# <chc@citi.sinica.edu.tw>, 2011 +# Harry Chen <harryhow@gmail.com>, 2011-2012 +# medicalwei <medicalwei@gmail.com>, 2013 +# medicalwei <medicalwei@gmail.com>, 2012 +# m13253 <m13253@hotmail.com>, 2013 +msgid "" +msgstr "" +"Project-Id-Version: GNU MediaGoblin\n" +"Report-Msgid-Bugs-To: http://issues.mediagoblin.org/\n" +"POT-Creation-Date: 2013-05-27 13:54-0500\n" +"PO-Revision-Date: 2013-06-16 01:40+0000\n" +"Last-Translator: m13253 <m13253@hotmail.com>\n" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/mediagoblin/language/zh_TW/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.6\n" +"Language: zh_TW\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: mediagoblin/auth/forms.py:26 +msgid "Username" +msgstr "使用者名稱" + +#: mediagoblin/auth/forms.py:30 mediagoblin/auth/forms.py:45 +#: mediagoblin/tests/test_util.py:110 +msgid "Password" +msgstr "密碼" + +#: mediagoblin/auth/forms.py:34 +msgid "Email address" +msgstr "Email 位址" + +#: mediagoblin/auth/forms.py:41 +msgid "Username or Email" +msgstr "使用者名稱或 email" + +#: mediagoblin/auth/forms.py:52 +msgid "Username or email" +msgstr "使用者名稱或 email" + +#: mediagoblin/auth/tools.py:31 +msgid "Invalid User name or email address." +msgstr "無效的使用者名稱或 email 位置。" + +#: mediagoblin/auth/tools.py:32 +msgid "This field does not take email addresses." +msgstr "本欄位不接受 email 位置。" + +#: mediagoblin/auth/tools.py:33 +msgid "This field requires an email address." +msgstr "本欄位需要 email 位置。" + +#: mediagoblin/auth/views.py:54 +msgid "Sorry, registration is disabled on this instance." +msgstr "抱歉,本站已經暫停註冊。" + +#: mediagoblin/auth/views.py:68 +msgid "Sorry, a user with that name already exists." +msgstr "抱歉,這個使用者名稱已經存在。" + +#: mediagoblin/auth/views.py:72 +msgid "Sorry, a user with that email address already exists." +msgstr "抱歉,此 email 位置已經被註冊了。" + +#: mediagoblin/auth/views.py:182 +msgid "" +"Your email address has been verified. You may now login, edit your profile, " +"and submit images!" +msgstr "您的 email 位址已被認證。您已經可以登入,編輯您的個人檔案並上傳圖片!" + +#: mediagoblin/auth/views.py:188 +msgid "The verification key or user id is incorrect" +msgstr "認證碼或是使用者 ID 錯誤" + +#: mediagoblin/auth/views.py:206 +msgid "You must be logged in so we know who to send the email to!" +msgstr "您必須登入,我們才知道信要送給誰!" + +#: mediagoblin/auth/views.py:214 +msgid "You've already verified your email address!" +msgstr "您的電子郵件已經確認了!" + +#: mediagoblin/auth/views.py:227 +msgid "Resent your verification email." +msgstr "重送認證信。" + +#: mediagoblin/auth/views.py:258 +msgid "" +"If that email address (case sensitive!) is registered an email has been sent" +" with instructions on how to change your password." +msgstr "如果那 email 位置 (請注意大小寫) 已經註冊,寫有修改密碼步驟的 email 已經送出。" + +#: mediagoblin/auth/views.py:269 +msgid "Couldn't find someone with that username." +msgstr "找不到相關的使用者名稱。" + +#: mediagoblin/auth/views.py:272 +msgid "" +"An email has been sent with instructions on how to change your password." +msgstr "修改密碼的指示已經由電子郵件寄送到您的信箱。" + +#: mediagoblin/auth/views.py:279 +msgid "" +"Could not send password recovery email as your username is inactive or your " +"account's email address has not been verified." +msgstr "無法傳送密碼回復信件,因為您的使用者名稱已失效或是帳號尚未認證。" + +#: mediagoblin/auth/views.py:336 +msgid "You can now log in using your new password." +msgstr "您現在可以用新的密碼登入了!" + +#: mediagoblin/edit/forms.py:25 mediagoblin/edit/forms.py:82 +#: mediagoblin/submit/forms.py:28 mediagoblin/submit/forms.py:47 +#: mediagoblin/user_pages/forms.py:45 +msgid "Title" +msgstr "標題" + +#: mediagoblin/edit/forms.py:28 mediagoblin/submit/forms.py:31 +msgid "Description of this work" +msgstr "這個作品的描述" + +#: mediagoblin/edit/forms.py:29 mediagoblin/edit/forms.py:52 +#: mediagoblin/edit/forms.py:86 mediagoblin/submit/forms.py:32 +#: mediagoblin/submit/forms.py:51 mediagoblin/user_pages/forms.py:49 +msgid "" +"You can use\n" +" <a href=\"http://daringfireball.net/projects/markdown/basics\">\n" +" Markdown</a> for formatting." +msgstr "您可以用 <a href=\"http://markdown.tw\">Markdown</a> 來排版。" + +#: mediagoblin/edit/forms.py:33 mediagoblin/submit/forms.py:36 +msgid "Tags" +msgstr "標籤" + +#: mediagoblin/edit/forms.py:35 mediagoblin/submit/forms.py:38 +msgid "Separate tags by commas." +msgstr "用逗號分隔標籤。" + +#: mediagoblin/edit/forms.py:38 mediagoblin/edit/forms.py:90 +msgid "Slug" +msgstr "簡稱" + +#: mediagoblin/edit/forms.py:39 mediagoblin/edit/forms.py:91 +msgid "The slug can't be empty" +msgstr "簡稱不能為空白" + +#: mediagoblin/edit/forms.py:40 +msgid "" +"The title part of this media's address. You usually don't need to change " +"this." +msgstr "此媒體網址的標題部份。通常不需要修改。" + +#: mediagoblin/edit/forms.py:44 mediagoblin/submit/forms.py:41 +#: mediagoblin/templates/mediagoblin/utils/license.html:20 +msgid "License" +msgstr "授權" + +#: mediagoblin/edit/forms.py:50 +msgid "Bio" +msgstr "自我介紹" + +#: mediagoblin/edit/forms.py:56 +msgid "Website" +msgstr "網站" + +#: mediagoblin/edit/forms.py:58 +msgid "This address contains errors" +msgstr "本網址出錯了" + +#: mediagoblin/edit/forms.py:63 +msgid "License preference" +msgstr "授權偏好" + +#: mediagoblin/edit/forms.py:69 +msgid "This will be your default license on upload forms." +msgstr "在上傳頁面,這將會是您預設的授權模式。" + +#: mediagoblin/edit/forms.py:71 +msgid "Email me when others comment on my media" +msgstr "當有人對我的媒體留言時寄信給我" + +#: mediagoblin/edit/forms.py:83 +msgid "The title can't be empty" +msgstr "標題不能是空的" + +#: mediagoblin/edit/forms.py:85 mediagoblin/submit/forms.py:50 +#: mediagoblin/user_pages/forms.py:48 +msgid "Description of this collection" +msgstr "這個蒐藏的描述" + +#: mediagoblin/edit/forms.py:92 +msgid "" +"The title part of this collection's address. You usually don't need to " +"change this." +msgstr "此蒐藏網址的標題部份,通常不需要修改。" + +#: mediagoblin/edit/forms.py:99 +msgid "Old password" +msgstr "舊的密碼" + +#: mediagoblin/edit/forms.py:101 +msgid "Enter your old password to prove you own this account." +msgstr "輸入您的舊密碼來證明您擁有這個帳號。" + +#: mediagoblin/edit/forms.py:104 +msgid "New password" +msgstr "新密碼" + +#: mediagoblin/edit/views.py:67 +msgid "An entry with that slug already exists for this user." +msgstr "這個簡稱已經被其他人用了" + +#: mediagoblin/edit/views.py:85 +msgid "You are editing another user's media. Proceed with caution." +msgstr "您正在修改別人的媒體,請小心操作。" + +#: mediagoblin/edit/views.py:155 +#, python-format +msgid "You added the attachment %s!" +msgstr "您加上了附件「%s」!" + +#: mediagoblin/edit/views.py:182 +msgid "You can only edit your own profile." +msgstr "您只能修改您自己的個人檔案。" + +#: mediagoblin/edit/views.py:188 +msgid "You are editing a user's profile. Proceed with caution." +msgstr "您正在修改別人的個人檔案,請小心操作。" + +#: mediagoblin/edit/views.py:204 +msgid "Profile changes saved" +msgstr "個人檔案修改已儲存" + +#: mediagoblin/edit/views.py:240 +msgid "Account settings saved" +msgstr "帳號設定已儲存" + +#: mediagoblin/edit/views.py:274 +msgid "You need to confirm the deletion of your account." +msgstr "您必須要確認是否刪除您的帳號。" + +#: mediagoblin/edit/views.py:310 mediagoblin/submit/views.py:138 +#: mediagoblin/user_pages/views.py:222 +#, python-format +msgid "You already have a collection called \"%s\"!" +msgstr "您已經有一個稱做「%s」的蒐藏了!" + +#: mediagoblin/edit/views.py:314 +msgid "A collection with that slug already exists for this user." +msgstr "這個使用者已經有使用該簡稱的蒐藏了。" + +#: mediagoblin/edit/views.py:329 +msgid "You are editing another user's collection. Proceed with caution." +msgstr "您正在修改別人的蒐藏,請小心操作。" + +#: mediagoblin/edit/views.py:348 +msgid "Wrong password" +msgstr "密碼錯誤" + +#: mediagoblin/edit/views.py:363 +msgid "Your password was changed successfully" +msgstr "您的密碼已經成功修改" + +#: mediagoblin/gmg_commands/assetlink.py:60 +msgid "Cannot link theme... no theme set\n" +msgstr "無法連結佈景…沒有此佈景\n" + +#: mediagoblin/gmg_commands/assetlink.py:73 +msgid "No asset directory for this theme\n" +msgstr "此佈景沒有素材目錄\n" + +#: mediagoblin/gmg_commands/assetlink.py:76 +msgid "However, old link directory symlink found; removed.\n" +msgstr "但是舊的目錄連結已經找到並移除。\n" + +#: mediagoblin/gmg_commands/assetlink.py:112 +#, python-format +msgid "Could not link \"%s\": %s exists and is not a symlink\n" +msgstr "無法連結「%s」:%s 存在,且不是符號連結\n" + +#: mediagoblin/gmg_commands/assetlink.py:119 +#, python-format +msgid "Skipping \"%s\"; already set up.\n" +msgstr "跳過「%s」,已經建置完成。\n" + +#: mediagoblin/gmg_commands/assetlink.py:124 +#, python-format +msgid "Old link found for \"%s\"; removing.\n" +msgstr "找到「%s」舊的連結,刪除中。\n" + +#: mediagoblin/meddleware/csrf.py:134 +msgid "" +"CSRF cookie not present. This is most likely the result of a cookie blocker " +"or somesuch.<br/>Make sure to permit the settings of cookies for this " +"domain." +msgstr "跨網站存取 (CSRF) 的 cookie 不存在,有可能是 cookie 阻擋程式之類的程式導致的。<br/>請允許此網域的 cookie 設定。" + +#: mediagoblin/media_types/__init__.py:111 +#: mediagoblin/media_types/__init__.py:155 +msgid "Sorry, I don't support that file type :(" +msgstr "抱歉,我不支援這樣的檔案格式 :(" + +#: mediagoblin/media_types/pdf/processing.py:136 +msgid "unoconv failing to run, check log file" +msgstr "unoconv 無法執行,請檢查紀錄檔" + +#: mediagoblin/media_types/video/processing.py:37 +msgid "Video transcoding failed" +msgstr "影像轉碼失敗" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:24 +msgid "Location" +msgstr "位置" + +#: mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html:52 +#, python-format +msgid "View on <a href=\"%(osm_url)s\">OpenStreetMap</a>" +msgstr "在 <a href=\"%(osm_url)s\">OpenStreetMap</a> 上觀看" + +#: mediagoblin/plugins/oauth/forms.py:29 +msgid "Allow" +msgstr "允許" + +#: mediagoblin/plugins/oauth/forms.py:30 +msgid "Deny" +msgstr "拒絕" + +#: mediagoblin/plugins/oauth/forms.py:34 +msgid "Name" +msgstr "名稱" + +#: mediagoblin/plugins/oauth/forms.py:35 +msgid "The name of the OAuth client" +msgstr "OAuth 用戶程式的名稱" + +#: mediagoblin/plugins/oauth/forms.py:36 +msgid "Description" +msgstr "描述" + +#: mediagoblin/plugins/oauth/forms.py:38 +msgid "" +"This will be visible to users allowing your\n" +" application to authenticate as them." +msgstr "本描述將會被進行應用程式認証的使用者看到。" + +#: mediagoblin/plugins/oauth/forms.py:40 +msgid "Type" +msgstr "類型" + +#: mediagoblin/plugins/oauth/forms.py:45 +msgid "" +"<strong>Confidential</strong> - The client can\n" +" make requests to the GNU MediaGoblin instance that can not be\n" +" intercepted by the user agent (e.g. server-side client).<br />\n" +" <strong>Public</strong> - The client can't make confidential\n" +" requests to the GNU MediaGoblin instance (e.g. client-side\n" +" JavaScript client)." +msgstr "<strong>秘密</strong> — OAuth 用戶程式可以對 GNU MediaGoblin 站台發送不被使用者代理攔截的請求 (例如伺服端的用戶程式)。\n<strong>公開</strong> — OAuth 用戶程式無法對 GNU MediaGoblin 站台發送秘密的請求 (例如客戶端的 JavaScript 用戶程式)。" + +#: mediagoblin/plugins/oauth/forms.py:52 +msgid "Redirect URI" +msgstr "重定向 URI" + +#: mediagoblin/plugins/oauth/forms.py:54 +msgid "" +"The redirect URI for the applications, this field\n" +" is <strong>required</strong> for public clients." +msgstr "此應用程式的重定向 URI,本欄位在公開類型的 OAuth 用戶程式為必填。" + +#: mediagoblin/plugins/oauth/forms.py:66 +msgid "This field is required for public clients" +msgstr "本欄位在公開類型的用戶程式為必填" + +#: mediagoblin/plugins/oauth/views.py:56 +msgid "The client {0} has been registered!" +msgstr "OAuth 用戶程式 {0} 註冊完成!" + +#: mediagoblin/plugins/oauth/templates/oauth/client/connections.html:22 +msgid "OAuth client connections" +msgstr "OAuth 用戶程式連線" + +#: mediagoblin/plugins/oauth/templates/oauth/client/list.html:22 +msgid "Your OAuth clients" +msgstr "您的 OAuth 用戶程式" + +#: mediagoblin/plugins/oauth/templates/oauth/client/register.html:29 +#: mediagoblin/templates/mediagoblin/submit/collection.html:30 +#: mediagoblin/templates/mediagoblin/submit/start.html:34 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:68 +msgid "Add" +msgstr "增加" + +#: mediagoblin/processing/__init__.py:193 +msgid "Invalid file given for media type." +msgstr "指定錯誤的媒體類別!" + +#: mediagoblin/submit/forms.py:26 +msgid "File" +msgstr "檔案" + +#: mediagoblin/submit/views.py:49 +msgid "You must provide a file." +msgstr "您必須提供一個檔案" + +#: mediagoblin/submit/views.py:93 +msgid "Woohoo! Submitted!" +msgstr "啊哈!PO 上去啦!" + +#: mediagoblin/submit/views.py:144 +#, python-format +msgid "Collection \"%s\" added!" +msgstr "蒐藏「%s」新增完成!" + +#: mediagoblin/templates/mediagoblin/base.html:67 +msgid "Verify your email!" +msgstr "確認您的電子郵件" + +#: mediagoblin/templates/mediagoblin/base.html:68 +msgid "log out" +msgstr "登出" + +#: mediagoblin/templates/mediagoblin/base.html:73 +#: mediagoblin/templates/mediagoblin/auth/login.html:28 +#: mediagoblin/templates/mediagoblin/auth/login.html:36 +#: mediagoblin/templates/mediagoblin/auth/login.html:54 +msgid "Log in" +msgstr "登入" + +#: mediagoblin/templates/mediagoblin/base.html:82 +#, python-format +msgid "<a href=\"%(user_url)s\">%(user_name)s</a>'s account" +msgstr "<a href=\"%(user_url)s\">%(user_name)s</a> 的帳號" + +#: mediagoblin/templates/mediagoblin/base.html:89 +msgid "Change account settings" +msgstr "更改帳號設定" + +#: mediagoblin/templates/mediagoblin/base.html:93 +#: mediagoblin/templates/mediagoblin/base.html:108 +#: mediagoblin/templates/mediagoblin/admin/panel.html:21 +#: mediagoblin/templates/mediagoblin/admin/panel.html:26 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:21 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:26 +msgid "Media processing panel" +msgstr "媒體處理面板" + +#: mediagoblin/templates/mediagoblin/base.html:96 +msgid "Log out" +msgstr "登出" + +#: mediagoblin/templates/mediagoblin/base.html:99 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:156 +msgid "Add media" +msgstr "新增媒體" + +#: mediagoblin/templates/mediagoblin/base.html:102 +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:41 +msgid "Create new collection" +msgstr "新增新的蒐藏" + +#: mediagoblin/templates/mediagoblin/error.html:24 +msgid "Image of goblin stressing out" +msgstr "滿臉問號的哥布林" + +#: mediagoblin/templates/mediagoblin/root.html:32 +msgid "Most recent media" +msgstr "最新的媒體" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:29 +msgid "" +"Here you can track the state of media being processed on this instance." +msgstr "此處您可以追蹤本站台處理媒體的狀態。" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:32 +msgid "Media in-processing" +msgstr "媒體處理中" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:58 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:56 +msgid "No media in-processing" +msgstr "沒有正在處理中的媒體" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:61 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:59 +msgid "These uploads failed to process:" +msgstr "無法處理這些上傳內容:" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:90 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:86 +msgid "No failed entries!" +msgstr "沒有失敗的紀錄!" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:92 +msgid "Last 10 successful uploads" +msgstr "最近 10 次成功上傳的紀錄" + +#: mediagoblin/templates/mediagoblin/admin/panel.html:112 +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:107 +msgid "No processed entries, yet!" +msgstr "現在還沒有處理的紀錄!" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:28 +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:36 +msgid "Set your new password" +msgstr "設定您的新密碼" + +#: mediagoblin/templates/mediagoblin/auth/change_fp.html:39 +msgid "Set password" +msgstr "設定新密碼" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:23 +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:31 +msgid "Recover password" +msgstr "找回密碼" + +#: mediagoblin/templates/mediagoblin/auth/forgot_password.html:34 +msgid "Send instructions" +msgstr "送出指示" + +#: mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to change your GNU MediaGoblin password, open the following URL in \n" +"your web browser:\n" +"\n" +"%(verification_url)s\n" +"\n" +"If you think this is an error, just ignore this email and continue being\n" +"a happy goblin!" +msgstr "%(username)s 您好:\n\n要修改 GNU MediaGoblin 的密碼,請在您的瀏覽器中打開下面的網址:\n\n%(verification_url)s\n\n如果您認為這個是個誤會,請忽略此封信件,繼續當個快樂的哥布林!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:39 +msgid "Logging in failed!" +msgstr "登入失敗!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:44 +msgid "Don't have an account yet?" +msgstr "還沒有帳號嗎?" + +#: mediagoblin/templates/mediagoblin/auth/login.html:45 +msgid "Create one here!" +msgstr "在這裡建立一個吧!" + +#: mediagoblin/templates/mediagoblin/auth/login.html:51 +msgid "Forgot your password?" +msgstr "忘了密碼嗎?" + +#: mediagoblin/templates/mediagoblin/auth/register.html:28 +#: mediagoblin/templates/mediagoblin/auth/register.html:36 +msgid "Create an account!" +msgstr "建立一個帳號!" + +#: mediagoblin/templates/mediagoblin/auth/register.html:40 +msgid "Create" +msgstr "建立" + +#: mediagoblin/templates/mediagoblin/auth/verification_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"\n" +"to activate your GNU MediaGoblin account, open the following URL in\n" +"your web browser:\n" +"\n" +"%(verification_url)s" +msgstr "%(username)s 您好:\n\n要啟動 GNU MediaGoblin 帳號,請在您的瀏覽器中打開下面的網址:\n\n%(verification_url)s" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:21 +#, python-format +msgid "" +"Powered by <a href=\"http://mediagoblin.org/\" title='Version " +"%(version)s'>MediaGoblin</a>, a <a href=\"http://gnu.org/\">GNU</a> project." +msgstr "本站使用 <a href=\"http://mediagoblin.org/\" title='Version %(version)s'>MediaGoblin</a>,這是一個 <a href=\"http://gnu.org/\">GNU</a> 專案。" + +#: mediagoblin/templates/mediagoblin/bits/base_footer.html:24 +#, python-format +msgid "" +"Released under the <a " +"href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a>. <a " +"href=\"%(source_link)s\">Source code</a> available." +msgstr "以 <a href=\"http://www.fsf.org/licensing/licenses/agpl-3.0.html\">AGPL</a> 授權釋出。備有<a href=\"%(source_link)s\">原始碼</a>。" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:20 +msgid "Explore" +msgstr "探索" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:22 +msgid "Hi there, welcome to this MediaGoblin site!" +msgstr "嘿!歡迎來到 MediaGoblin 站台! " + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:24 +msgid "" +"This site is running <a href=\"http://mediagoblin.org\">MediaGoblin</a>, an " +"extraordinarily great piece of media hosting software." +msgstr "本站使用 <a href=\"http://mediagoblin.org\">MediaGoblin</a> — 與眾不同的媒體分享網站。" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:25 +msgid "" +"To add your own media, place comments, and more, you can log in with your " +"MediaGoblin account." +msgstr "您可以登入您的 MediaGoblin 帳號以進行上傳媒體、張貼評論等等。" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:27 +msgid "Don't have one yet? It's easy!" +msgstr "沒有帳號嗎?開帳號很簡單!" + +#: mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html:28 +#, python-format +msgid "" +"<a class=\"button_action_highlight\" href=\"%(register_url)s\">Create an account at this site</a>\n" +" or\n" +" <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">Set up MediaGoblin on your own server</a>" +msgstr "<a class=\"button_action_highlight\" href=\"%(register_url)s\">在本站建立您的帳號</a>\n 或是\n <a class=\"button_action\" href=\"http://wiki.mediagoblin.org/HackingHowto\">在您自己的伺服器上安裝 MediaGoblin</a>" + +#: mediagoblin/templates/mediagoblin/bits/logo.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html:23 +msgid "MediaGoblin logo" +msgstr "MediaGoblin 標誌" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:23 +#: mediagoblin/templates/mediagoblin/edit/attachments.html:35 +#, python-format +msgid "Editing attachments for %(media_title)s" +msgstr "編輯 %(media_title)s 的附件" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:182 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:198 +msgid "Attachments" +msgstr "附件" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:57 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:204 +msgid "Add attachment" +msgstr "新增附件" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:61 +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit.html:41 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:32 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:46 +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:67 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:48 +msgid "Cancel" +msgstr "取消" + +#: mediagoblin/templates/mediagoblin/edit/attachments.html:63 +#: mediagoblin/templates/mediagoblin/edit/edit.html:42 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:55 +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:33 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:40 +msgid "Save changes" +msgstr "儲存變更" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:28 +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:38 +#, python-format +msgid "Changing %(username)s's password" +msgstr "更改 %(username)s 的密碼" + +#: mediagoblin/templates/mediagoblin/edit/change_pass.html:45 +msgid "Save" +msgstr "儲存" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:28 +#, python-format +msgid "Really delete user '%(user_name)s' and all related media/comments?" +msgstr "真的要刪除使用者「%(user_name)s」以及相關的媒體與留言?" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:35 +msgid "Yes, really delete my account" +msgstr "是的,我真的要把我的帳號刪除" + +#: mediagoblin/templates/mediagoblin/edit/delete_account.html:44 +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:48 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:49 +msgid "Delete permanently" +msgstr "永久刪除" + +#: mediagoblin/templates/mediagoblin/edit/edit.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit.html:35 +#, python-format +msgid "Editing %(media_title)s" +msgstr "編輯 %(media_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:28 +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:40 +#, python-format +msgid "Changing %(username)s's account settings" +msgstr "正在改變 %(username)s 的帳號設定" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:46 +msgid "Change your password." +msgstr "更改您的密碼。" + +#: mediagoblin/templates/mediagoblin/edit/edit_account.html:62 +msgid "Delete my account" +msgstr "刪除我的帳號" + +#: mediagoblin/templates/mediagoblin/edit/edit_collection.html:29 +#, python-format +msgid "Editing %(collection_title)s" +msgstr "編輯 %(collection_title)s" + +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:23 +#: mediagoblin/templates/mediagoblin/edit/edit_profile.html:34 +#, python-format +msgid "Editing %(username)s's profile" +msgstr "編輯 %(username)s 的個人檔案" + +#: mediagoblin/templates/mediagoblin/listings/collection.html:30 +#: mediagoblin/templates/mediagoblin/listings/collection.html:35 +#: mediagoblin/templates/mediagoblin/listings/tag.html:30 +#: mediagoblin/templates/mediagoblin/listings/tag.html:35 +#, python-format +msgid "Media tagged with: %(tag_name)s" +msgstr "這個媒體具有以下標籤:%(tag_name)s" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:34 +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:56 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:65 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:136 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:55 +msgid "Download" +msgstr "下載" + +#: mediagoblin/templates/mediagoblin/media_displays/ascii.html:38 +msgid "Original" +msgstr "原始檔" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:44 +msgid "" +"Sorry, this audio will not work because \n" +"\tyour web browser does not support HTML5 \n" +"\taudio." +msgstr "抱歉,此聲音無法播放,因為您的瀏覽器不支援 HTML5 音訊。" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:47 +msgid "" +"You can get a modern web browser that \n" +"\tcan play the audio at <a href=\"http://getfirefox.com\">\n" +"\t http://getfirefox.com</a>!" +msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此聲音的瀏覽器!" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:60 +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:71 +#: mediagoblin/templates/mediagoblin/media_displays/video.html:61 +msgid "Original file" +msgstr "原始檔案" + +#: mediagoblin/templates/mediagoblin/media_displays/audio.html:63 +msgid "WebM file (Vorbis codec)" +msgstr "WebM 檔案 (Vorbis 編碼)" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:59 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:87 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:93 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:99 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:105 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:59 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:65 +#, python-format +msgid "Image for %(media_title)s" +msgstr " %(media_title)s 的照片" + +#: mediagoblin/templates/mediagoblin/media_displays/pdf.html:79 +msgid "PDF file" +msgstr "PDF 檔" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:112 +msgid "Toggle Rotate" +msgstr "切換旋轉" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:113 +msgid "Perspective" +msgstr "透視" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:116 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:117 +msgid "Front" +msgstr "正面" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:120 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:121 +msgid "Top" +msgstr "頂面" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:124 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:125 +msgid "Side" +msgstr "側面" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:130 +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:131 +msgid "WebGL" +msgstr "WebGL" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:138 +msgid "Download model" +msgstr "下載模型" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:146 +msgid "File Format" +msgstr "檔案格式" + +#: mediagoblin/templates/mediagoblin/media_displays/stl.html:148 +msgid "Object Height" +msgstr "物件高度" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:44 +msgid "" +"Sorry, this video will not work because\n" +" your web browser does not support HTML5 \n" +" video." +msgstr "抱歉,由於您的瀏覽器不支援 HTML5 影片,本影片無法播放" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:47 +msgid "" +"You can get a modern web browser that \n" +" can play this video at <a href=\"http://getfirefox.com\">\n" +" http://getfirefox.com</a>!" +msgstr "您可以在 <a href=\"http://getfirefox.com\">http://getfirefox.com</a> 取得可以播放此影片的先進瀏覽器。" + +#: mediagoblin/templates/mediagoblin/media_displays/video.html:69 +msgid "WebM file (640p; VP8/Vorbis)" +msgstr "WebM 檔案 (640p; VP8/Vorbis)" + +#: mediagoblin/templates/mediagoblin/submit/collection.html:26 +msgid "Add a collection" +msgstr "新增蒐藏" + +#: mediagoblin/templates/mediagoblin/submit/start.html:23 +#: mediagoblin/templates/mediagoblin/submit/start.html:30 +msgid "Add your media" +msgstr "加入您的媒體" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:30 +#, python-format +msgid "%(collection_title)s (%(username)s's collection)" +msgstr "%(collection_title)s (%(username)s 的蒐藏)" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:39 +#, python-format +msgid "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "%(collection_title)s by <a href=\"%(user_url)s\">%(username)s</a>" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:52 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:79 +msgid "Edit" +msgstr "編輯" + +#: mediagoblin/templates/mediagoblin/user_pages/collection.html:56 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:83 +msgid "Delete" +msgstr "刪除" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html:30 +#: mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html:30 +#, python-format +msgid "Really delete %(title)s?" +msgstr "真的要刪除 %(title)s?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:31 +#, python-format +msgid "Really remove %(media_title)s from %(collection_title)s?" +msgstr "確定要從 %(collection_title)s 移除 %(media_title)s 嗎?" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html:54 +msgid "Remove" +msgstr "移除" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:21 +#, python-format +msgid "%(username)s's collections" +msgstr "%(username)s 的蒐藏" + +#: mediagoblin/templates/mediagoblin/user_pages/collection_list.html:28 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s collections" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的蒐藏" + +#: mediagoblin/templates/mediagoblin/user_pages/comment_email.txt:19 +#, python-format +msgid "" +"Hi %(username)s,\n" +"%(comment_author)s commented on your post (%(comment_url)s) at %(instance_name)s\n" +msgstr "%(username)s 您好:\n%(comment_author)s 在 %(instance_name)s 對您的內容 (%(comment_url)s) 張貼留言\n" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:30 +#, python-format +msgid "%(username)s's media" +msgstr "%(username)s的媒體" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:38 +#, python-format +msgid "" +"<a href=\"%(user_url)s\">%(username)s</a>'s media with tag <a " +"href=\"%(tag_url)s\">%(tag)s</a>" +msgstr "標籤為 <a href=\"%(tag_url)s\">%(tag)s</a> 的 <a href=\"%(user_url)s\">%(username)s</a> 的媒體" + +#: mediagoblin/templates/mediagoblin/user_pages/gallery.html:48 +#, python-format +msgid "<a href=\"%(user_url)s\">%(username)s</a>'s media" +msgstr "<a href=\"%(user_url)s\">%(username)s</a> 的媒體" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:38 +#, python-format +msgid "❖ Browsing media by <a href=\"%(user_url)s\">%(username)s</a>" +msgstr "❖ 瀏覽 <a href=\"%(user_url)s\">%(username)s</a> 的媒體" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:95 +msgid "Add a comment" +msgstr "新增留言" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:104 +msgid "Add this comment" +msgstr "增加留言" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:132 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:152 +#: mediagoblin/templates/mediagoblin/user_pages/media.html:164 +#, python-format +msgid "%(formatted_time)s ago" +msgstr "%(formatted_time)s 前" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:150 +msgid "Added" +msgstr "新增於" + +#: mediagoblin/templates/mediagoblin/user_pages/media.html:161 +msgid "Created" +msgstr "建立於" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:28 +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:40 +#, python-format +msgid "Add “%(media_title)s” to a collection" +msgstr "加入 “%(media_title)s” 至蒐藏" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:54 +msgid "+" +msgstr "+" + +#: mediagoblin/templates/mediagoblin/user_pages/media_collect.html:58 +msgid "Add a new collection" +msgstr "新增新的蒐藏" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:29 +msgid "" +"You can track the state of media being processed for your gallery here." +msgstr "您可以在這裡追蹤您的藝廊中媒體處理的狀態。" + +#: mediagoblin/templates/mediagoblin/user_pages/processing_panel.html:89 +msgid "Your last 10 successful uploads" +msgstr "您的最近 10 次成功上傳的紀錄" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:31 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:89 +#, python-format +msgid "%(username)s's profile" +msgstr "%(username)s 的個人檔案" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:43 +msgid "Sorry, no such user found." +msgstr "抱歉,找不到這個使用者。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:50 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:70 +msgid "Email verification needed" +msgstr "需要認證電子郵件" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:53 +msgid "Almost done! Your account still needs to be activated." +msgstr "快完成了!但您需要啟用您的帳號。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:58 +msgid "" +"An email should arrive in a few moments with instructions on how to do so." +msgstr "啟用步驟的 email 將會寄到您的信箱。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:62 +msgid "In case it doesn't:" +msgstr "如果仍然無法認證,您可以:" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:65 +msgid "Resend verification email" +msgstr "重送認證信" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:73 +msgid "" +"Someone has registered an account with this username, but it still has to be" +" activated." +msgstr "有人用了這個帳號登錄了,但是這個帳號需要被啟用。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:79 +#, python-format +msgid "" +"If you are that person but you've lost your verification email, you can <a " +"href=\"%(login_url)s\">log in</a> and resend it." +msgstr "如果您就是本人但是掉了認證信,您可以 <a href=\"%(login_url)s\">登入</a> 然後重送一次。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:96 +msgid "Here's a spot to tell others about yourself." +msgstr "這個地方能讓您向他人介紹自己。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:100 +#: mediagoblin/templates/mediagoblin/user_pages/user.html:117 +msgid "Edit profile" +msgstr "編輯個人檔案" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:105 +msgid "This user hasn't filled in their profile (yet)." +msgstr "這個使用者(還)沒有填寫個人檔案。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:124 +msgid "Browse collections" +msgstr "瀏覽蒐藏" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:137 +#, python-format +msgid "View all of %(username)s's media" +msgstr "查看 %(username)s 的全部媒體" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:150 +msgid "" +"This is where your media will appear, but you don't seem to have added " +"anything yet." +msgstr "此處是您的媒體會出現的地方,但是似乎還沒有加入任何東西。" + +#: mediagoblin/templates/mediagoblin/user_pages/user.html:162 +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:84 +#: mediagoblin/templates/mediagoblin/utils/object_gallery.html:70 +msgid "There doesn't seem to be any media here yet..." +msgstr "那裡好像還沒有任何的媒體…" + +#: mediagoblin/templates/mediagoblin/utils/collection_gallery.html:49 +msgid "(remove)" +msgstr " (移除)" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:21 +msgid "Collected in" +msgstr "蒐集了" + +#: mediagoblin/templates/mediagoblin/utils/collections.html:40 +msgid "Add to a collection" +msgstr "加入至蒐藏" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:21 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:21 +msgid "feed icon" +msgstr "feed 圖示" + +#: mediagoblin/templates/mediagoblin/utils/feed_link.html:23 +#: mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html:23 +msgid "Atom feed" +msgstr "Atom feed" + +#: mediagoblin/templates/mediagoblin/utils/license.html:25 +msgid "All rights reserved" +msgstr "版權所有" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:39 +msgid "← Newer" +msgstr "← 更新的" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:45 +msgid "Older →" +msgstr "更舊的 →" + +#: mediagoblin/templates/mediagoblin/utils/pagination.html:48 +msgid "Go to page:" +msgstr "跳到頁數:" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:28 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:33 +msgid "newer" +msgstr "更新的" + +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:39 +#: mediagoblin/templates/mediagoblin/utils/prev_next.html:44 +msgid "older" +msgstr "更舊的" + +#: mediagoblin/templates/mediagoblin/utils/tags.html:20 +msgid "Tagged with" +msgstr "標籤" + +#: mediagoblin/tools/exif.py:83 +msgid "Could not read the image file." +msgstr "無法讀取圖片檔案。" + +#: mediagoblin/tools/response.py:35 +msgid "Oops!" +msgstr "糟糕!" + +#: mediagoblin/tools/response.py:36 +msgid "An error occured" +msgstr "發生錯誤" + +#: mediagoblin/tools/response.py:51 +msgid "Operation not allowed" +msgstr "操作不允許" + +#: mediagoblin/tools/response.py:52 +msgid "" +"Sorry Dave, I can't let you do that!</p><p>You have tried to perform a " +"function that you are not allowed to. Have you been trying to delete all " +"user accounts again?" +msgstr "Dave 對不起,我不能讓你這樣做!</p><p>您正在試著操作不允許您使用的功能。您打算刪除所有使用者的帳號嗎?" + +#: mediagoblin/tools/response.py:60 +msgid "" +"There doesn't seem to be a page at this address. Sorry!</p><p>If you're sure" +" the address is correct, maybe the page you're looking for has been moved or" +" deleted." +msgstr "不好意思,看起來這個網址上沒有網頁。</p><p>如果您確定這個網址是正確的,您在尋找的頁面可能已經移動或是被刪除了。" + +#: mediagoblin/tools/timesince.py:62 +msgid "year" +msgstr "年" + +#: mediagoblin/tools/timesince.py:63 +msgid "month" +msgstr "月" + +#: mediagoblin/tools/timesince.py:64 +msgid "week" +msgstr "週" + +#: mediagoblin/tools/timesince.py:65 +msgid "day" +msgstr "日" + +#: mediagoblin/tools/timesince.py:66 +msgid "hour" +msgstr "小時" + +#: mediagoblin/tools/timesince.py:67 +msgid "minute" +msgstr "分" + +#: mediagoblin/user_pages/forms.py:23 +msgid "Comment" +msgstr "留言" + +#: mediagoblin/user_pages/forms.py:25 +msgid "" +"You can use <a " +"href=\"http://daringfireball.net/projects/markdown/basics\">Markdown</a> for" +" formatting." +msgstr "您可以用 <a href=\"http://markdown.tw\">Markdown</a> 來排版。" + +#: mediagoblin/user_pages/forms.py:31 +msgid "I am sure I want to delete this" +msgstr "我確定我要刪除這個媒體" + +#: mediagoblin/user_pages/forms.py:35 +msgid "I am sure I want to remove this item from the collection" +msgstr "我確定我要從蒐藏中移除此項目" + +#: mediagoblin/user_pages/forms.py:39 +msgid "Collection" +msgstr "蒐藏" + +#: mediagoblin/user_pages/forms.py:40 +msgid "-- Select --" +msgstr "— 請選擇 —" + +#: mediagoblin/user_pages/forms.py:42 +msgid "Include a note" +msgstr "加註" + +#: mediagoblin/user_pages/lib.py:58 +msgid "commented on your post" +msgstr "在您的內容張貼留言" + +#: mediagoblin/user_pages/views.py:169 +msgid "Sorry, comments are disabled." +msgstr "抱歉,留言被關閉。" + +#: mediagoblin/user_pages/views.py:174 +msgid "Oops, your comment was empty." +msgstr "啊,您的留言是空的。" + +#: mediagoblin/user_pages/views.py:180 +msgid "Your comment has been posted!" +msgstr "您的留言已經張貼完成!" + +#: mediagoblin/user_pages/views.py:205 +msgid "Please check your entries and try again." +msgstr "請檢查項目並重試。" + +#: mediagoblin/user_pages/views.py:245 +msgid "You have to select or add a collection" +msgstr "您需要選擇或是新增一個蒐藏" + +#: mediagoblin/user_pages/views.py:256 +#, python-format +msgid "\"%s\" already in collection \"%s\"" +msgstr "「%s」已經在「%s」蒐藏" + +#: mediagoblin/user_pages/views.py:262 +#, python-format +msgid "\"%s\" added to collection \"%s\"" +msgstr "「%s」加入「%s」蒐藏" + +#: mediagoblin/user_pages/views.py:282 +msgid "You deleted the media." +msgstr "您已經刪除此媒體。" + +#: mediagoblin/user_pages/views.py:289 +msgid "The media was not deleted because you didn't check that you were sure." +msgstr "由於您沒有勾選確認,該媒體沒有被移除。" + +#: mediagoblin/user_pages/views.py:296 +msgid "You are about to delete another user's media. Proceed with caution." +msgstr "您正在刪除別人的媒體,請小心操作。" + +#: mediagoblin/user_pages/views.py:370 +msgid "You deleted the item from the collection." +msgstr "您已經從該蒐藏中刪除該項目。" + +#: mediagoblin/user_pages/views.py:374 +msgid "The item was not removed because you didn't check that you were sure." +msgstr "由於您沒有勾選確認,該項目沒有被移除。" + +#: mediagoblin/user_pages/views.py:382 +msgid "" +"You are about to delete an item from another user's collection. Proceed with" +" caution." +msgstr "您正在從別人的蒐藏中刪除項目,請小心操作。" + +#: mediagoblin/user_pages/views.py:415 +#, python-format +msgid "You deleted the collection \"%s\"" +msgstr "您已經刪除「%s」蒐藏。" + +#: mediagoblin/user_pages/views.py:422 +msgid "" +"The collection was not deleted because you didn't check that you were sure." +msgstr "由於您沒有勾選確認,該蒐藏沒有被移除。" + +#: mediagoblin/user_pages/views.py:430 +msgid "" +"You are about to delete another user's collection. Proceed with caution." +msgstr "您正在刪除別人的蒐藏,請小心操作。" diff --git a/mediagoblin/init/__init__.py b/mediagoblin/init/__init__.py new file mode 100644 index 00000000..444c624f --- /dev/null +++ b/mediagoblin/init/__init__.py @@ -0,0 +1,153 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import jinja2 + +from mediagoblin.tools import staticdirect +from mediagoblin.tools.translate import set_available_locales +from mediagoblin.init.config import ( + read_mediagoblin_config, generate_validation_report) +from mediagoblin import mg_globals +from mediagoblin.mg_globals import setup_globals +from mediagoblin.db.open import setup_connection_and_db_from_config, \ + check_db_migrations_current, load_models +from mediagoblin.tools.pluginapi import hook_runall +from mediagoblin.tools.workbench import WorkbenchManager +from mediagoblin.storage import storage_system_from_config + + +class Error(Exception): + pass + + +class ImproperlyConfigured(Error): + pass + + +def setup_locales(): + """Checks which language translations are available and sets them""" + set_available_locales() + + +def setup_global_and_app_config(config_path): + global_config, validation_result = read_mediagoblin_config(config_path) + app_config = global_config['mediagoblin'] + # report errors if necessary + validation_report = generate_validation_report( + global_config, validation_result) + if validation_report: + raise ImproperlyConfigured(validation_report) + + setup_globals( + app_config=app_config, + global_config=global_config) + + return global_config, app_config + + +def setup_database(): + app_config = mg_globals.app_config + + # Load all models for media types (plugins, ...) + load_models(app_config) + + # Set up the database + db = setup_connection_and_db_from_config(app_config) + + check_db_migrations_current(db) + + setup_globals(database=db) + + return db + + +def get_jinja_loader(user_template_path=None, current_theme=None, + plugin_template_paths=None): + """ + Set up the Jinja template loaders, possibly allowing for user + overridden templates. + + (In the future we may have another system for providing theming; + for now this is good enough.) + """ + path_list = [] + + # Add user path first--this takes precedence over everything. + if user_template_path is not None: + path_list.append(jinja2.FileSystemLoader(user_template_path)) + + # Any theme directories in the registry + if current_theme and current_theme.get('templates_dir'): + path_list.append( + jinja2.FileSystemLoader( + current_theme['templates_dir'])) + + # Add plugin template paths next--takes precedence over + # core templates. + if plugin_template_paths is not None: + path_list.extend((jinja2.FileSystemLoader(path) + for path in plugin_template_paths)) + + # Add core templates last. + path_list.append(jinja2.PackageLoader('mediagoblin', 'templates')) + + return jinja2.ChoiceLoader(path_list) + + +def get_staticdirector(app_config): + # At minimum, we need the direct_remote_path + if not 'direct_remote_path' in app_config \ + or not 'theme_web_path' in app_config: + raise ImproperlyConfigured( + "direct_remote_path and theme_web_path must be provided") + + direct_domains = {None: app_config['direct_remote_path'].strip()} + direct_domains['theme'] = app_config['theme_web_path'].strip() + + # Let plugins load additional paths + for plugin_static in hook_runall("static_setup"): + direct_domains[plugin_static.name] = "%s/%s" % ( + app_config['plugin_web_path'].rstrip('/'), + plugin_static.name) + + return staticdirect.StaticDirect( + direct_domains) + + +def setup_storage(): + global_config = mg_globals.global_config + + key_short = 'publicstore' + key_long = "storage:" + key_short + public_store = storage_system_from_config(global_config[key_long]) + + key_short = 'queuestore' + key_long = "storage:" + key_short + queue_store = storage_system_from_config(global_config[key_long]) + + setup_globals( + public_store=public_store, + queue_store=queue_store) + + return public_store, queue_store + + +def setup_workbench(): + app_config = mg_globals.app_config + + workbench_manager = WorkbenchManager(app_config['workbench_path']) + + setup_globals(workbench_manager=workbench_manager) diff --git a/mediagoblin/init/celery/__init__.py b/mediagoblin/init/celery/__init__.py new file mode 100644 index 00000000..169cc935 --- /dev/null +++ b/mediagoblin/init/celery/__init__.py @@ -0,0 +1,99 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys + +from celery import Celery +from mediagoblin.tools.pluginapi import hook_runall + + +MANDATORY_CELERY_IMPORTS = ['mediagoblin.processing.task'] + +DEFAULT_SETTINGS_MODULE = 'mediagoblin.init.celery.dummy_settings_module' + + +def get_celery_settings_dict(app_config, global_config, + force_celery_always_eager=False): + """ + Get a celery settings dictionary from reading the config + """ + if 'celery' in global_config: + celery_conf = global_config['celery'] + else: + celery_conf = {} + + celery_settings = {} + + # Add all celery settings from config + for key, value in celery_conf.iteritems(): + celery_settings[key] = value + + # TODO: use default result stuff here if it exists + + # add mandatory celery imports + celery_imports = celery_settings.setdefault('CELERY_IMPORTS', []) + celery_imports.extend(MANDATORY_CELERY_IMPORTS) + + if force_celery_always_eager: + celery_settings['CELERY_ALWAYS_EAGER'] = True + celery_settings['CELERY_EAGER_PROPAGATES_EXCEPTIONS'] = True + + return celery_settings + + +def setup_celery_app(app_config, global_config, + settings_module=DEFAULT_SETTINGS_MODULE, + force_celery_always_eager=False): + """ + Setup celery without using terrible setup-celery-module hacks. + """ + celery_settings = get_celery_settings_dict( + app_config, global_config, force_celery_always_eager) + celery_app = Celery() + celery_app.config_from_object(celery_settings) + + hook_runall('celery_setup', celery_app) + + +def setup_celery_from_config(app_config, global_config, + settings_module=DEFAULT_SETTINGS_MODULE, + force_celery_always_eager=False, + set_environ=True): + """ + Take a mediagoblin app config and try to set up a celery settings + module from this. + + Args: + - app_config: the application config section + - global_config: the entire ConfigObj loaded config, all sections + - settings_module: the module to populate, as a string + - force_celery_always_eager: whether or not to force celery into + always eager mode; good for development and small installs + - set_environ: if set, this will CELERY_CONFIG_MODULE to the + settings_module + """ + celery_settings = get_celery_settings_dict( + app_config, global_config, force_celery_always_eager) + + __import__(settings_module) + this_module = sys.modules[settings_module] + + for key, value in celery_settings.iteritems(): + setattr(this_module, key, value) + + if set_environ: + os.environ['CELERY_CONFIG_MODULE'] = settings_module diff --git a/mediagoblin/init/celery/dummy_settings_module.py b/mediagoblin/init/celery/dummy_settings_module.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mediagoblin/init/celery/dummy_settings_module.py diff --git a/mediagoblin/init/celery/from_celery.py b/mediagoblin/init/celery/from_celery.py new file mode 100644 index 00000000..b395a826 --- /dev/null +++ b/mediagoblin/init/celery/from_celery.py @@ -0,0 +1,96 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import logging +import logging.config + +from celery.signals import setup_logging + +from mediagoblin import app, mg_globals +from mediagoblin.init.celery import setup_celery_from_config +from mediagoblin.tools.pluginapi import hook_runall + + +OUR_MODULENAME = __name__ + +_log = logging.getLogger(__name__) + + +def setup_logging_from_paste_ini(loglevel, **kw): + if os.path.exists(os.path.abspath('paste_local.ini')): + logging_conf_file = 'paste_local.ini' + else: + logging_conf_file = 'paste.ini' + + # allow users to set up explicitly which paste file to check via the + # PASTE_CONFIG environment variable + logging_conf_file = os.environ.get( + 'PASTE_CONFIG', logging_conf_file) + + if not os.path.exists(logging_conf_file): + raise IOError('{0} does not exist. Logging can not be set up.'.format( + logging_conf_file)) + + logging.config.fileConfig(logging_conf_file) + + hook_runall('celery_logging_setup') + + +setup_logging.connect(setup_logging_from_paste_ini) + + +def setup_self(check_environ_for_conf=True, module_name=OUR_MODULENAME, + default_conf_file=None): + """ + Transform this module into a celery config module by reading the + mediagoblin config file. Set the environment variable + MEDIAGOBLIN_CONFIG to specify where this config file is. + + By default it defaults to 'mediagoblin.ini'. + + Note that if celery_setup_elsewhere is set in your config file, + this simply won't work. + """ + if not default_conf_file: + if os.path.exists(os.path.abspath('mediagoblin_local.ini')): + default_conf_file = 'mediagoblin_local.ini' + else: + default_conf_file = 'mediagoblin.ini' + + if check_environ_for_conf: + mgoblin_conf_file = os.path.abspath( + os.environ.get('MEDIAGOBLIN_CONFIG', default_conf_file)) + else: + mgoblin_conf_file = default_conf_file + + if not os.path.exists(mgoblin_conf_file): + raise IOError( + "MEDIAGOBLIN_CONFIG not set or file does not exist") + + # By setting the environment variable here we should ensure that + # this is the module that gets set up. + os.environ['CELERY_CONFIG_MODULE'] = module_name + app.MediaGoblinApp(mgoblin_conf_file, setup_celery=False) + + setup_celery_from_config( + mg_globals.app_config, mg_globals.global_config, + settings_module=module_name, + set_environ=False) + + +if os.environ['CELERY_CONFIG_MODULE'] == OUR_MODULENAME: + setup_self() diff --git a/mediagoblin/init/config.py b/mediagoblin/init/config.py new file mode 100644 index 00000000..11a91cff --- /dev/null +++ b/mediagoblin/init/config.py @@ -0,0 +1,164 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import os +import pkg_resources + +from configobj import ConfigObj, flatten_errors +from validate import Validator + + +_log = logging.getLogger(__name__) + + +CONFIG_SPEC_PATH = pkg_resources.resource_filename( + 'mediagoblin', 'config_spec.ini') + + +def _setup_defaults(config, config_path): + """ + Setup DEFAULTS in a config object from an (absolute) config_path. + """ + config.setdefault('DEFAULT', {}) + config['DEFAULT']['here'] = os.path.dirname(config_path) + config['DEFAULT']['__file__'] = config_path + + +def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): + """ + Read a config object from config_path. + + Does automatic value transformation based on the config_spec. + Also provides %(__file__)s and %(here)s values of this file and + its directory respectively similar to paste deploy. + + Also reads for [plugins] section, appends all config_spec.ini + files from said plugins into the general config_spec specification. + + This function doesn't itself raise any exceptions if validation + fails, you'll have to do something + + Args: + - config_path: path to the config file + - config_spec: config file that provides defaults and value types + for validation / conversion. Defaults to mediagoblin/config_spec.ini + + Returns: + A tuple like: (config, validation_result) + ... where 'conf' is the parsed config object and 'validation_result' + is the information from the validation process. + """ + config_path = os.path.abspath(config_path) + + # PRE-READ of config file. This allows us to fetch the plugins so + # we can add their plugin specs to the general config_spec. + config = ConfigObj( + config_path, + interpolation='ConfigParser') + + plugins = config.get("plugins", {}).keys() + plugin_configs = {} + + for plugin in plugins: + try: + plugin_config_spec_path = pkg_resources.resource_filename( + plugin, "config_spec.ini") + if not os.path.exists(plugin_config_spec_path): + continue + + plugin_config_spec = ConfigObj( + plugin_config_spec_path, + encoding='UTF8', list_values=False, _inspec=True) + _setup_defaults(plugin_config_spec, config_path) + + if not "plugin_spec" in plugin_config_spec: + continue + + plugin_configs[plugin] = plugin_config_spec["plugin_spec"] + + except ImportError: + _log.warning( + "When setting up config section, could not import '%s'" % + plugin) + + # Now load the main config spec + config_spec = ConfigObj( + config_spec, + encoding='UTF8', list_values=False, _inspec=True) + + # append the plugin specific sections of the config spec + config_spec['plugins'] = plugin_configs + + _setup_defaults(config_spec, config_path) + + config = ConfigObj( + config_path, + configspec=config_spec, + interpolation='ConfigParser') + + _setup_defaults(config, config_path) + + # For now the validator just works with the default functions, + # but in the future if we want to add additional validation/configuration + # functions we'd add them to validator.functions here. + # + # See also: + # http://www.voidspace.org.uk/python/validate.html#adding-functions + validator = Validator() + validation_result = config.validate(validator, preserve_errors=True) + + return config, validation_result + + +REPORT_HEADER = u"""\ +There were validation problems loading this config file: +-------------------------------------------------------- +""" + + +def generate_validation_report(config, validation_result): + """ + Generate a report if necessary of problems while validating. + + Returns: + Either a string describing for a user the problems validating + this config or None if there are no problems. + """ + report = [] + + # Organize the report + for entry in flatten_errors(config, validation_result): + # each entry is a tuple + section_list, key, error = entry + + if key is not None: + section_list.append(key) + else: + section_list.append(u'[missing section]') + + section_string = u':'.join(section_list) + + if error == False: + # We don't care about missing values for now. + continue + + report.append(u"%s = %s" % (section_string, error)) + + if report: + return REPORT_HEADER + u"\n".join(report) + else: + return None diff --git a/mediagoblin/init/plugins/__init__.py b/mediagoblin/init/plugins/__init__.py new file mode 100644 index 00000000..0df4f381 --- /dev/null +++ b/mediagoblin/init/plugins/__init__.py @@ -0,0 +1,62 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import sys + +from mediagoblin import mg_globals +from mediagoblin.tools import pluginapi + + +_log = logging.getLogger(__name__) + + +def setup_plugins(): + """This loads, configures and registers plugins + + See plugin documentation for more details. + """ + + global_config = mg_globals.global_config + plugin_section = global_config.get('plugins', {}) + + if not plugin_section: + _log.info("No plugins to load") + return + + pman = pluginapi.PluginManager() + + # Go through and import all the modules that are subsections of + # the [plugins] section and read in the hooks. + for plugin_module, config in plugin_section.items(): + # Skip any modules that start with -. This makes it easier for + # someone to tweak their configuration so as to not load a + # plugin without having to remove swaths of plugin + # configuration. + if plugin_module.startswith('-'): + continue + + _log.info("Importing plugin module: %s" % plugin_module) + pman.register_plugin(plugin_module) + # If this throws errors, that's ok--it'll halt mediagoblin + # startup. + __import__(plugin_module) + plugin = sys.modules[plugin_module] + if hasattr(plugin, 'hooks'): + pman.register_hooks(plugin.hooks) + + # Execute anything registered to the setup hook. + pluginapi.hook_runall('setup') diff --git a/mediagoblin/listings/__init__.py b/mediagoblin/listings/__init__.py new file mode 100644 index 00000000..3f873e0c --- /dev/null +++ b/mediagoblin/listings/__init__.py @@ -0,0 +1,19 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +Non-user listing views and routing should go in this submodule. +""" diff --git a/mediagoblin/listings/routing.py b/mediagoblin/listings/routing.py new file mode 100644 index 00000000..ee8f5020 --- /dev/null +++ b/mediagoblin/listings/routing.py @@ -0,0 +1,29 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.routing import add_route + +add_route('mediagoblin.listings.tags_listing', + "/tag/<string:tag>/", + "mediagoblin.listings.views:tag_listing") + +# Atom feeds: +add_route('mediagoblin.listings.tag_atom_feed', "/tag/<string:tag>/atom/", + "mediagoblin.listings.views:atom_feed") + +# The all new entries feed +add_route('mediagoblin.listings.atom_feed', '/atom/', + "mediagoblin.listings.views:atom_feed") diff --git a/mediagoblin/listings/views.py b/mediagoblin/listings/views.py new file mode 100644 index 00000000..35af7148 --- /dev/null +++ b/mediagoblin/listings/views.py @@ -0,0 +1,110 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.db.models import MediaEntry +from mediagoblin.db.util import media_entries_for_tag_slug +from mediagoblin.tools.pagination import Pagination +from mediagoblin.tools.response import render_to_response +from mediagoblin.decorators import uses_pagination + +from werkzeug.contrib.atom import AtomFeed + + +def _get_tag_name_from_entries(media_entries, tag_slug): + """ + Get a tag name from the first entry by looking it up via its slug. + """ + # ... this is slightly hacky looking :\ + tag_name = tag_slug + + for entry in media_entries: + for tag in entry.tags: + if tag['slug'] == tag_slug: + tag_name = tag['name'] + break + break + return tag_name + + +@uses_pagination +def tag_listing(request, page): + """'Gallery'/listing for this tag slug""" + tag_slug = request.matchdict[u'tag'] + + cursor = media_entries_for_tag_slug(request.db, tag_slug) + cursor = cursor.order_by(MediaEntry.created.desc()) + + pagination = Pagination(page, cursor) + media_entries = pagination() + + tag_name = _get_tag_name_from_entries(media_entries, tag_slug) + + return render_to_response( + request, + 'mediagoblin/listings/tag.html', + {'tag_slug': tag_slug, + 'tag_name': tag_name, + 'media_entries': media_entries, + 'pagination': pagination}) + + +ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15 + + +def atom_feed(request): + """ + generates the atom feed with the tag images + """ + tag_slug = request.matchdict.get(u'tag') + feed_title = "MediaGoblin Feed" + if tag_slug: + cursor = media_entries_for_tag_slug(request.db, tag_slug) + link = request.urlgen('mediagoblin.listings.tags_listing', + qualified=True, tag=tag_slug ) + feed_title += "for tag '%s'" % tag_slug + else: # all recent item feed + cursor = MediaEntry.query.filter_by(state=u'processed') + link = request.urlgen('index', qualified=True) + feed_title += "for all recent items" + + cursor = cursor.order_by(MediaEntry.created.desc()) + cursor = cursor.limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS) + + feed = AtomFeed( + feed_title, + feed_url=request.url, + id=link, + links=[{'href': link, + 'rel': 'alternate', + 'type': 'text/html'}]) + for entry in cursor: + feed.add(entry.get('title'), + entry.description_html, + id=entry.url_for_self(request.urlgen,qualified=True), + content_type='html', + author={'name': entry.get_uploader.username, + 'uri': request.urlgen( + 'mediagoblin.user_pages.user_home', + qualified=True, user=entry.get_uploader.username)}, + updated=entry.get('created'), + links=[{ + 'href':entry.url_for_self( + request.urlgen, + qualified=True), + 'rel': 'alternate', + 'type': 'text/html'}]) + + return feed.get_response() diff --git a/mediagoblin/meddleware/__init__.py b/mediagoblin/meddleware/__init__.py new file mode 100644 index 00000000..886c9ad9 --- /dev/null +++ b/mediagoblin/meddleware/__init__.py @@ -0,0 +1,31 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +ENABLED_MEDDLEWARE = [ + 'mediagoblin.meddleware.csrf:CsrfMeddleware', + ] + + +class BaseMeddleware(object): + + def __init__(self, mg_app): + self.app = mg_app + + def process_request(self, request, controller): + pass + + def process_response(self, request, response): + pass diff --git a/mediagoblin/meddleware/csrf.py b/mediagoblin/meddleware/csrf.py new file mode 100644 index 00000000..661f0ba2 --- /dev/null +++ b/mediagoblin/meddleware/csrf.py @@ -0,0 +1,152 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import random +import logging + +from werkzeug.exceptions import Forbidden +from wtforms import Form, HiddenField, validators + +from mediagoblin import mg_globals +from mediagoblin.meddleware import BaseMeddleware +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +_log = logging.getLogger(__name__) + +# Use the system (hardware-based) random number generator if it exists. +# -- this optimization is lifted from Django +if hasattr(random, 'SystemRandom'): + getrandbits = random.SystemRandom().getrandbits +else: + getrandbits = random.getrandbits + + +def csrf_exempt(func): + """Decorate a Controller to exempt it from CSRF protection.""" + + func.csrf_enabled = False + return func + + +class CsrfForm(Form): + """Simple form to handle rendering a CSRF token and confirming it + is included in the POST.""" + + csrf_token = HiddenField("", + [validators.Required()]) + + +def render_csrf_form_token(request): + """Render the CSRF token in a format suitable for inclusion in a + form.""" + + if 'CSRF_TOKEN' not in request.environ: + return None + + form = CsrfForm(csrf_token=request.environ['CSRF_TOKEN']) + + return form.csrf_token + + +class CsrfMeddleware(BaseMeddleware): + """CSRF Protection Meddleware + + Adds a CSRF Cookie to responses and verifies that it is present + and matches the form token for non-safe requests. + """ + + CSRF_KEYLEN = 64 + SAFE_HTTP_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE") + + def process_request(self, request, controller): + """For non-safe requests, confirm that the tokens are present + and match. + """ + + # get the token from the cookie + try: + request.environ['CSRF_TOKEN'] = \ + request.cookies[mg_globals.app_config['csrf_cookie_name']] + + except KeyError: + # if it doesn't exist, make a new one + request.environ['CSRF_TOKEN'] = self._make_token(request) + + # if this is a non-"safe" request (ie, one that could have + # side effects), confirm that the CSRF tokens are present and + # valid + if (getattr(controller, 'csrf_enabled', True) and + request.method not in self.SAFE_HTTP_METHODS and + ('gmg.verify_csrf' in request.environ or + 'paste.testing' not in request.environ) + ): + + return self.verify_tokens(request) + + def process_response(self, request, response): + """Add the CSRF cookie to the response if needed and set Vary + headers. + """ + + # set the CSRF cookie + response.set_cookie( + mg_globals.app_config['csrf_cookie_name'], + request.environ['CSRF_TOKEN'], + path=request.environ['SCRIPT_NAME'], + domain=mg_globals.app_config.get('csrf_cookie_domain'), + secure=(request.scheme.lower() == 'https'), + httponly=True) + + # update the Vary header + response.vary = (getattr(response, 'vary', None) or []) + ['Cookie'] + + def _make_token(self, request): + """Generate a new token to use for CSRF protection.""" + + return "%s" % (getrandbits(self.CSRF_KEYLEN),) + + def verify_tokens(self, request): + """Verify that the CSRF Cookie exists and that it matches the + form value.""" + + # confirm the cookie token was presented + cookie_token = request.cookies.get( + mg_globals.app_config['csrf_cookie_name'], + None) + + if cookie_token is None: + # the CSRF cookie must be present in the request, if not a + # cookie blocker might be in action (in the best case) + _log.error('CSRF cookie not present') + raise Forbidden(_('CSRF cookie not present. This is most likely ' + 'the result of a cookie blocker or somesuch.<br/>' + 'Make sure to permit the settings of cookies for ' + 'this domain.')) + + # get the form token and confirm it matches + form = CsrfForm(request.form) + if form.validate(): + form_token = form.csrf_token.data + + if form_token == cookie_token: + # all's well that ends well + return + + # either the tokens didn't match or the form token wasn't + # present; either way, the request is denied + errstr = 'CSRF validation failed' + _log.error(errstr) + raise Forbidden(errstr) diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py new file mode 100644 index 00000000..20e1918e --- /dev/null +++ b/mediagoblin/media_types/__init__.py @@ -0,0 +1,155 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import sys +import logging +import tempfile + +from mediagoblin import mg_globals +from mediagoblin.tools.common import import_component +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +_log = logging.getLogger(__name__) + +class FileTypeNotSupported(Exception): + pass + +class InvalidFileType(Exception): + pass + + +class MediaManagerBase(object): + "Base class for all media managers" + + # Please override in actual media managers + media_fetch_order = None + + @staticmethod + def sniff_handler(*args, **kwargs): + return False + + def __init__(self, entry): + self.entry = entry + + def __getitem__(self, i): + return getattr(self, i) + + def __contains__(self, i): + return hasattr(self, i) + + +class CompatMediaManager(object): + def __init__(self, mm_dict, entry=None): + self.mm_dict = mm_dict + self.entry = entry + + def __call__(self, entry): + "So this object can look like a class too, somehow" + assert self.entry is None + return self.__class__(self.mm_dict, entry) + + def __getitem__(self, i): + return self.mm_dict[i] + + def __contains__(self, i): + return (i in self.mm_dict) + + @property + def media_fetch_order(self): + return self.mm_dict.get('media_fetch_order') + + def sniff_handler(self, *args, **kwargs): + func = self.mm_dict.get("sniff_handler", None) + if func is not None: + return func(*args, **kwargs) + return False + + def __getattr__(self, i): + return self.mm_dict[i] + + +def sniff_media(media): + ''' + Iterate through the enabled media types and find those suited + for a certain file. + ''' + + try: + return get_media_type_and_manager(media.filename) + except FileTypeNotSupported: + _log.info('No media handler found by file extension. Doing it the expensive way...') + # Create a temporary file for sniffers suchs as GStreamer-based + # Audio video + media_file = tempfile.NamedTemporaryFile() + media_file.write(media.stream.read()) + media.stream.seek(0) + + for media_type, manager in get_media_managers(): + _log.info('Sniffing {0}'.format(media_type)) + if manager.sniff_handler(media_file, media=media): + _log.info('{0} accepts the file'.format(media_type)) + return media_type, manager + else: + _log.debug('{0} did not accept the file'.format(media_type)) + + raise FileTypeNotSupported( + # TODO: Provide information on which file types are supported + _(u'Sorry, I don\'t support that file type :(')) + + +def get_media_types(): + """ + Generator, yields the available media types + """ + for media_type in mg_globals.app_config['media_types']: + yield media_type + + +def get_media_managers(): + ''' + Generator, yields all enabled media managers + ''' + for media_type in get_media_types(): + mm = import_component(media_type + ":MEDIA_MANAGER") + + if isinstance(mm, dict): + mm = CompatMediaManager(mm) + + yield media_type, mm + + +def get_media_type_and_manager(filename): + ''' + Try to find the media type based on the file name, extension + specifically. This is used as a speedup, the sniffing functionality + then falls back on more in-depth bitsniffing of the source file. + ''' + if filename.find('.') > 0: + # Get the file extension + ext = os.path.splitext(filename)[1].lower() + + for media_type, manager in get_media_managers(): + # Omit the dot from the extension and match it against + # the media manager + if ext[1:] in manager.accepted_extensions: + return media_type, manager + else: + _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format( + filename)) + + raise FileTypeNotSupported( + _(u'Sorry, I don\'t support that file type :(')) diff --git a/mediagoblin/media_types/ascii/__init__.py b/mediagoblin/media_types/ascii/__init__.py new file mode 100644 index 00000000..0931e83a --- /dev/null +++ b/mediagoblin/media_types/ascii/__init__.py @@ -0,0 +1,31 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.media_types import MediaManagerBase +from mediagoblin.media_types.ascii.processing import process_ascii, \ + sniff_handler + + +class ASCIIMediaManager(MediaManagerBase): + human_readable = "ASCII" + processor = staticmethod(process_ascii) + sniff_handler = staticmethod(sniff_handler) + display_template = "mediagoblin/media_displays/ascii.html" + default_thumb = "images/media_thumbs/ascii.jpg" + accepted_extensions = ["txt", "asc", "nfo"] + + +MEDIA_MANAGER = ASCIIMediaManager diff --git a/mediagoblin/media_types/ascii/asciitoimage.py b/mediagoblin/media_types/ascii/asciitoimage.py new file mode 100644 index 00000000..786941f6 --- /dev/null +++ b/mediagoblin/media_types/ascii/asciitoimage.py @@ -0,0 +1,146 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +try: + from PIL import Image + from PIL import ImageFont + from PIL import ImageDraw +except ImportError: + import Image + import ImageFont + import ImageDraw +import logging +import pkg_resources +import os + +_log = logging.getLogger(__name__) + + +class AsciiToImage(object): + ''' + Converter of ASCII art into image files, preserving whitespace + + kwargs: + - font: Path to font file + default: fonts/Inconsolata.otf + - font_size: Font size, ``int`` + default: 11 + ''' + def __init__(self, **kw): + self._font = kw.get('font', pkg_resources.resource_filename( + 'mediagoblin.media_types.ascii', + os.path.join('fonts', 'Inconsolata.otf'))) + + self._font_size = kw.get('font_size', 11) + + self._if = ImageFont.truetype( + self._font, + self._font_size, + encoding='unic') + + _log.info('Font set to {0}, size {1}'.format( + self._font, + self._font_size)) + + # ,-,-^-'-^'^-^'^-'^-. + # ( I am a wall socket )Oo, ___ + # `-.,.-.,.-.-.,.-.--' ' ` + # Get the size, in pixels of the '.' character + self._if_dims = self._if.getsize('.') + # `---' + + def convert(self, text, destination): + # TODO: Detect if text is a file-like, if so, act accordingly + im = self._create_image(text) + + # PIL's Image.save will handle both file-likes and paths + if im.save(destination): + _log.info('Saved image in {0}'.format( + destination)) + + def _create_image(self, text): + ''' + Write characters to a PIL image canvas. + + TODO: + - Character set detection and decoding, + http://pypi.python.org/pypi/chardet + ''' + _log.debug('Drawing image') + # Convert the input from str to unicode + text = text.decode('utf-8') + + # TODO: Account for alternative line endings + lines = text.split('\n') + + line_lengths = [len(i) for i in lines] + + # Calculate destination size based on text input and character size + im_dims = ( + max(line_lengths) * self._if_dims[0], + len(line_lengths) * self._if_dims[1]) + + _log.info('Destination image dimensions will be {0}'.format( + im_dims)) + + im = Image.new( + 'RGBA', + im_dims, + (255, 255, 255, 0)) + + draw = ImageDraw.Draw(im) + + char_pos = [0, 0] + + for line in lines: + line_length = len(line) + + _log.debug('Writing line at {0}'.format(char_pos)) + + for _pos in range(0, line_length): + char = line[_pos] + + px_pos = self._px_pos(char_pos) + + _log.debug('Writing character "{0}" at {1} (px pos {2})'.format( + char.encode('ascii', 'replace'), + char_pos, + px_pos)) + + draw.text( + px_pos, + char, + font=self._if, + fill=(0, 0, 0, 255)) + + char_pos[0] += 1 + + # Reset X position, increment Y position + char_pos[0] = 0 + char_pos[1] += 1 + + return im + + def _px_pos(self, char_pos): + ''' + Helper function to calculate the pixel position based on + character position and character dimensions + ''' + px_pos = [0, 0] + for index, val in zip(range(0, len(char_pos)), char_pos): + px_pos[index] = char_pos[index] * self._if_dims[index] + + return px_pos diff --git a/mediagoblin/media_types/ascii/fonts/Inconsolata.otf b/mediagoblin/media_types/ascii/fonts/Inconsolata.otf new file mode 120000 index 00000000..4e742b5e --- /dev/null +++ b/mediagoblin/media_types/ascii/fonts/Inconsolata.otf @@ -0,0 +1 @@ +../../../../extlib/inconsolata/Inconsolata.otf
\ No newline at end of file diff --git a/mediagoblin/media_types/ascii/migrations.py b/mediagoblin/media_types/ascii/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/ascii/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/ascii/models.py b/mediagoblin/media_types/ascii/models.py new file mode 100644 index 00000000..c7505292 --- /dev/null +++ b/mediagoblin/media_types/ascii/models.py @@ -0,0 +1,40 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from mediagoblin.db.base import Base + +from sqlalchemy import ( + Column, Integer, ForeignKey) +from sqlalchemy.orm import relationship, backref + + +BACKREF_NAME = "ascii__media_data" + + +class AsciiData(Base): + __tablename__ = "ascii__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) + + +DATA_MODEL = AsciiData +MODELS = [AsciiData] diff --git a/mediagoblin/media_types/ascii/processing.py b/mediagoblin/media_types/ascii/processing.py new file mode 100644 index 00000000..2f6079be --- /dev/null +++ b/mediagoblin/media_types/ascii/processing.py @@ -0,0 +1,146 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import chardet +import os +try: + from PIL import Image +except ImportError: + import Image +import logging + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import create_pub_filepath +from mediagoblin.media_types.ascii import asciitoimage + +_log = logging.getLogger(__name__) + +SUPPORTED_EXTENSIONS = ['txt', 'asc', 'nfo'] + + +def sniff_handler(media_file, **kw): + if kw.get('media') is not None: + name, ext = os.path.splitext(kw['media'].filename) + clean_ext = ext[1:].lower() + + if clean_ext in SUPPORTED_EXTENSIONS: + return True + + return False + + +def process_ascii(proc_state): + """Code to process a txt file. Will be run by celery. + + A Workbench() represents a local tempory dir. It is automatically + cleaned up when this function exits. + """ + entry = proc_state.entry + workbench = proc_state.workbench + ascii_config = mgg.global_config['media_type:mediagoblin.media_types.ascii'] + # Conversions subdirectory to avoid collisions + conversions_subdir = os.path.join( + workbench.dir, 'conversions') + os.mkdir(conversions_subdir) + + queued_filepath = entry.queued_media_file + queued_filename = workbench.localized_file( + mgg.queue_store, queued_filepath, + 'source') + + queued_file = file(queued_filename, 'rb') + + with queued_file: + queued_file_charset = chardet.detect(queued_file.read()) + + # Only select a non-utf-8 charset if chardet is *really* sure + # Tested with "Feli\x0109an superjaron", which was detecte + if queued_file_charset['confidence'] < 0.9: + interpreted_charset = 'utf-8' + else: + interpreted_charset = queued_file_charset['encoding'] + + _log.info('Charset detected: {0}\nWill interpret as: {1}'.format( + queued_file_charset, + interpreted_charset)) + + queued_file.seek(0) # Rewind the queued file + + thumb_filepath = create_pub_filepath( + entry, 'thumbnail.png') + + tmp_thumb_filename = os.path.join( + conversions_subdir, thumb_filepath[-1]) + + ascii_converter_args = {} + + if ascii_config['thumbnail_font']: + ascii_converter_args.update( + {'font': ascii_config['thumbnail_font']}) + + converter = asciitoimage.AsciiToImage( + **ascii_converter_args) + + thumb = converter._create_image( + queued_file.read()) + + with file(tmp_thumb_filename, 'w') as thumb_file: + thumb.thumbnail( + (mgg.global_config['media:thumb']['max_width'], + mgg.global_config['media:thumb']['max_height']), + Image.ANTIALIAS) + thumb.save(thumb_file) + + _log.debug('Copying local file to public storage') + mgg.public_store.copy_local_to_storage( + tmp_thumb_filename, thumb_filepath) + + queued_file.seek(0) + + original_filepath = create_pub_filepath(entry, queued_filepath[-1]) + + with mgg.public_store.get_file(original_filepath, 'wb') \ + as original_file: + original_file.write(queued_file.read()) + + queued_file.seek(0) # Rewind *again* + + unicode_filepath = create_pub_filepath(entry, 'ascii-portable.txt') + + with mgg.public_store.get_file(unicode_filepath, 'wb') \ + as unicode_file: + # Decode the original file from its detected charset (or UTF8) + # Encode the unicode instance to ASCII and replace any non-ASCII + # with an HTML entity (&# + unicode_file.write( + unicode(queued_file.read().decode( + interpreted_charset)).encode( + 'ascii', + 'xmlcharrefreplace')) + + # Remove queued media file from storage and database. + # queued_filepath is in the task_id directory which should + # be removed too, but fail if the directory is not empty to be on + # the super-safe side. + mgg.queue_store.delete_file(queued_filepath) # rm file + mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir + entry.queued_media_file = [] + + media_files_dict = entry.setdefault('media_files', {}) + media_files_dict['thumb'] = thumb_filepath + media_files_dict['unicode'] = unicode_filepath + media_files_dict['original'] = original_filepath + + entry.save() diff --git a/mediagoblin/media_types/audio/__init__.py b/mediagoblin/media_types/audio/__init__.py new file mode 100644 index 00000000..2eb7300e --- /dev/null +++ b/mediagoblin/media_types/audio/__init__.py @@ -0,0 +1,30 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.media_types import MediaManagerBase +from mediagoblin.media_types.audio.processing import process_audio, \ + sniff_handler + + +class AudioMediaManager(MediaManagerBase): + human_readable = "Audio" + processor = staticmethod(process_audio) + sniff_handler = staticmethod(sniff_handler) + display_template = "mediagoblin/media_displays/audio.html" + accepted_extensions = ["mp3", "flac", "wav", "m4a"] + + +MEDIA_MANAGER = AudioMediaManager diff --git a/mediagoblin/media_types/audio/audioprocessing.py b/mediagoblin/media_types/audio/audioprocessing.py new file mode 120000 index 00000000..c5e3c52c --- /dev/null +++ b/mediagoblin/media_types/audio/audioprocessing.py @@ -0,0 +1 @@ +../../../extlib/freesound/audioprocessing.py
\ No newline at end of file diff --git a/mediagoblin/media_types/audio/migrations.py b/mediagoblin/media_types/audio/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/audio/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/audio/models.py b/mediagoblin/media_types/audio/models.py new file mode 100644 index 00000000..d01367d5 --- /dev/null +++ b/mediagoblin/media_types/audio/models.py @@ -0,0 +1,40 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from mediagoblin.db.base import Base + +from sqlalchemy import ( + Column, Integer, ForeignKey) +from sqlalchemy.orm import relationship, backref + + +BACKREF_NAME = "audio__media_data" + + +class AudioData(Base): + __tablename__ = "audio__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) + + +DATA_MODEL = AudioData +MODELS = [AudioData] diff --git a/mediagoblin/media_types/audio/processing.py b/mediagoblin/media_types/audio/processing.py new file mode 100644 index 00000000..101b83e5 --- /dev/null +++ b/mediagoblin/media_types/audio/processing.py @@ -0,0 +1,156 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +from tempfile import NamedTemporaryFile +import os + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import (create_pub_filepath, BadMediaFail, + FilenameBuilder, ProgressCallback) + +from mediagoblin.media_types.audio.transcoders import (AudioTranscoder, + AudioThumbnailer) + +_log = logging.getLogger(__name__) + + +def sniff_handler(media_file, **kw): + try: + transcoder = AudioTranscoder() + data = transcoder.discover(media_file.name) + except BadMediaFail: + _log.debug('Audio discovery raised BadMediaFail') + return False + + if data.is_audio == True and data.is_video == False: + return True + + return False + + +def process_audio(proc_state): + """Code to process uploaded audio. Will be run by celery. + + A Workbench() represents a local tempory dir. It is automatically + cleaned up when this function exits. + """ + entry = proc_state.entry + workbench = proc_state.workbench + audio_config = mgg.global_config['media_type:mediagoblin.media_types.audio'] + + queued_filepath = entry.queued_media_file + queued_filename = workbench.localized_file( + mgg.queue_store, queued_filepath, + 'source') + name_builder = FilenameBuilder(queued_filename) + + webm_audio_filepath = create_pub_filepath( + entry, + '{original}.webm'.format( + original=os.path.splitext( + queued_filepath[-1])[0])) + + if audio_config['keep_original']: + with open(queued_filename, 'rb') as queued_file: + original_filepath = create_pub_filepath( + entry, name_builder.fill('{basename}{ext}')) + + with mgg.public_store.get_file(original_filepath, 'wb') as \ + original_file: + _log.debug('Saving original...') + original_file.write(queued_file.read()) + + entry.media_files['original'] = original_filepath + + transcoder = AudioTranscoder() + + with NamedTemporaryFile(dir=workbench.dir) as webm_audio_tmp: + progress_callback = ProgressCallback(entry) + + transcoder.transcode( + queued_filename, + webm_audio_tmp.name, + quality=audio_config['quality'], + progress_callback=progress_callback) + + transcoder.discover(webm_audio_tmp.name) + + _log.debug('Saving medium...') + mgg.public_store.get_file(webm_audio_filepath, 'wb').write( + webm_audio_tmp.read()) + + entry.media_files['webm_audio'] = webm_audio_filepath + + # entry.media_data_init(length=int(data.audiolength)) + + if audio_config['create_spectrogram']: + spectrogram_filepath = create_pub_filepath( + entry, + '{original}-spectrogram.jpg'.format( + original=os.path.splitext( + queued_filepath[-1])[0])) + + with NamedTemporaryFile(dir=workbench.dir, suffix='.ogg') as wav_tmp: + _log.info('Creating OGG source for spectrogram') + transcoder.transcode( + queued_filename, + wav_tmp.name, + mux_string='vorbisenc quality={0} ! oggmux'.format( + audio_config['quality'])) + + thumbnailer = AudioThumbnailer() + + with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as spectrogram_tmp: + thumbnailer.spectrogram( + wav_tmp.name, + spectrogram_tmp.name, + width=mgg.global_config['media:medium']['max_width'], + fft_size=audio_config['spectrogram_fft_size']) + + _log.debug('Saving spectrogram...') + mgg.public_store.get_file(spectrogram_filepath, 'wb').write( + spectrogram_tmp.read()) + + entry.media_files['spectrogram'] = spectrogram_filepath + + with NamedTemporaryFile(dir=workbench.dir, suffix='.jpg') as thumb_tmp: + thumbnailer.thumbnail_spectrogram( + spectrogram_tmp.name, + thumb_tmp.name, + (mgg.global_config['media:thumb']['max_width'], + mgg.global_config['media:thumb']['max_height'])) + + thumb_filepath = create_pub_filepath( + entry, + '{original}-thumbnail.jpg'.format( + original=os.path.splitext( + queued_filepath[-1])[0])) + + mgg.public_store.get_file(thumb_filepath, 'wb').write( + thumb_tmp.read()) + + entry.media_files['thumb'] = thumb_filepath + else: + entry.media_files['thumb'] = ['fake', 'thumb', 'path.jpg'] + + # Remove queued media file from storage and database. + # queued_filepath is in the task_id directory which should + # be removed too, but fail if the directory is not empty to be on + # the super-safe side. + mgg.queue_store.delete_file(queued_filepath) # rm file + mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir + entry.queued_media_file = [] diff --git a/mediagoblin/media_types/audio/spectrogram.py b/mediagoblin/media_types/audio/spectrogram.py new file mode 100644 index 00000000..dd4d0299 --- /dev/null +++ b/mediagoblin/media_types/audio/spectrogram.py @@ -0,0 +1,360 @@ +# processing.py -- various audio processing functions +# Copyright (C) 2008 MUSIC TECHNOLOGY GROUP (MTG) +# UNIVERSITAT POMPEU FABRA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Bram de Jong <bram.dejong at domain.com where domain in gmail> +# 2012, Joar Wandborg <first name at last name dot se> + +try: + from PIL import Image +except ImportError: + import Image +import math +import numpy + +try: + import scikits.audiolab as audiolab +except ImportError: + print "WARNING: audiolab is not installed so wav2png will not work" + + +class AudioProcessingException(Exception): + pass + + +class SpectrogramImage(object): + def __init__(self, image_size, fft_size): + self.image_width, self.image_height = image_size + self.fft_size = fft_size + + colors = [ + (0, 0, 0, 0), + (58 / 4, 68 / 4, 65 / 4, 255), + (80 / 2, 100 / 2, 153 / 2, 255), + (90, 180, 100, 255), + (224, 224, 44, 255), + (255, 60, 30, 255), + (255, 255, 255, 255) + ] + + self.palette = interpolate_colors(colors) + + # Generate lookup table for y-coordinate from fft-bin + self.y_to_bin = [] + + fft_min = 100.0 + fft_max = 22050.0 # kHz? + + y_min = math.log10(fft_min) + y_max = math.log10(fft_max) + + for y in range(self.image_height): + freq = math.pow( + 10.0, + y_min + y / (self.image_height - 1.0) + * (y_max - y_min)) + + fft_bin = freq / fft_max * (self.fft_size / 2 + 1) + + if fft_bin < self.fft_size / 2: + alpha = fft_bin - int(fft_bin) + + self.y_to_bin.append((int(fft_bin), alpha * 255)) + + # this is a bit strange, but using image.load()[x,y] = ... is + # a lot slower than using image.putadata and then rotating the image + # so we store all the pixels in an array and then create the image when saving + self.pixels = [] + + def draw_spectrum(self, x, spectrum): + # for all frequencies, draw the pixels + for index, alpha in self.y_to_bin: + self.pixels.append( + self.palette[int((255.0 - alpha) * spectrum[index] + + alpha * spectrum[index + 1])]) + + # if the FFT is too small to fill up the image, fill with black to the top + for y in range(len(self.y_to_bin), self.image_height): + self.pixels.append(self.palette[0]) + + def save(self, filename, quality=90): + self.image = Image.new( + 'RGBA', + (self.image_height, self.image_width)) + + self.image.putdata(self.pixels) + self.image.transpose(Image.ROTATE_90).save( + filename, + quality=quality) + + +class AudioProcessor(object): + """ + The audio processor processes chunks of audio an calculates the spectrac centroid and the peak + samples in that chunk of audio. + """ + def __init__(self, input_filename, fft_size, window_function=numpy.hanning): + max_level = get_max_level(input_filename) + + self.audio_file = audiolab.Sndfile(input_filename, 'r') + self.fft_size = fft_size + self.window = window_function(self.fft_size) + self.spectrum_range = None + self.lower = 100 + self.higher = 22050 + self.lower_log = math.log10(self.lower) + self.higher_log = math.log10(self.higher) + self.clip = lambda val, low, high: min(high, max(low, val)) + + # figure out what the maximum value is for an FFT doing the FFT of a DC signal + fft = numpy.fft.rfft(numpy.ones(fft_size) * self.window) + max_fft = (numpy.abs(fft)).max() + + # set the scale to normalized audio and normalized FFT + self.scale = 1.0 / max_level / max_fft if max_level > 0 else 1 + + def read(self, start, size, resize_if_less=False): + """ read size samples starting at start, if resize_if_less is True and less than size + samples are read, resize the array to size and fill with zeros """ + + # number of zeros to add to start and end of the buffer + add_to_start = 0 + add_to_end = 0 + + if start < 0: + # the first FFT window starts centered around zero + if size + start <= 0: + return numpy.zeros(size) if resize_if_less else numpy.array([]) + else: + self.audio_file.seek(0) + + add_to_start = - start # remember: start is negative! + to_read = size + start + + if to_read > self.audio_file.nframes: + add_to_end = to_read - self.audio_file.nframes + to_read = self.audio_file.nframes + else: + self.audio_file.seek(start) + + to_read = size + if start + to_read >= self.audio_file.nframes: + to_read = self.audio_file.nframes - start + add_to_end = size - to_read + + try: + samples = self.audio_file.read_frames(to_read) + except RuntimeError: + # this can happen for wave files with broken headers... + return numpy.zeros(size) if resize_if_less else numpy.zeros(2) + + # convert to mono by selecting left channel only + if self.audio_file.channels > 1: + samples = samples[:,0] + + if resize_if_less and (add_to_start > 0 or add_to_end > 0): + if add_to_start > 0: + samples = numpy.concatenate((numpy.zeros(add_to_start), samples), axis=1) + + if add_to_end > 0: + samples = numpy.resize(samples, size) + samples[size - add_to_end:] = 0 + + return samples + + def spectral_centroid(self, seek_point, spec_range=110.0): + """ starting at seek_point read fft_size samples, and calculate the spectral centroid """ + + samples = self.read(seek_point - self.fft_size/2, self.fft_size, True) + + samples *= self.window + fft = numpy.fft.rfft(samples) + spectrum = self.scale * numpy.abs(fft) # normalized abs(FFT) between 0 and 1 + + length = numpy.float64(spectrum.shape[0]) + + # scale the db spectrum from [- spec_range db ... 0 db] > [0..1] + db_spectrum = ((20*(numpy.log10(spectrum + 1e-60))).clip(-spec_range, 0.0) + spec_range)/spec_range + + energy = spectrum.sum() + spectral_centroid = 0 + + if energy > 1e-60: + # calculate the spectral centroid + + if self.spectrum_range == None: + self.spectrum_range = numpy.arange(length) + + spectral_centroid = (spectrum * self.spectrum_range).sum() / (energy * (length - 1)) * self.audio_file.samplerate * 0.5 + + # clip > log10 > scale between 0 and 1 + spectral_centroid = (math.log10(self.clip(spectral_centroid, self.lower, self.higher)) - self.lower_log) / (self.higher_log - self.lower_log) + + return (spectral_centroid, db_spectrum) + + + def peaks(self, start_seek, end_seek): + """ read all samples between start_seek and end_seek, then find the minimum and maximum peak + in that range. Returns that pair in the order they were found. So if min was found first, + it returns (min, max) else the other way around. """ + + # larger blocksizes are faster but take more mem... + # Aha, Watson, a clue, a tradeof! + block_size = 4096 + + max_index = -1 + max_value = -1 + min_index = -1 + min_value = 1 + + if start_seek < 0: + start_seek = 0 + + if end_seek > self.audio_file.nframes: + end_seek = self.audio_file.nframes + + if end_seek <= start_seek: + samples = self.read(start_seek, 1) + return (samples[0], samples[0]) + + if block_size > end_seek - start_seek: + block_size = end_seek - start_seek + + for i in range(start_seek, end_seek, block_size): + samples = self.read(i, block_size) + + local_max_index = numpy.argmax(samples) + local_max_value = samples[local_max_index] + + if local_max_value > max_value: + max_value = local_max_value + max_index = local_max_index + + local_min_index = numpy.argmin(samples) + local_min_value = samples[local_min_index] + + if local_min_value < min_value: + min_value = local_min_value + min_index = local_min_index + + return (min_value, max_value) if min_index < max_index else (max_value, min_value) + + +def create_spectrogram_image(source_filename, output_filename, + image_size, fft_size, progress_callback=None): + + processor = AudioProcessor(source_filename, fft_size, numpy.hamming) + samples_per_pixel = processor.audio_file.nframes / float(image_size[0]) + + spectrogram = SpectrogramImage(image_size, fft_size) + + for x in range(image_size[0]): + if progress_callback and x % (image_size[0] / 10) == 0: + progress_callback((x * 100) / image_size[0]) + + seek_point = int(x * samples_per_pixel) + next_seek_point = int((x + 1) * samples_per_pixel) + + (spectral_centroid, db_spectrum) = processor.spectral_centroid(seek_point) + + spectrogram.draw_spectrum(x, db_spectrum) + + if progress_callback: + progress_callback(100) + + spectrogram.save(output_filename) + + +def interpolate_colors(colors, flat=False, num_colors=256): + + palette = [] + + for i in range(num_colors): + # TODO: What does this do? + index = ( + (i * + (len(colors) - 1) # 7 + ) # 0..7..14..21..28... + / + (num_colors - 1.0) # 255.0 + ) + + # TODO: What is the meaning of 'alpha' in this context? + alpha = index - round(index) + + channels = list('rgb') + values = dict() + + for k, v in zip(range(len(channels)), channels): + if alpha > 0: + values[v] = ( + (1.0 - alpha) + * + colors[int(index)][k] + + + alpha * colors[int(index) + 1][k] + ) + else: + values[v] = ( + (1.0 - alpha) + * + colors[int(index)][k] + ) + + if flat: + palette.extend( + tuple(int(values[i]) for i in channels)) + else: + palette.append( + tuple(int(values[i]) for i in channels)) + + return palette + + +def get_max_level(filename): + max_value = 0 + buffer_size = 4096 + audio_file = audiolab.Sndfile(filename, 'r') + n_samples_left = audio_file.nframes + + while n_samples_left: + to_read = min(buffer_size, n_samples_left) + + try: + samples = audio_file.read_frames(to_read) + except RuntimeError: + # this can happen with a broken header + break + + # convert to mono by selecting left channel only + if audio_file.channels > 1: + samples = samples[:,0] + + max_value = max(max_value, numpy.abs(samples).max()) + + n_samples_left -= to_read + + audio_file.close() + + return max_value + +if __name__ == '__main__': + import sys + sys.argv[4] = int(sys.argv[4]) + sys.argv[3] = tuple([int(i) for i in sys.argv[3].split('x')]) + + create_spectrogram_image(*sys.argv[1:]) diff --git a/mediagoblin/media_types/audio/transcoders.py b/mediagoblin/media_types/audio/transcoders.py new file mode 100644 index 00000000..84e6af7e --- /dev/null +++ b/mediagoblin/media_types/audio/transcoders.py @@ -0,0 +1,237 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +try: + from PIL import Image +except ImportError: + import Image + +from mediagoblin.processing import BadMediaFail +from mediagoblin.media_types.audio import audioprocessing + + +_log = logging.getLogger(__name__) + +CPU_COUNT = 2 # Just assuming for now + +# IMPORT MULTIPROCESSING +try: + import multiprocessing + try: + CPU_COUNT = multiprocessing.cpu_count() + except NotImplementedError: + _log.warning('multiprocessing.cpu_count not implemented!\n' + 'Assuming 2 CPU cores') +except ImportError: + _log.warning('Could not import multiprocessing, assuming 2 CPU cores') + +# IMPORT GOBJECT +try: + import gobject + gobject.threads_init() +except ImportError: + raise Exception('gobject could not be found') + +# IMPORT PYGST +try: + import pygst + + # We won't settle for less. For now, this is an arbitrary limit + # as we have not tested with > 0.10 + pygst.require('0.10') + + import gst + + import gst.extend.discoverer +except ImportError: + raise Exception('gst/pygst >= 0.10 could not be imported') + +import numpy + + +class AudioThumbnailer(object): + def __init__(self): + _log.info('Initializing {0}'.format(self.__class__.__name__)) + + def spectrogram(self, src, dst, **kw): + width = kw['width'] + height = int(kw.get('height', float(width) * 0.3)) + fft_size = kw.get('fft_size', 2048) + callback = kw.get('progress_callback') + + processor = audioprocessing.AudioProcessor( + src, + fft_size, + numpy.hanning) + + samples_per_pixel = processor.audio_file.nframes / float(width) + + spectrogram = audioprocessing.SpectrogramImage(width, height, fft_size) + + for x in range(width): + if callback and x % (width / 10) == 0: + callback((x * 100) / width) + + seek_point = int(x * samples_per_pixel) + + (spectral_centroid, db_spectrum) = processor.spectral_centroid( + seek_point) + + spectrogram.draw_spectrum(x, db_spectrum) + + if callback: + callback(100) + + spectrogram.save(dst) + + def thumbnail_spectrogram(self, src, dst, thumb_size): + ''' + Takes a spectrogram and creates a thumbnail from it + ''' + if not (type(thumb_size) == tuple and len(thumb_size) == 2): + raise Exception('thumb_size argument should be a tuple(width, height)') + + im = Image.open(src) + + im_w, im_h = [float(i) for i in im.size] + th_w, th_h = [float(i) for i in thumb_size] + + wadsworth_position = im_w * 0.3 + + start_x = max(( + wadsworth_position - ((im_h * (th_w / th_h)) / 2.0), + 0.0)) + + stop_x = start_x + (im_h * (th_w / th_h)) + + th = im.crop(( + int(start_x), 0, + int(stop_x), int(im_h))) + + if th.size[0] > th_w or th.size[1] > th_h: + th.thumbnail(thumb_size, Image.ANTIALIAS) + + th.save(dst) + + +class AudioTranscoder(object): + def __init__(self): + _log.info('Initializing {0}'.format(self.__class__.__name__)) + + # Instantiate MainLoop + self._loop = gobject.MainLoop() + self._failed = None + + def discover(self, src): + self._src_path = src + _log.info('Discovering {0}'.format(src)) + self._discovery_path = src + + self._discoverer = gst.extend.discoverer.Discoverer( + self._discovery_path) + self._discoverer.connect('discovered', self.__on_discovered) + self._discoverer.discover() + + self._loop.run() # Run MainLoop + + if self._failed: + raise self._failed + + # Once MainLoop has returned, return discovery data + return getattr(self, '_discovery_data', False) + + def __on_discovered(self, data, is_media): + if not is_media: + self._failed = BadMediaFail() + _log.error('Could not discover {0}'.format(self._src_path)) + self.halt() + + _log.debug('Discovered: {0}'.format(data.__dict__)) + + self._discovery_data = data + + # Gracefully shut down MainLoop + self.halt() + + def transcode(self, src, dst, **kw): + _log.info('Transcoding {0} into {1}'.format(src, dst)) + self._discovery_data = kw.get('data', self.discover(src)) + + self.__on_progress = kw.get('progress_callback') + + quality = kw.get('quality', 0.3) + + mux_string = kw.get( + 'mux_string', + 'vorbisenc quality={0} ! webmmux'.format(quality)) + + # Set up pipeline + self.pipeline = gst.parse_launch( + 'filesrc location="{src}" ! ' + 'decodebin2 ! queue ! audiorate tolerance={tolerance} ! ' + 'audioconvert ! audio/x-raw-float,channels=2 ! ' + '{mux_string} ! ' + 'progressreport silent=true ! ' + 'filesink location="{dst}"'.format( + src=src, + tolerance=80000000, + mux_string=mux_string, + dst=dst)) + + self.bus = self.pipeline.get_bus() + self.bus.add_signal_watch() + self.bus.connect('message', self.__on_bus_message) + + self.pipeline.set_state(gst.STATE_PLAYING) + + self._loop.run() + + def __on_bus_message(self, bus, message): + _log.debug(message) + + if (message.type == gst.MESSAGE_ELEMENT + and message.structure.get_name() == 'progress'): + data = dict(message.structure) + + if self.__on_progress: + self.__on_progress(data.get('percent')) + + _log.info('{0}% done...'.format( + data.get('percent'))) + elif message.type == gst.MESSAGE_EOS: + _log.info('Done') + self.halt() + + def halt(self): + if getattr(self, 'pipeline', False): + self.pipeline.set_state(gst.STATE_NULL) + del self.pipeline + _log.info('Quitting MainLoop gracefully...') + gobject.idle_add(self._loop.quit) + +if __name__ == '__main__': + import sys + logging.basicConfig() + _log.setLevel(logging.INFO) + + #transcoder = AudioTranscoder() + #data = transcoder.discover(sys.argv[1]) + #res = transcoder.transcode(*sys.argv[1:3]) + + thumbnailer = AudioThumbnailer() + + thumbnailer.spectrogram(*sys.argv[1:], width=640) diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py new file mode 100644 index 00000000..5130ef48 --- /dev/null +++ b/mediagoblin/media_types/image/__init__.py @@ -0,0 +1,55 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import datetime + +from mediagoblin.media_types import MediaManagerBase +from mediagoblin.media_types.image.processing import process_image, \ + sniff_handler + + +class ImageMediaManager(MediaManagerBase): + human_readable = "Image" + processor = staticmethod(process_image) + sniff_handler = staticmethod(sniff_handler) + display_template = "mediagoblin/media_displays/image.html" + default_thumb = "images/media_thumbs/image.png" + accepted_extensions = ["jpg", "jpeg", "png", "gif", "tiff"] + media_fetch_order = [u'medium', u'original', u'thumb'] + + def get_original_date(self): + """ + Get the original date and time from the EXIF information. Returns + either a datetime object or None (if anything goes wrong) + """ + if not self.entry.media_data or not self.entry.media_data.exif_all: + return None + + try: + # Try wrapped around all since exif_all might be none, + # EXIF DateTimeOriginal or printable might not exist + # or strptime might not be able to parse date correctly + exif_date = self.entry.media_data.exif_all[ + 'EXIF DateTimeOriginal']['printable'] + original_date = datetime.datetime.strptime( + exif_date, + '%Y:%m:%d %H:%M:%S') + return original_date + except (KeyError, ValueError): + return None + + +MEDIA_MANAGER = ImageMediaManager diff --git a/mediagoblin/media_types/image/migrations.py b/mediagoblin/media_types/image/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/image/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/image/models.py b/mediagoblin/media_types/image/models.py new file mode 100644 index 00000000..b2ea3960 --- /dev/null +++ b/mediagoblin/media_types/image/models.py @@ -0,0 +1,49 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from mediagoblin.db.base import Base + +from sqlalchemy import ( + Column, Integer, Float, ForeignKey) +from sqlalchemy.orm import relationship, backref +from mediagoblin.db.extratypes import JSONEncoded + + +BACKREF_NAME = "image__media_data" + + +class ImageData(Base): + __tablename__ = "image__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) + + width = Column(Integer) + height = Column(Integer) + exif_all = Column(JSONEncoded) + gps_longitude = Column(Float) + gps_latitude = Column(Float) + gps_altitude = Column(Float) + gps_direction = Column(Float) + + +DATA_MODEL = ImageData +MODELS = [ImageData] diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py new file mode 100644 index 00000000..bc0ce3f8 --- /dev/null +++ b/mediagoblin/media_types/image/processing.py @@ -0,0 +1,180 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +try: + from PIL import Image +except ImportError: + import Image +import os +import logging + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import BadMediaFail, FilenameBuilder +from mediagoblin.tools.exif import exif_fix_image_orientation, \ + extract_exif, clean_exif, get_gps_data, get_useful, \ + exif_image_needs_rotation + +_log = logging.getLogger(__name__) + +PIL_FILTERS = { + 'NEAREST': Image.NEAREST, + 'BILINEAR': Image.BILINEAR, + 'BICUBIC': Image.BICUBIC, + 'ANTIALIAS': Image.ANTIALIAS} + + +def resize_image(proc_state, resized, keyname, target_name, new_size, + exif_tags, workdir): + """ + Store a resized version of an image and return its pathname. + + Arguments: + proc_state -- the processing state for the image to resize + resized -- an image from Image.open() of the original image being resized + keyname -- Under what key to save in the db. + target_name -- public file path for the new resized image + exif_tags -- EXIF data for the original image + workdir -- directory path for storing converted image files + new_size -- 2-tuple size for the resized image + """ + config = mgg.global_config['media_type:mediagoblin.media_types.image'] + + resized = exif_fix_image_orientation(resized, exif_tags) # Fix orientation + + filter_config = config['resize_filter'] + try: + resize_filter = PIL_FILTERS[filter_config.upper()] + except KeyError: + raise Exception('Filter "{0}" not found, choose one of {1}'.format( + unicode(filter_config), + u', '.join(PIL_FILTERS.keys()))) + + resized.thumbnail(new_size, resize_filter) + + # Copy the new file to the conversion subdir, then remotely. + tmp_resized_filename = os.path.join(workdir, target_name) + with file(tmp_resized_filename, 'w') as resized_file: + resized.save(resized_file, quality=config['quality']) + proc_state.store_public(keyname, tmp_resized_filename, target_name) + + +def resize_tool(proc_state, force, keyname, target_name, + conversions_subdir, exif_tags): + # filename -- the filename of the original image being resized + filename = proc_state.get_queued_filename() + max_width = mgg.global_config['media:' + keyname]['max_width'] + max_height = mgg.global_config['media:' + keyname]['max_height'] + # If the size of the original file exceeds the specified size for the desized + # file, a target_name file is created and later associated with the media + # entry. + # Also created if the file needs rotation, or if forced. + try: + im = Image.open(filename) + except IOError: + raise BadMediaFail() + if force \ + or im.size[0] > max_width \ + or im.size[1] > max_height \ + or exif_image_needs_rotation(exif_tags): + resize_image( + proc_state, im, unicode(keyname), target_name, + (max_width, max_height), + exif_tags, conversions_subdir) + + +SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg'] + + +def sniff_handler(media_file, **kw): + if kw.get('media') is not None: # That's a double negative! + name, ext = os.path.splitext(kw['media'].filename) + clean_ext = ext[1:].lower() # Strip the . from ext and make lowercase + + if clean_ext in SUPPORTED_FILETYPES: + _log.info('Found file extension in supported filetypes') + return True + else: + _log.debug('Media present, extension not found in {0}'.format( + SUPPORTED_FILETYPES)) + else: + _log.warning('Need additional information (keyword argument \'media\')' + ' to be able to handle sniffing') + + return False + + +def process_image(proc_state): + """Code to process an image. Will be run by celery. + + A Workbench() represents a local tempory dir. It is automatically + cleaned up when this function exits. + """ + entry = proc_state.entry + workbench = proc_state.workbench + + # Conversions subdirectory to avoid collisions + conversions_subdir = os.path.join( + workbench.dir, 'conversions') + os.mkdir(conversions_subdir) + + queued_filename = proc_state.get_queued_filename() + name_builder = FilenameBuilder(queued_filename) + + # EXIF extraction + exif_tags = extract_exif(queued_filename) + gps_data = get_gps_data(exif_tags) + + # Always create a small thumbnail + resize_tool(proc_state, True, 'thumb', + name_builder.fill('{basename}.thumbnail{ext}'), + conversions_subdir, exif_tags) + + # Possibly create a medium + resize_tool(proc_state, False, 'medium', + name_builder.fill('{basename}.medium{ext}'), + conversions_subdir, exif_tags) + + # Copy our queued local workbench to its final destination + proc_state.copy_original(name_builder.fill('{basename}{ext}')) + + # Remove queued media file from storage and database + proc_state.delete_queue_file() + + # Insert exif data into database + exif_all = clean_exif(exif_tags) + + if len(exif_all): + entry.media_data_init(exif_all=exif_all) + + if len(gps_data): + for key in list(gps_data.keys()): + gps_data['gps_' + key] = gps_data.pop(key) + entry.media_data_init(**gps_data) + + +if __name__ == '__main__': + import sys + import pprint + + pp = pprint.PrettyPrinter() + + result = extract_exif(sys.argv[1]) + gps = get_gps_data(result) + clean = clean_exif(result) + useful = get_useful(clean) + + print pp.pprint( + clean) diff --git a/mediagoblin/media_types/pdf/__init__.py b/mediagoblin/media_types/pdf/__init__.py new file mode 100644 index 00000000..f0ba7867 --- /dev/null +++ b/mediagoblin/media_types/pdf/__init__.py @@ -0,0 +1,31 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.media_types import MediaManagerBase +from mediagoblin.media_types.pdf.processing import process_pdf, \ + sniff_handler + + +class PDFMediaManager(MediaManagerBase): + human_readable = "PDF" + processor = staticmethod(process_pdf) + sniff_handler = staticmethod(sniff_handler) + display_template = "mediagoblin/media_displays/pdf.html" + default_thumb = "images/media_thumbs/pdf.jpg" + accepted_extensions = ["pdf"] + + +MEDIA_MANAGER = PDFMediaManager diff --git a/mediagoblin/media_types/pdf/migrations.py b/mediagoblin/media_types/pdf/migrations.py new file mode 100644 index 00000000..f54c23ea --- /dev/null +++ b/mediagoblin/media_types/pdf/migrations.py @@ -0,0 +1,17 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +MIGRATIONS = {} diff --git a/mediagoblin/media_types/pdf/models.py b/mediagoblin/media_types/pdf/models.py new file mode 100644 index 00000000..c39262d1 --- /dev/null +++ b/mediagoblin/media_types/pdf/models.py @@ -0,0 +1,58 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from mediagoblin.db.base import Base + +from sqlalchemy import ( + Column, Float, Integer, String, DateTime, ForeignKey) +from sqlalchemy.orm import relationship, backref + + +BACKREF_NAME = "pdf__media_data" + + +class PdfData(Base): + __tablename__ = "pdf__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) + pages = Column(Integer) + + # These are taken from what pdfinfo can do, perhaps others make sense too + pdf_author = Column(String) + pdf_title = Column(String) + # note on keywords: this is the pdf parsed string, it should be considered a cached + # value like the rest of these values, since they can be deduced at query time / client + # side too. + pdf_keywords = Column(String) + pdf_creator = Column(String) + pdf_producer = Column(String) + pdf_creation_date = Column(DateTime) + pdf_modified_date = Column(DateTime) + pdf_version_major = Column(Integer) + pdf_version_minor = Column(Integer) + pdf_page_size_width = Column(Float) # unit: pts + pdf_page_size_height = Column(Float) + pdf_pages = Column(Integer) + + +DATA_MODEL = PdfData +MODELS = [PdfData] diff --git a/mediagoblin/media_types/pdf/processing.py b/mediagoblin/media_types/pdf/processing.py new file mode 100644 index 00000000..49742fd7 --- /dev/null +++ b/mediagoblin/media_types/pdf/processing.py @@ -0,0 +1,277 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import os +import logging +import dateutil.parser +from subprocess import PIPE, Popen + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import (create_pub_filepath, + FilenameBuilder, BadMediaFail) +from mediagoblin.tools.translate import fake_ugettext_passthrough as _ + +_log = logging.getLogger(__name__) + +# TODO - cache (memoize) util + +# This is a list created via uniconv --show and hand removing some types that +# we already support via other media types better. +unoconv_supported = [ + 'bib', # - BibTeX [.bib] + #bmp - Windows Bitmap [.bmp] + 'csv', # - Text CSV [.csv] + 'dbf', # - dBASE [.dbf] + 'dif', # - Data Interchange Format [.dif] + 'doc6', # - Microsoft Word 6.0 [.doc] + 'doc95', # - Microsoft Word 95 [.doc] + 'docbook', # - DocBook [.xml] + 'doc', # - Microsoft Word 97/2000/XP [.doc] + 'docx7', # - Microsoft Office Open XML [.docx] + 'docx', # - Microsoft Office Open XML [.docx] + #emf - Enhanced Metafile [.emf] + 'eps', # - Encapsulated PostScript [.eps] + 'fodp', # - OpenDocument Presentation (Flat XML) [.fodp] + 'fods', # - OpenDocument Spreadsheet (Flat XML) [.fods] + 'fodt', # - OpenDocument Text (Flat XML) [.fodt] + #gif - Graphics Interchange Format [.gif] + 'html', # - HTML Document (OpenOffice.org Writer) [.html] + #jpg - Joint Photographic Experts Group [.jpg] + 'latex', # - LaTeX 2e [.ltx] + 'mediawiki', # - MediaWiki [.txt] + 'met', # - OS/2 Metafile [.met] + 'odd', # - OpenDocument Drawing [.odd] + 'odg', # - ODF Drawing (Impress) [.odg] + 'odp', # - ODF Presentation [.odp] + 'ods', # - ODF Spreadsheet [.ods] + 'odt', # - ODF Text Document [.odt] + 'ooxml', # - Microsoft Office Open XML [.xml] + 'otg', # - OpenDocument Drawing Template [.otg] + 'otp', # - ODF Presentation Template [.otp] + 'ots', # - ODF Spreadsheet Template [.ots] + 'ott', # - Open Document Text [.ott] + #pbm - Portable Bitmap [.pbm] + #pct - Mac Pict [.pct] + 'pdb', # - AportisDoc (Palm) [.pdb] + #pdf - Portable Document Format [.pdf] + #pgm - Portable Graymap [.pgm] + #png - Portable Network Graphic [.png] + 'pot', # - Microsoft PowerPoint 97/2000/XP Template [.pot] + 'potm', # - Microsoft PowerPoint 2007/2010 XML Template [.potm] + #ppm - Portable Pixelmap [.ppm] + 'pps', # - Microsoft PowerPoint 97/2000/XP (Autoplay) [.pps] + 'ppt', # - Microsoft PowerPoint 97/2000/XP [.ppt] + 'pptx', # - Microsoft PowerPoint 2007/2010 XML [.pptx] + 'psw', # - Pocket Word [.psw] + 'pwp', # - PlaceWare [.pwp] + 'pxl', # - Pocket Excel [.pxl] + #ras - Sun Raster Image [.ras] + 'rtf', # - Rich Text Format [.rtf] + 'sda', # - StarDraw 5.0 (OpenOffice.org Impress) [.sda] + 'sdc3', # - StarCalc 3.0 [.sdc] + 'sdc4', # - StarCalc 4.0 [.sdc] + 'sdc', # - StarCalc 5.0 [.sdc] + 'sdd3', # - StarDraw 3.0 (OpenOffice.org Impress) [.sdd] + 'sdd4', # - StarImpress 4.0 [.sdd] + 'sdd', # - StarImpress 5.0 [.sdd] + 'sdw3', # - StarWriter 3.0 [.sdw] + 'sdw4', # - StarWriter 4.0 [.sdw] + 'sdw', # - StarWriter 5.0 [.sdw] + 'slk', # - SYLK [.slk] + 'stc', # - OpenOffice.org 1.0 Spreadsheet Template [.stc] + 'std', # - OpenOffice.org 1.0 Drawing Template [.std] + 'sti', # - OpenOffice.org 1.0 Presentation Template [.sti] + 'stw', # - Open Office.org 1.0 Text Document Template [.stw] + #svg - Scalable Vector Graphics [.svg] + 'svm', # - StarView Metafile [.svm] + 'swf', # - Macromedia Flash (SWF) [.swf] + 'sxc', # - OpenOffice.org 1.0 Spreadsheet [.sxc] + 'sxd3', # - StarDraw 3.0 [.sxd] + 'sxd5', # - StarDraw 5.0 [.sxd] + 'sxd', # - OpenOffice.org 1.0 Drawing (OpenOffice.org Impress) [.sxd] + 'sxi', # - OpenOffice.org 1.0 Presentation [.sxi] + 'sxw', # - Open Office.org 1.0 Text Document [.sxw] + #text - Text Encoded [.txt] + #tiff - Tagged Image File Format [.tiff] + #txt - Text [.txt] + 'uop', # - Unified Office Format presentation [.uop] + 'uos', # - Unified Office Format spreadsheet [.uos] + 'uot', # - Unified Office Format text [.uot] + 'vor3', # - StarDraw 3.0 Template (OpenOffice.org Impress) [.vor] + 'vor4', # - StarWriter 4.0 Template [.vor] + 'vor5', # - StarDraw 5.0 Template (OpenOffice.org Impress) [.vor] + 'vor', # - StarCalc 5.0 Template [.vor] + #wmf - Windows Metafile [.wmf] + 'xhtml', # - XHTML Document [.html] + 'xls5', # - Microsoft Excel 5.0 [.xls] + 'xls95', # - Microsoft Excel 95 [.xls] + 'xls', # - Microsoft Excel 97/2000/XP [.xls] + 'xlt5', # - Microsoft Excel 5.0 Template [.xlt] + 'xlt95', # - Microsoft Excel 95 Template [.xlt] + 'xlt', # - Microsoft Excel 97/2000/XP Template [.xlt] + #xpm - X PixMap [.xpm] +] + +def is_unoconv_working(): + # TODO: must have libreoffice-headless installed too, need to check for it + unoconv = where('unoconv') + if not unoconv: + return False + try: + proc = Popen([unoconv, '--show'], stderr=PIPE) + output = proc.stderr.read() + except OSError, e: + _log.warn(_('unoconv failing to run, check log file')) + return False + if 'ERROR' in output: + return False + return True + +def supported_extensions(cache=[None]): + if cache[0] == None: + cache[0] = 'pdf' + if is_unoconv_working(): + cache.extend(unoconv_supported) + return cache + +def where(name): + for p in os.environ['PATH'].split(os.pathsep): + fullpath = os.path.join(p, name) + if os.path.exists(fullpath): + return fullpath + return None + +def check_prerequisites(): + if not where('pdfinfo'): + _log.warn('missing pdfinfo') + return False + if not where('pdftocairo'): + _log.warn('missing pdfcairo') + return False + return True + +def sniff_handler(media_file, **kw): + if not check_prerequisites(): + return False + if kw.get('media') is not None: + name, ext = os.path.splitext(kw['media'].filename) + clean_ext = ext[1:].lower() + + if clean_ext in supported_extensions(): + return True + + return False + +def create_pdf_thumb(original, thumb_filename, width, height): + # Note: pdftocairo adds '.png', remove it + thumb_filename = thumb_filename[:-4] + executable = where('pdftocairo') + args = [executable, '-scale-to', str(min(width, height)), + '-singlefile', '-png', original, thumb_filename] + _log.debug('calling {0}'.format(repr(' '.join(args)))) + Popen(executable=executable, args=args).wait() + +def pdf_info(original): + """ + Extract dictionary of pdf information. This could use a library instead + of a process. + + Note: I'm assuming pdfinfo output is sanitized (integers where integers are + expected, etc.) - if this is wrong then an exception will be raised and caught + leading to the dreaded error page. It seems a safe assumption. + """ + ret_dict = {} + pdfinfo = where('pdfinfo') + try: + proc = Popen(executable=pdfinfo, + args=[pdfinfo, original], stdout=PIPE) + lines = proc.stdout.readlines() + except OSError: + _log.debug('pdfinfo could not read the pdf file.') + raise BadMediaFail() + + info_dict = dict([[part.strip() for part in l.strip().split(':', 1)] + for l in lines if ':' in l]) + + for date_key in [('pdf_mod_date', 'ModDate'), + ('pdf_creation_date', 'CreationDate')]: + if date_key in info_dict: + ret_dict[date_key] = dateutil.parser.parse(info_dict[date_key]) + for db_key, int_key in [('pdf_pages', 'Pages')]: + if int_key in info_dict: + ret_dict[db_key] = int(info_dict[int_key]) + + # parse 'PageSize' field: 595 x 842 pts (A4) + page_size_parts = info_dict['Page size'].split() + ret_dict['pdf_page_size_width'] = float(page_size_parts[0]) + ret_dict['pdf_page_size_height'] = float(page_size_parts[2]) + + for db_key, str_key in [('pdf_keywords', 'Keywords'), + ('pdf_creator', 'Creator'), ('pdf_producer', 'Producer'), + ('pdf_author', 'Author'), ('pdf_title', 'Title')]: + ret_dict[db_key] = info_dict.get(str_key, None) + ret_dict['pdf_version_major'], ret_dict['pdf_version_minor'] = \ + map(int, info_dict['PDF version'].split('.')) + + return ret_dict + +def process_pdf(proc_state): + """Code to process a pdf file. Will be run by celery. + + A Workbench() represents a local tempory dir. It is automatically + cleaned up when this function exits. + """ + entry = proc_state.entry + workbench = proc_state.workbench + + queued_filename = proc_state.get_queued_filename() + name_builder = FilenameBuilder(queued_filename) + + # Copy our queued local workbench to its final destination + original_dest = name_builder.fill('{basename}{ext}') + proc_state.copy_original(original_dest) + + # Create a pdf if this is a different doc, store pdf for viewer + ext = queued_filename.rsplit('.', 1)[-1].lower() + if ext == 'pdf': + pdf_filename = queued_filename + else: + pdf_filename = queued_filename.rsplit('.', 1)[0] + '.pdf' + unoconv = where('unoconv') + call(executable=unoconv, + args=[unoconv, '-v', '-f', 'pdf', queued_filename]) + if not os.path.exists(pdf_filename): + _log.debug('unoconv failed to convert file to pdf') + raise BadMediaFail() + proc_state.store_public(keyname=u'pdf', local_file=pdf_filename) + + pdf_info_dict = pdf_info(pdf_filename) + + for name, width, height in [ + (u'thumb', mgg.global_config['media:thumb']['max_width'], + mgg.global_config['media:thumb']['max_height']), + (u'medium', mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height']), + ]: + filename = name_builder.fill('{basename}.%s.png' % name) + path = workbench.joinpath(filename) + create_pdf_thumb(pdf_filename, path, width, height) + assert(os.path.exists(path)) + proc_state.store_public(keyname=name, local_file=path) + + proc_state.delete_queue_file() + + entry.media_data_init(**pdf_info_dict) + entry.save() diff --git a/mediagoblin/media_types/stl/__init__.py b/mediagoblin/media_types/stl/__init__.py new file mode 100644 index 00000000..6ae8a8b9 --- /dev/null +++ b/mediagoblin/media_types/stl/__init__.py @@ -0,0 +1,31 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.media_types import MediaManagerBase +from mediagoblin.media_types.stl.processing import process_stl, \ + sniff_handler + + +class STLMediaManager(MediaManagerBase): + human_readable = "stereo lithographics" + processor = staticmethod(process_stl) + sniff_handler = staticmethod(sniff_handler) + display_template = "mediagoblin/media_displays/stl.html" + default_thumb = "images/media_thumbs/video.jpg" + accepted_extensions = ["obj", "stl"] + + +MEDIA_MANAGER = STLMediaManager diff --git a/mediagoblin/media_types/stl/assets/blender_render.blend b/mediagoblin/media_types/stl/assets/blender_render.blend 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 new file mode 100644 index 00000000..569cf11a --- /dev/null +++ b/mediagoblin/media_types/video/__init__.py @@ -0,0 +1,36 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.media_types import MediaManagerBase +from mediagoblin.media_types.video.processing import process_video, \ + sniff_handler + + +class VideoMediaManager(MediaManagerBase): + human_readable = "Video" + processor = staticmethod(process_video) + sniff_handler = staticmethod(sniff_handler) + display_template = "mediagoblin/media_displays/video.html" + default_thumb = "images/media_thumbs/video.jpg" + accepted_extensions = [ + "mp4", "mov", "webm", "avi", "3gp", "3gpp", "mkv", "ogv", "m4v"] + + # Used by the media_entry.get_display_media method + media_fetch_order = [u'webm_640', u'original'] + default_webm_type = 'video/webm; codecs="vp8, vorbis"' + + +MEDIA_MANAGER = VideoMediaManager diff --git a/mediagoblin/media_types/video/devices/web-advanced.json b/mediagoblin/media_types/video/devices/web-advanced.json new file mode 100644 index 00000000..ce1d22ff --- /dev/null +++ b/mediagoblin/media_types/video/devices/web-advanced.json @@ -0,0 +1,505 @@ +{ + "make": "Generic", + "model": "Web Browser (Advanced)", + "description": "Media for World Wide Web", + "version": "0.1", + "author": { + "name": "Dionisio E Alonso", + "email": "dealonso@gmail.com" + }, + "icon": "file://web.svg", + "default": "WebM 480p", + "presets": [ + { + "name": "H.264 720p", + "extension": "mp4", + "container": "qtmux", + "vcodec": { + "name": "x264enc", + "container": "qtmux", + "width": [ + 960, 1280 + ], + "height": [ + 720, 720 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "qtmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + }, + { + "name": "WebM 720p", + "extension": "webm", + "container": "webmmux", + "icon": "file://web-webm.svg", + "vcodec": { + "name": "vp8enc", + "container": "webmmux", + "width": [ + 960, 1280 + ], + "height": [ + 720, 720 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "quality=5.75 threads=%(threads)s speed=2" + ] + }, + "acodec": { + "name": "vorbisenc", + "container": "webmmux", + "width": [ + 8, 32 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "quality=0.3" + ] + } + }, + { + "name": "Flash Video 720p", + "extension": "flv", + "icon": "file://web-flv.png", + "container": "flvmux", + "vcodec": { + "name": "x264enc", + "container": "flvmux", + "width": [ + 960, 1280 + ], + "height": [ + 720, 720 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "flvmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + }, + + { + "name": "H.264 576p", + "extension": "mp4", + "container": "qtmux", + "vcodec": { + "name": "x264enc", + "container": "qtmux", + "width": [ + 768, 1024 + ], + "height": [ + 576, 576 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "qtmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + }, + { + "name": "WebM 576p", + "extension": "webm", + "container": "webmmux", + "icon": "file://web-webm.svg", + "vcodec": { + "name": "vp8enc", + "container": "webmmux", + "width": [ + 768, 1024 + ], + "height": [ + 576, 576 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "quality=5.75 threads=%(threads)s speed=2" + ] + }, + "acodec": { + "name": "vorbisenc", + "container": "webmmux", + "width": [ + 8, 32 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "quality=0.3" + ] + } + }, + { + "name": "Flash Video 576p", + "extension": "flv", + "icon": "file://web-flv.png", + "container": "flvmux", + "vcodec": { + "name": "x264enc", + "container": "flvmux", + "width": [ + 768, 1024 + ], + "height": [ + 576, 576 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "flvmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + }, + + { + "name": "H.264 480p", + "extension": "mp4", + "container": "qtmux", + "vcodec": { + "name": "x264enc", + "container": "qtmux", + "width": [ + 640, 854 + ], + "height": [ + 480, 480 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "qtmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + }, + { + "name": "WebM 480p", + "extension": "webm", + "container": "webmmux", + "icon": "file://web-webm.svg", + "vcodec": { + "name": "vp8enc", + "container": "webmmux", + "width": [ + 640, 854 + ], + "height": [ + 480, 480 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "quality=5.75 threads=%(threads)s speed=2" + ] + }, + "acodec": { + "name": "vorbisenc", + "container": "webmmux", + "width": [ + 8, 32 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "quality=0.3" + ] + } + }, + { + "name": "Flash Video 480p", + "extension": "flv", + "icon": "file://web-flv.png", + "container": "flvmux", + "vcodec": { + "name": "x264enc", + "container": "flvmux", + "width": [ + 640, 854 + ], + "height": [ + 480, 480 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "flvmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + }, + + { + "name": "H.264 360p", + "extension": "mp4", + "container": "qtmux", + "vcodec": { + "name": "x264enc", + "container": "qtmux", + "width": [ + 480, 640 + ], + "height": [ + 360, 360 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "qtmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + }, + { + "name": "WebM 360p", + "extension": "webm", + "container": "webmmux", + "icon": "file://web-webm.svg", + "vcodec": { + "name": "vp8enc", + "container": "webmmux", + "width": [ + 480, 640 + ], + "height": [ + 360, 360 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "quality=5.75 threads=%(threads)s speed=2" + ] + }, + "acodec": { + "name": "vorbisenc", + "container": "webmmux", + "width": [ + 8, 32 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "quality=0.3" + ] + } + }, + { + "name": "Flash Video 360p", + "extension": "flv", + "icon": "file://web-flv.png", + "container": "flvmux", + "vcodec": { + "name": "x264enc", + "container": "flvmux", + "width": [ + 480, 640 + ], + "height": [ + 360, 360 + ], + "rate": [ + 1, 30 + ], + "passes": [ + "pass=qual quantizer=23 subme=6 cabac=0 threads=0" + ] + }, + "acodec": { + "name": "faac", + "container": "flvmux", + "width": [ + 8, 24 + ], + "depth": [ + 8, 24 + ], + "rate": [ + 8000, 96000 + ], + "channels": [ + 1, 2 + ], + "passes": [ + "bitrate=131072 profile=LC" + ] + } + } + ] +} diff --git a/mediagoblin/media_types/video/devices/web-flv.png b/mediagoblin/media_types/video/devices/web-flv.png Binary files differnew file mode 100644 index 00000000..b75699f4 --- /dev/null +++ b/mediagoblin/media_types/video/devices/web-flv.png diff --git a/mediagoblin/media_types/video/devices/web-webm.svg b/mediagoblin/media_types/video/devices/web-webm.svg new file mode 100644 index 00000000..4e5b3e97 --- /dev/null +++ b/mediagoblin/media_types/video/devices/web-webm.svg @@ -0,0 +1,259 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="48px" + height="48px" + id="svg2816" + version="1.1" + inkscape:version="0.47 r22583" + sodipodi:docname="web-webm.svg"> + <defs + id="defs2818"> + <linearGradient + id="linearGradient3656"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop3658" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop3660" /> + </linearGradient> + <linearGradient + id="linearGradient3632"> + <stop + style="stop-color:#ffffff;stop-opacity:0.54901963;" + offset="0" + id="stop3634" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop3636" /> + </linearGradient> + <linearGradient + id="linearGradient3622"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop3624" /> + <stop + style="stop-color:#d3d7cf;stop-opacity:1;" + offset="1" + id="stop3626" /> + </linearGradient> + <linearGradient + id="linearGradient3600"> + <stop + style="stop-color:#8ae234;stop-opacity:1;" + offset="0" + id="stop3602" /> + <stop + style="stop-color:#4e9a06;stop-opacity:1;" + offset="1" + id="stop3604" /> + </linearGradient> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective2824" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3600" + id="linearGradient3606" + x1="20.256382" + y1="2.546674" + x2="20.256382" + y2="46.881901" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3622" + id="linearGradient3628" + x1="21.2349" + y1="7.948472" + x2="21.2349" + y2="40.191879" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3632" + id="linearGradient3638" + x1="6.4826794" + y1="4.543263" + x2="25.363527" + y2="35.227882" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(0,-0.35355339)" /> + <inkscape:perspective + id="perspective3693" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3600-5" + id="linearGradient3606-9" + x1="20.256382" + y1="2.546674" + x2="20.256382" + y2="46.881901" + gradientUnits="userSpaceOnUse" /> + <linearGradient + id="linearGradient3600-5"> + <stop + style="stop-color:#8ae234;stop-opacity:1;" + offset="0" + id="stop3602-7" /> + <stop + style="stop-color:#4e9a06;stop-opacity:1;" + offset="1" + id="stop3604-2" /> + </linearGradient> + <filter + inkscape:collect="always" + id="filter3731"> + <feGaussianBlur + inkscape:collect="always" + stdDeviation="0.82730657" + id="feGaussianBlur3733" /> + </filter> + <inkscape:perspective + id="perspective3749" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + <inkscape:perspective + id="perspective3782" + inkscape:persp3d-origin="0.5 : 0.33333333 : 1" + inkscape:vp_z="1 : 0.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_x="0 : 0.5 : 1" + sodipodi:type="inkscape:persp3d" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="8" + inkscape:cx="20.51741" + inkscape:cy="22.534228" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:grid-bbox="true" + inkscape:document-units="px" + showguides="true" + inkscape:guide-bbox="true" + inkscape:window-width="1099" + inkscape:window-height="834" + inkscape:window-x="801" + inkscape:window-y="106" + inkscape:window-maximized="0"> + <inkscape:grid + type="xygrid" + id="grid3608" /> + </sodipodi:namedview> + <metadata + id="metadata2821"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="Layer 1" + inkscape:groupmode="layer"> + <path + sodipodi:type="star" + style="fill:#000000;fill-opacity:0.2869955;stroke:none;filter:url(#filter3731)" + id="path3598-4" + sodipodi:sides="3" + sodipodi:cx="13.857143" + sodipodi:cy="24.714287" + sodipodi:r1="25.596954" + sodipodi:r2="12.798477" + sodipodi:arg1="0" + sodipodi:arg2="1.0471976" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="M 39.454098,24.714287 20.256381,35.798093 1.0586662,46.8819 l 0,-22.167614 0,-22.1676119 19.1977168,11.0838069 19.197715,11.083806 z" + transform="matrix(1.0537808,0,0,1.0537808,3.6163385,-1.9600717)" /> + <path + sodipodi:type="star" + style="fill:url(#linearGradient3606);fill-opacity:1;stroke:#366a04;stroke-width:1.05497880999999993;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-linejoin:round" + id="path3598" + sodipodi:sides="3" + sodipodi:cx="13.857143" + sodipodi:cy="24.714287" + sodipodi:r1="25.596954" + sodipodi:r2="12.798477" + sodipodi:arg1="0" + sodipodi:arg2="1.0471976" + inkscape:flatsided="false" + inkscape:rounded="0" + inkscape:randomized="0" + d="M 39.454098,24.714287 20.256381,35.798093 1.0586662,46.8819 l 0,-22.167614 0,-22.1676119 19.1977168,11.0838069 19.197715,11.083806 z" + transform="matrix(0.94788634,0,0,0.94788634,5.0257749,0.56128794)" /> + <path + style="fill:url(#linearGradient3628);stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1" + d="m 6.5304575,9.646791 8.7347075,20.091724 4.674611,-18.160553 4.525987,2.612472 3.885316,12.559503 4.403755,-7.765833 1.744319,1.009296 -2.127799,9.211229 -6.155446,3.554753 -4.028978,-9.439016 -2.255629,13.086534 -5.852703,3.373025 -7.5584205,-9.989634 0.01028,-20.1435 z" + id="path3620" + sodipodi:nodetypes="cccccccccccccc" /> + <path + style="fill:none;stroke:url(#linearGradient3638);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 6.9826793,42.785087 0,-38.0953773 32.9068657,18.9987873" + id="path3630" + sodipodi:nodetypes="ccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275" + d="M 6.6184028,8.6135689 15.026019,28.134068 19.45616,10.995613" + id="path3739" + sodipodi:nodetypes="ccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275" + d="m 25.081121,14.552251 3.345117,11.020499 3.93014,-6.825955" + id="path3739-5" + sodipodi:nodetypes="ccc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275" + d="m 6.6291261,30.85266 7.0710679,9.280777" + id="path3772" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275" + d="m 34.736621,20.290253 -2.032932,8.794642" + id="path3772-6" + sodipodi:nodetypes="cc" /> + <path + style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.15686275" + d="m 20.594485,35.934991 1.811961,-10.650796 3.270369,7.778174" + id="path3796" + sodipodi:nodetypes="ccc" /> + </g> +</svg> diff --git a/mediagoblin/media_types/video/devices/web.svg b/mediagoblin/media_types/video/devices/web.svg new file mode 100644 index 00000000..c0c68244 --- /dev/null +++ b/mediagoblin/media_types/video/devices/web.svg @@ -0,0 +1,982 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/apps"
+ sodipodi:docname="internet-web-browser.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs3">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective156" />
+ <linearGradient
+ id="linearGradient4750">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4752" />
+ <stop
+ style="stop-color:#fefefe;stop-opacity:1.0000000;"
+ offset="0.37931034"
+ id="stop4758" />
+ <stop
+ style="stop-color:#1d1d1d;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop4754" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4350">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4352" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4354" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3962"
+ id="radialGradient3968"
+ gradientTransform="scale(0.999989,1.000011)"
+ cx="18.247644"
+ cy="15.716079"
+ fx="18.247644"
+ fy="15.716079"
+ r="29.993349"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4114"
+ id="radialGradient4120"
+ gradientTransform="scale(1.643990,0.608276)"
+ cx="15.115514"
+ cy="63.965388"
+ fx="15.115514"
+ fy="63.965388"
+ r="12.289036"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4126"
+ id="radialGradient4132"
+ gradientTransform="scale(0.999989,1.000011)"
+ cx="15.601279"
+ cy="12.142302"
+ fx="15.601279"
+ fy="12.142302"
+ r="43.526714"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4350"
+ id="radialGradient4356"
+ gradientTransform="scale(1.179536,0.847791)"
+ cx="11.826907"
+ cy="10.476453"
+ fx="11.826907"
+ fy="10.476453"
+ r="32.664848"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4750"
+ id="radialGradient4756"
+ gradientTransform="scale(1.036822,0.964486)"
+ cx="18.633780"
+ cy="17.486208"
+ fx="18.934305"
+ fy="17.810213"
+ r="40.692665"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1460"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1462"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1466"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1468"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1470"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1474"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1476"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1478"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1482"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1484"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1486"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1490"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1492"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1494"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1498"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1500"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1502"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1506"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1508"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1510"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1514"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1516"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1518"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1522"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1524"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1526"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1528"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1530"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1532"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1534"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1536"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1538"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1540"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1542"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1544"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1546"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1550"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1552"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1554"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ <radialGradient
+ r="40.692665"
+ fy="17.810213"
+ fx="18.934305"
+ cy="17.486208"
+ cx="18.633780"
+ gradientTransform="scale(1.036822,0.964486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient1558"
+ xlink:href="#linearGradient4750"
+ inkscape:collect="always" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="9.8994949"
+ inkscape:cx="25.799661"
+ inkscape:cy="24.622653"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1440"
+ inkscape:window-height="823"
+ inkscape:window-x="0"
+ inkscape:window-y="30"
+ inkscape:showpageshadow="false" />
+ <metadata
+ id="metadata4">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Globe</dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>globe</rdf:li>
+ <rdf:li>international</rdf:li>
+ <rdf:li>web</rdf:li>
+ <rdf:li>www</rdf:li>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>network</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient4120);fill-opacity:1.0000000;stroke:none;stroke-opacity:1.0000000"
+ id="path4112"
+ sodipodi:cx="24.849752"
+ sodipodi:cy="38.908627"
+ sodipodi:rx="20.203051"
+ sodipodi:ry="7.4751287"
+ d="M 45.052803 38.908627 A 20.203051 7.4751287 0 1 1 4.6467018,38.908627 A 20.203051 7.4751287 0 1 1 45.052803 38.908627 z"
+ transform="matrix(1.000000,0.000000,0.000000,1.243244,0.000000,-10.27241)" />
+ <path
+ style="fill:url(#radialGradient3968);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#39396c;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 43.959853,23.485499 C 43.959853,34.195217 35.277750,42.877222 24.569505,42.877222 C 13.860279,42.877222 5.1786663,34.195119 5.1786663,23.485499 C 5.1786663,12.776272 13.860279,4.0951517 24.569505,4.0951517 C 35.277750,4.0951517 43.959853,12.776272 43.959853,23.485499 L 43.959853,23.485499 z "
+ id="path3214" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.42159382;fill:url(#radialGradient4356);fill-opacity:1.0000000;stroke:none;stroke-opacity:1.0000000"
+ id="path4348"
+ sodipodi:cx="17.778685"
+ sodipodi:cy="15.271057"
+ sodipodi:rx="12.929953"
+ sodipodi:ry="9.2934036"
+ d="M 30.708637 15.271057 A 12.929953 9.2934036 0 1 1 4.8487320,15.271057 A 12.929953 9.2934036 0 1 1 30.708637 15.271057 z"
+ transform="matrix(0.835938,0.000000,0.000000,1.000000,9.886868,0.000000)" />
+ <g
+ id="g4136"
+ style="fill:#000000;fill-opacity:0.71345031;fill-rule:nonzero;stroke:none;stroke-miterlimit:4.0000000"
+ transform="matrix(0.982371,0.000000,0.000000,0.982371,0.121079,0.232914)">
+ <g
+ id="g4138">
+ <g
+ id="g4142">
+ <path
+ d="M 44.071300,20.714400 C 44.071300,20.977100 44.071300,20.714400 44.071300,20.714400 L 43.526400,21.331600 C 43.192400,20.938000 42.817400,20.607000 42.436600,20.261300 L 41.600700,20.384300 L 40.837000,19.521000 L 40.837000,20.589400 L 41.491300,21.084500 L 41.926800,21.577700 L 42.508800,20.919500 C 42.655300,21.193900 42.799800,21.468300 42.945300,21.742700 L 42.945300,22.565000 L 42.290000,23.305200 L 41.090800,24.128400 L 40.182600,25.034700 L 39.600600,24.374500 L 39.891600,23.634300 L 39.310500,22.976100 L 38.329100,20.878400 L 37.493200,19.933100 L 37.274400,20.179200 L 37.602500,21.372600 L 38.219700,22.071800 C 38.572200,23.089400 38.920900,24.062000 39.383800,25.034700 C 40.101600,25.034700 40.778300,24.958500 41.491200,24.868700 L 41.491200,25.444900 L 40.619100,27.584100 L 39.819300,28.488400 L 39.165000,29.888800 C 39.165000,30.656400 39.165000,31.424000 39.165000,32.191500 L 39.383800,33.097800 L 39.020500,33.508000 L 38.219700,34.002100 L 37.383800,34.701300 L 38.075200,35.482600 L 37.129900,36.306800 L 37.311500,36.840000 L 35.893500,38.445500 L 34.949200,38.445500 L 34.149400,38.939600 L 33.639600,38.939600 L 33.639600,38.281400 L 33.422800,36.963000 C 33.141500,36.136800 32.848600,35.316500 32.550700,34.496200 C 32.550700,33.890700 32.586800,33.291100 32.623000,32.685700 L 32.987300,31.863400 L 32.477500,30.875100 L 32.514600,29.517700 L 31.823200,28.736400 L 32.168900,27.605500 L 31.606400,26.967300 L 30.624000,26.967300 L 30.296900,26.597200 L 29.315500,27.214900 L 28.916100,26.761300 L 28.006900,27.543000 C 27.389700,26.843300 26.771500,26.144100 26.153400,25.444900 L 25.426800,23.716400 L 26.081100,22.730100 L 25.717800,22.319000 L 26.516600,20.425400 C 27.172900,19.609000 27.858400,18.825800 28.551800,18.039700 L 29.788100,17.710600 L 31.169000,17.546500 L 32.114300,17.793600 L 33.459000,19.150000 L 33.931700,18.615800 L 34.585000,18.533800 L 35.821300,18.944900 L 36.766600,18.944900 L 37.420900,18.368700 L 37.711900,17.957600 L 37.056600,17.546500 L 35.965800,17.464500 C 35.663100,17.044600 35.381800,16.603200 35.022400,16.230100 L 34.658100,16.394200 L 34.512600,17.464500 L 33.858300,16.724300 L 33.713800,15.900100 L 32.987200,15.325900 L 32.695200,15.325900 L 33.422700,16.148200 L 33.131700,16.888400 L 32.550600,17.052500 L 32.913900,16.312300 L 32.258600,15.984200 L 31.678500,15.326000 L 30.586700,15.572100 L 30.442200,15.900200 L 29.787900,16.312300 L 29.424600,17.217600 L 28.516400,17.669700 L 28.116000,17.217600 L 27.680500,17.217600 L 27.680500,15.736200 L 28.625800,15.242100 L 29.352400,15.242100 L 29.205900,14.666900 L 28.625800,14.090700 L 29.606300,13.884600 L 30.151200,13.268400 L 30.586700,12.527200 L 31.387500,12.527200 L 31.168700,11.952000 L 31.678500,11.622900 L 31.678500,12.281100 L 32.768300,12.527200 L 33.858100,11.622900 L 33.931300,11.210800 L 34.875600,10.553100 C 34.533800,10.595600 34.192000,10.626800 33.858000,10.717700 L 33.858000,9.9766000 L 34.221300,9.1538000 L 33.858000,9.1538000 L 33.059600,9.8940000 L 32.840800,10.305600 L 33.059600,10.882300 L 32.695300,11.868600 L 32.114200,11.539500 L 31.606400,10.964300 L 30.805600,11.539500 L 30.514600,10.223600 L 31.895500,9.3188000 L 31.895500,8.8247000 L 32.768500,8.2490000 L 34.149400,7.9194000 L 35.094700,8.2490000 L 36.838800,8.5781000 L 36.403300,9.0713000 L 35.458000,9.0713000 L 36.403300,10.058600 L 37.129900,9.2363000 L 37.350600,8.8745000 C 37.350600,8.8745000 40.137700,11.372500 41.730500,14.105000 C 43.323300,16.838400 44.071300,20.060100 44.071300,20.714400 z "
+ id="path4144" />
+ </g>
+ </g>
+ <g
+ id="g4146">
+ <g
+ id="g4150">
+ <path
+ d="M 26.070300,9.2363000 L 25.997100,9.7295000 L 26.506900,10.058600 L 27.378000,9.4829000 L 26.942500,8.9892000 L 26.360500,9.3188000 L 26.070500,9.2363000"
+ id="path4152" />
+ </g>
+ </g>
+ <g
+ id="g4154">
+ <g
+ id="g4158">
+ <path
+ d="M 26.870100,5.8633000 L 24.979500,5.1226000 L 22.799800,5.3692000 L 20.109400,6.1094000 L 19.600600,6.6035000 L 21.272500,7.7549000 L 21.272500,8.4131000 L 20.618200,9.0713000 L 21.491200,10.800300 L 22.071300,10.470200 L 22.799800,9.3188000 C 23.922800,8.9716000 24.929700,8.5781000 25.997100,8.0844000 L 26.870100,5.8632000"
+ id="path4160" />
+ </g>
+ </g>
+ <g
+ id="g4162">
+ <g
+ id="g4166">
+ <path
+ d="M 28.833000,12.774900 L 28.542000,12.033700 L 28.032200,12.198700 L 28.178700,13.103000 L 28.833000,12.774900"
+ id="path4168" />
+ </g>
+ </g>
+ <g
+ id="g4170">
+ <g
+ id="g4174">
+ <path
+ d="M 29.123000,12.608900 L 28.977500,13.597200 L 29.777300,13.432200 L 30.358400,12.857000 L 29.849600,12.362900 C 29.678700,11.907800 29.482400,11.483000 29.268500,11.046500 L 28.833000,11.046500 L 28.833000,11.539700 L 29.123000,11.868800 L 29.123000,12.609000"
+ id="path4176" />
+ </g>
+ </g>
+ <g
+ id="g4178">
+ <g
+ id="g4182">
+ <path
+ d="M 18.365200,28.242200 L 17.783200,27.089900 L 16.692900,26.843300 L 16.111400,25.280800 L 14.657800,25.444900 L 13.422400,24.540600 L 12.113300,25.692000 L 12.113300,25.873600 C 11.717300,25.759300 11.230500,25.743700 10.877900,25.526900 L 10.586900,24.704600 L 10.586900,23.799300 L 9.7148000,23.881300 C 9.7876000,23.305100 9.8598000,22.729900 9.9331000,22.153800 L 9.4238000,22.153800 L 8.9155000,22.812000 L 8.4062000,23.058100 L 7.6791000,22.647900 L 7.6063000,21.742600 L 7.7518000,20.755300 L 8.8426000,19.933000 L 9.7147000,19.933000 L 9.8597000,19.438900 L 10.950000,19.685000 L 11.749800,20.673300 L 11.895300,19.026800 L 13.276600,17.875400 L 13.785400,16.641000 L 14.803000,16.229900 L 15.384500,15.407600 L 16.692600,15.159600 L 17.347400,14.173300 C 16.693100,14.173300 16.038800,14.173300 15.384500,14.173300 L 16.620300,13.597100 L 17.491900,13.597100 L 18.728200,13.185000 L 18.873700,12.692800 L 18.437200,12.280700 L 17.928400,12.115700 L 18.073900,11.622500 L 17.710600,10.882300 L 16.838000,11.210400 L 16.983500,10.552700 L 15.965900,9.9765000 L 15.166600,11.374400 L 15.238900,11.868500 L 14.439600,12.198600 L 13.930300,13.267900 L 13.712500,12.280600 L 12.331200,11.704400 L 12.112900,10.964200 L 13.930300,9.8939000 L 14.730100,9.1537000 L 14.802900,8.2489000 L 14.366900,8.0018000 L 13.785400,7.9193000 L 13.422100,8.8246000 C 13.422100,8.8246000 12.814200,8.9437000 12.657900,8.9823000 C 10.661800,10.821700 6.6286000,14.792400 5.6916000,22.288500 C 5.7287000,22.462300 6.3708000,23.470100 6.3708000,23.470100 L 7.8972000,24.374400 L 9.4236000,24.786500 L 10.078400,25.609700 L 11.095500,26.349900 L 11.677000,26.267900 L 12.113000,26.464200 L 12.113000,26.597000 L 11.531900,28.160000 L 11.095400,28.818200 L 11.240900,29.148300 L 10.877600,30.380700 L 12.186200,32.767400 L 13.494300,33.919700 L 14.076300,34.742000 L 14.003100,36.470500 L 14.439600,37.456800 L 14.003100,39.349400 C 14.003100,39.349400 13.968900,39.337700 14.024600,39.527100 C 14.080800,39.716600 16.353700,40.978300 16.498200,40.870900 C 16.642200,40.761500 16.765300,40.665800 16.765300,40.665800 L 16.620300,40.255600 L 17.201400,39.679400 L 17.419700,39.103200 L 18.365000,38.773100 L 19.091600,36.962600 L 18.873800,36.470400 L 19.381600,35.730200 L 20.472400,35.482200 L 21.054400,34.165800 L 20.908900,32.521300 L 21.781000,31.286900 L 21.926500,30.052500 C 20.733100,29.460700 19.549500,28.851300 18.365000,28.242000"
+ id="path4184" />
+ </g>
+ </g>
+ <g
+ id="g4186">
+ <g
+ id="g4190">
+ <path
+ d="M 16.765600,9.5649000 L 17.492200,10.058600 L 18.074200,10.058600 L 18.074200,9.4829000 L 17.347600,9.1538000 L 16.765600,9.5649000"
+ id="path4192" />
+ </g>
+ </g>
+ <g
+ id="g4194">
+ <g
+ id="g4198">
+ <path
+ d="M 14.876000,8.9072000 L 14.512200,9.8120000 L 15.239300,9.8120000 L 15.603100,8.9892000 C 15.916600,8.7675000 16.228600,8.5444000 16.547900,8.3310000 L 17.275000,8.5781000 C 17.759400,8.9072000 18.243800,9.2363000 18.728600,9.5649000 L 19.456100,8.9072000 L 18.655800,8.5781000 L 18.292000,7.8374000 L 16.911100,7.6728000 L 16.838300,7.2612000 L 16.184000,7.4262000 L 15.893600,8.0020000 L 15.529800,7.2613000 L 15.384800,7.5904000 L 15.457600,8.4132000 L 14.876000,8.9072000"
+ id="path4200" />
+ </g>
+ </g>
+ <g
+ id="g4202">
+ <g
+ style="opacity:0.75000000"
+ id="g4204">
+ <path
+ id="path4206"
+ d="" />
+ </g>
+ <g
+ id="g4208">
+ <path
+ id="path4210"
+ d="" />
+ </g>
+ </g>
+ <g
+ id="g4212">
+ <g
+ style="opacity:0.75000000"
+ id="g4214">
+ <path
+ id="path4216"
+ d="" />
+ </g>
+ <g
+ id="g4218">
+ <path
+ id="path4220"
+ d="" />
+ </g>
+ </g>
+ <g
+ id="g4222">
+ <g
+ id="g4226">
+ <path
+ d="M 17.492200,6.8496000 L 17.856000,6.5210000 L 18.583100,6.3564000 C 19.081100,6.1142000 19.581100,5.9511000 20.109500,5.7802000 L 19.819500,5.2865000 L 18.881000,5.4213000 L 18.437600,5.8632000 L 17.706600,5.9692000 L 17.056700,6.2744000 L 16.740800,6.4272000 L 16.547900,6.6855000 L 17.492200,6.8496000"
+ id="path4228" />
+ </g>
+ </g>
+ <g
+ id="g4230">
+ <g
+ id="g4234">
+ <path
+ d="M 18.728500,14.666500 L 19.165000,14.008300 L 18.510200,13.515100 L 18.728500,14.666500"
+ id="path4236" />
+ </g>
+ </g>
+ </g>
+ <g
+ id="g3216"
+ style="color:#000000;fill:url(#radialGradient1460);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:1.0179454;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ transform="matrix(0.982371,0.000000,0.000000,0.982371,-8.095179e-2,3.088300e-2)">
+ <g
+ id="g3218"
+ style="color:#000000;fill:url(#radialGradient1462);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3222"
+ style="color:#000000;fill:url(#radialGradient1466);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 44.071300,20.714400 C 44.071300,20.977100 44.071300,20.714400 44.071300,20.714400 L 43.526400,21.331600 C 43.192400,20.938000 42.817400,20.607000 42.436600,20.261300 L 41.600700,20.384300 L 40.837000,19.521000 L 40.837000,20.589400 L 41.491300,21.084500 L 41.926800,21.577700 L 42.508800,20.919500 C 42.655300,21.193900 42.799800,21.468300 42.945300,21.742700 L 42.945300,22.565000 L 42.290000,23.305200 L 41.090800,24.128400 L 40.182600,25.034700 L 39.600600,24.374500 L 39.891600,23.634300 L 39.310500,22.976100 L 38.329100,20.878400 L 37.493200,19.933100 L 37.274400,20.179200 L 37.602500,21.372600 L 38.219700,22.071800 C 38.572200,23.089400 38.920900,24.062000 39.383800,25.034700 C 40.101600,25.034700 40.778300,24.958500 41.491200,24.868700 L 41.491200,25.444900 L 40.619100,27.584100 L 39.819300,28.488400 L 39.165000,29.888800 C 39.165000,30.656400 39.165000,31.424000 39.165000,32.191500 L 39.383800,33.097800 L 39.020500,33.508000 L 38.219700,34.002100 L 37.383800,34.701300 L 38.075200,35.482600 L 37.129900,36.306800 L 37.311500,36.840000 L 35.893500,38.445500 L 34.949200,38.445500 L 34.149400,38.939600 L 33.639600,38.939600 L 33.639600,38.281400 L 33.422800,36.963000 C 33.141500,36.136800 32.848600,35.316500 32.550700,34.496200 C 32.550700,33.890700 32.586800,33.291100 32.623000,32.685700 L 32.987300,31.863400 L 32.477500,30.875100 L 32.514600,29.517700 L 31.823200,28.736400 L 32.168900,27.605500 L 31.606400,26.967300 L 30.624000,26.967300 L 30.296900,26.597200 L 29.315500,27.214900 L 28.916100,26.761300 L 28.006900,27.543000 C 27.389700,26.843300 26.771500,26.144100 26.153400,25.444900 L 25.426800,23.716400 L 26.081100,22.730100 L 25.717800,22.319000 L 26.516600,20.425400 C 27.172900,19.609000 27.858400,18.825800 28.551800,18.039700 L 29.788100,17.710600 L 31.169000,17.546500 L 32.114300,17.793600 L 33.459000,19.150000 L 33.931700,18.615800 L 34.585000,18.533800 L 35.821300,18.944900 L 36.766600,18.944900 L 37.420900,18.368700 L 37.711900,17.957600 L 37.056600,17.546500 L 35.965800,17.464500 C 35.663100,17.044600 35.381800,16.603200 35.022400,16.230100 L 34.658100,16.394200 L 34.512600,17.464500 L 33.858300,16.724300 L 33.713800,15.900100 L 32.987200,15.325900 L 32.695200,15.325900 L 33.422700,16.148200 L 33.131700,16.888400 L 32.550600,17.052500 L 32.913900,16.312300 L 32.258600,15.984200 L 31.678500,15.326000 L 30.586700,15.572100 L 30.442200,15.900200 L 29.787900,16.312300 L 29.424600,17.217600 L 28.516400,17.669700 L 28.116000,17.217600 L 27.680500,17.217600 L 27.680500,15.736200 L 28.625800,15.242100 L 29.352400,15.242100 L 29.205900,14.666900 L 28.625800,14.090700 L 29.606300,13.884600 L 30.151200,13.268400 L 30.586700,12.527200 L 31.387500,12.527200 L 31.168700,11.952000 L 31.678500,11.622900 L 31.678500,12.281100 L 32.768300,12.527200 L 33.858100,11.622900 L 33.931300,11.210800 L 34.875600,10.553100 C 34.533800,10.595600 34.192000,10.626800 33.858000,10.717700 L 33.858000,9.9766000 L 34.221300,9.1538000 L 33.858000,9.1538000 L 33.059600,9.8940000 L 32.840800,10.305600 L 33.059600,10.882300 L 32.695300,11.868600 L 32.114200,11.539500 L 31.606400,10.964300 L 30.805600,11.539500 L 30.514600,10.223600 L 31.895500,9.3188000 L 31.895500,8.8247000 L 32.768500,8.2490000 L 34.149400,7.9194000 L 35.094700,8.2490000 L 36.838800,8.5781000 L 36.403300,9.0713000 L 35.458000,9.0713000 L 36.403300,10.058600 L 37.129900,9.2363000 L 37.350600,8.8745000 C 37.350600,8.8745000 40.137700,11.372500 41.730500,14.105000 C 43.323300,16.838400 44.071300,20.060100 44.071300,20.714400 z "
+ id="path3224"
+ style="color:#000000;fill:url(#radialGradient1468);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3226"
+ style="color:#000000;fill:url(#radialGradient1470);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3230"
+ style="color:#000000;fill:url(#radialGradient1474);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 26.070300,9.2363000 L 25.997100,9.7295000 L 26.506900,10.058600 L 27.378000,9.4829000 L 26.942500,8.9892000 L 26.360500,9.3188000 L 26.070500,9.2363000"
+ id="path3232"
+ style="color:#000000;fill:url(#radialGradient1476);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3234"
+ style="color:#000000;fill:url(#radialGradient1478);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3238"
+ style="color:#000000;fill:url(#radialGradient1482);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 26.870100,5.8633000 L 24.979500,5.1226000 L 22.799800,5.3692000 L 20.109400,6.1094000 L 19.600600,6.6035000 L 21.272500,7.7549000 L 21.272500,8.4131000 L 20.618200,9.0713000 L 21.491200,10.800300 L 22.071300,10.470200 L 22.799800,9.3188000 C 23.922800,8.9716000 24.929700,8.5781000 25.997100,8.0844000 L 26.870100,5.8632000"
+ id="path3240"
+ style="color:#000000;fill:url(#radialGradient1484);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3242"
+ style="color:#000000;fill:url(#radialGradient1486);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3246"
+ style="color:#000000;fill:url(#radialGradient1490);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 28.833000,12.774900 L 28.542000,12.033700 L 28.032200,12.198700 L 28.178700,13.103000 L 28.833000,12.774900"
+ id="path3248"
+ style="color:#000000;fill:url(#radialGradient1492);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3250"
+ style="color:#000000;fill:url(#radialGradient1494);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3254"
+ style="color:#000000;fill:url(#radialGradient1498);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 29.123000,12.608900 L 28.977500,13.597200 L 29.777300,13.432200 L 30.358400,12.857000 L 29.849600,12.362900 C 29.678700,11.907800 29.482400,11.483000 29.268500,11.046500 L 28.833000,11.046500 L 28.833000,11.539700 L 29.123000,11.868800 L 29.123000,12.609000"
+ id="path3256"
+ style="color:#000000;fill:url(#radialGradient1500);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3258"
+ style="color:#000000;fill:url(#radialGradient1502);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3262"
+ style="color:#000000;fill:url(#radialGradient1506);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 18.365200,28.242200 L 17.783200,27.089900 L 16.692900,26.843300 L 16.111400,25.280800 L 14.657800,25.444900 L 13.422400,24.540600 L 12.113300,25.692000 L 12.113300,25.873600 C 11.717300,25.759300 11.230500,25.743700 10.877900,25.526900 L 10.586900,24.704600 L 10.586900,23.799300 L 9.7148000,23.881300 C 9.7876000,23.305100 9.8598000,22.729900 9.9331000,22.153800 L 9.4238000,22.153800 L 8.9155000,22.812000 L 8.4062000,23.058100 L 7.6791000,22.647900 L 7.6063000,21.742600 L 7.7518000,20.755300 L 8.8426000,19.933000 L 9.7147000,19.933000 L 9.8597000,19.438900 L 10.950000,19.685000 L 11.749800,20.673300 L 11.895300,19.026800 L 13.276600,17.875400 L 13.785400,16.641000 L 14.803000,16.229900 L 15.384500,15.407600 L 16.692600,15.159600 L 17.347400,14.173300 C 16.693100,14.173300 16.038800,14.173300 15.384500,14.173300 L 16.620300,13.597100 L 17.491900,13.597100 L 18.728200,13.185000 L 18.873700,12.692800 L 18.437200,12.280700 L 17.928400,12.115700 L 18.073900,11.622500 L 17.710600,10.882300 L 16.838000,11.210400 L 16.983500,10.552700 L 15.965900,9.9765000 L 15.166600,11.374400 L 15.238900,11.868500 L 14.439600,12.198600 L 13.930300,13.267900 L 13.712500,12.280600 L 12.331200,11.704400 L 12.112900,10.964200 L 13.930300,9.8939000 L 14.730100,9.1537000 L 14.802900,8.2489000 L 14.366900,8.0018000 L 13.785400,7.9193000 L 13.422100,8.8246000 C 13.422100,8.8246000 12.814200,8.9437000 12.657900,8.9823000 C 10.661800,10.821700 6.6286000,14.792400 5.6916000,22.288500 C 5.7287000,22.462300 6.3708000,23.470100 6.3708000,23.470100 L 7.8972000,24.374400 L 9.4236000,24.786500 L 10.078400,25.609700 L 11.095500,26.349900 L 11.677000,26.267900 L 12.113000,26.464200 L 12.113000,26.597000 L 11.531900,28.160000 L 11.095400,28.818200 L 11.240900,29.148300 L 10.877600,30.380700 L 12.186200,32.767400 L 13.494300,33.919700 L 14.076300,34.742000 L 14.003100,36.470500 L 14.439600,37.456800 L 14.003100,39.349400 C 14.003100,39.349400 13.968900,39.337700 14.024600,39.527100 C 14.080800,39.716600 16.353700,40.978300 16.498200,40.870900 C 16.642200,40.761500 16.765300,40.665800 16.765300,40.665800 L 16.620300,40.255600 L 17.201400,39.679400 L 17.419700,39.103200 L 18.365000,38.773100 L 19.091600,36.962600 L 18.873800,36.470400 L 19.381600,35.730200 L 20.472400,35.482200 L 21.054400,34.165800 L 20.908900,32.521300 L 21.781000,31.286900 L 21.926500,30.052500 C 20.733100,29.460700 19.549500,28.851300 18.365000,28.242000"
+ id="path3264"
+ style="color:#000000;fill:url(#radialGradient1508);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3266"
+ style="color:#000000;fill:url(#radialGradient1510);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3270"
+ style="color:#000000;fill:url(#radialGradient1514);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 16.765600,9.5649000 L 17.492200,10.058600 L 18.074200,10.058600 L 18.074200,9.4829000 L 17.347600,9.1538000 L 16.765600,9.5649000"
+ id="path3272"
+ style="color:#000000;fill:url(#radialGradient1516);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3274"
+ style="color:#000000;fill:url(#radialGradient1518);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3278"
+ style="color:#000000;fill:url(#radialGradient1522);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 14.876000,8.9072000 L 14.512200,9.8120000 L 15.239300,9.8120000 L 15.603100,8.9892000 C 15.916600,8.7675000 16.228600,8.5444000 16.547900,8.3310000 L 17.275000,8.5781000 C 17.759400,8.9072000 18.243800,9.2363000 18.728600,9.5649000 L 19.456100,8.9072000 L 18.655800,8.5781000 L 18.292000,7.8374000 L 16.911100,7.6728000 L 16.838300,7.2612000 L 16.184000,7.4262000 L 15.893600,8.0020000 L 15.529800,7.2613000 L 15.384800,7.5904000 L 15.457600,8.4132000 L 14.876000,8.9072000"
+ id="path3280"
+ style="color:#000000;fill:url(#radialGradient1524);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3282"
+ style="color:#000000;fill:url(#radialGradient1526);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ style="opacity:0.75000000;color:#000000;fill:url(#radialGradient1528);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="g3284">
+ <path
+ d=""
+ style="color:#000000;fill:url(#radialGradient1530);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="path3286" />
+ </g>
+ <g
+ id="g3288"
+ style="color:#000000;fill:url(#radialGradient1532);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d=""
+ id="path3290"
+ style="color:#000000;fill:url(#radialGradient1534);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3292"
+ style="color:#000000;fill:url(#radialGradient1536);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ style="opacity:0.75000000;color:#000000;fill:url(#radialGradient1538);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="g3294">
+ <path
+ d=""
+ style="color:#000000;fill:url(#radialGradient1540);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible"
+ id="path3296" />
+ </g>
+ <g
+ id="g3298"
+ style="color:#000000;fill:url(#radialGradient1542);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d=""
+ id="path3300"
+ style="color:#000000;fill:url(#radialGradient1544);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3302"
+ style="color:#000000;fill:url(#radialGradient1546);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3306"
+ style="color:#000000;fill:url(#radialGradient1550);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 17.492200,6.8496000 L 17.856000,6.5210000 L 18.583100,6.3564000 C 19.081100,6.1142000 19.581100,5.9511000 20.109500,5.7802000 L 19.819500,5.2865000 L 18.881000,5.4213000 L 18.437600,5.8632000 L 17.706600,5.9692000 L 17.056700,6.2744000 L 16.740800,6.4272000 L 16.547900,6.6855000 L 17.492200,6.8496000"
+ id="path3308"
+ style="color:#000000;fill:url(#radialGradient1552);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ <g
+ id="g3310"
+ style="color:#000000;fill:url(#radialGradient1554);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <g
+ id="g3314"
+ style="color:#000000;fill:url(#radialGradient1558);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible">
+ <path
+ d="M 18.728500,14.666500 L 19.165000,14.008300 L 18.510200,13.515100 L 18.728500,14.666500"
+ id="path3316"
+ style="color:#000000;fill:url(#radialGradient4756);stroke-dashoffset:0.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible" />
+ </g>
+ </g>
+ </g>
+ <path
+ style="fill:none;fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#radialGradient4132);stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 42.975093,23.485534 C 42.975093,33.651354 34.733915,41.892440 24.569493,41.892440 C 14.404139,41.892440 6.1634261,33.651261 6.1634261,23.485534 C 6.1634261,13.320180 14.404139,5.0799340 24.569493,5.0799340 C 34.733915,5.0799340 42.975093,13.320180 42.975093,23.485534 L 42.975093,23.485534 z "
+ id="path4122" />
+ </g>
+</svg>
diff --git a/mediagoblin/media_types/video/migrations.py b/mediagoblin/media_types/video/migrations.py new file mode 100644 index 00000000..442bbd8d --- /dev/null +++ b/mediagoblin/media_types/video/migrations.py @@ -0,0 +1,32 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.db.migration_tools import RegisterMigration, inspect_table + +from sqlalchemy import MetaData, Column, Unicode + +MIGRATIONS = {} + +@RegisterMigration(1, MIGRATIONS) +def add_orig_metadata_column(db_conn): + metadata = MetaData(bind=db_conn.bind) + + vid_data = inspect_table(metadata, "video__mediadata") + + col = Column('orig_metadata', Unicode, + default=None, nullable=True) + col.create(vid_data) + db_conn.commit() diff --git a/mediagoblin/media_types/video/models.py b/mediagoblin/media_types/video/models.py new file mode 100644 index 00000000..0b52c53f --- /dev/null +++ b/mediagoblin/media_types/video/models.py @@ -0,0 +1,97 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from mediagoblin.db.base import Base + +from sqlalchemy import ( + Column, Integer, SmallInteger, ForeignKey) +from sqlalchemy.orm import relationship, backref +from mediagoblin.db.extratypes import JSONEncoded +from mediagoblin.media_types import video + + +BACKREF_NAME = "video__media_data" + + +class VideoData(Base): + """ + Attributes: + - media_data: the originating media entry (of course) + - width: width of the transcoded video + - height: height of the transcoded video + - orig_metadata: A loose json structure containing metadata gstreamer + pulled from the original video. + This field is NOT GUARANTEED to exist! + + Likely metadata extracted: + "videoheight", "videolength", "videowidth", + "audiorate", "audiolength", "audiochannels", "audiowidth", + "mimetype", "tags" + + TODO: document the above better. + """ + __tablename__ = "video__mediadata" + + # The primary key *and* reference to the main media_entry + media_entry = Column(Integer, ForeignKey('core__media_entries.id'), + primary_key=True) + get_media_entry = relationship("MediaEntry", + backref=backref(BACKREF_NAME, uselist=False, + cascade="all, delete-orphan")) + + width = Column(SmallInteger) + height = Column(SmallInteger) + + orig_metadata = Column(JSONEncoded) + + def source_type(self): + """ + Construct a useful type=... that is to say, used like: + <video><source type="{{ entry.media_data.source_type() }}" /></video> + + Try to construct it out of self.orig_metadata... if we fail we + just dope'ily fall back on DEFAULT_WEBM_TYPE + """ + orig_metadata = self.orig_metadata or {} + + if "webm_640" not in self.get_media_entry.media_files \ + and "mimetype" in orig_metadata \ + and "tags" in orig_metadata \ + and "audio-codec" in orig_metadata["tags"] \ + and "video-codec" in orig_metadata["tags"]: + if orig_metadata['mimetype'] == 'application/ogg': + # stupid ambiguous .ogg extension + mimetype = "video/ogg" + else: + mimetype = orig_metadata['mimetype'] + + video_codec = orig_metadata["tags"]["video-codec"].lower() + audio_codec = orig_metadata["tags"]["audio-codec"].lower() + + # We don't want the "video" at the end of vp8... + # not sure of a nicer way to be cleaning this stuff + if video_codec == "vp8 video": + video_codec = "vp8" + + return '%s; codecs="%s, %s"' % ( + mimetype, video_codec, audio_codec) + else: + return video.VideoMediaManager.default_webm_type + + +DATA_MODEL = VideoData +MODELS = [VideoData] diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py new file mode 100644 index 00000000..ff2c94a0 --- /dev/null +++ b/mediagoblin/media_types/video/processing.py @@ -0,0 +1,212 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from tempfile import NamedTemporaryFile +import logging +import datetime + +from mediagoblin import mg_globals as mgg +from mediagoblin.processing import \ + create_pub_filepath, FilenameBuilder, BaseProcessingFail, ProgressCallback +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +from . import transcoders +from .util import skip_transcode + +_log = logging.getLogger(__name__) +_log.setLevel(logging.DEBUG) + + +class VideoTranscodingFail(BaseProcessingFail): + ''' + Error raised if video transcoding fails + ''' + general_message = _(u'Video transcoding failed') + + +def sniff_handler(media_file, **kw): + transcoder = transcoders.VideoTranscoder() + data = transcoder.discover(media_file.name) + + _log.debug('Discovered: {0}'.format(data)) + + if not data: + _log.error('Could not discover {0}'.format( + kw.get('media'))) + return False + + if data['is_video'] == True: + return True + + return False + + +def process_video(proc_state): + """ + Process a video entry, transcode the queued media files (originals) and + create a thumbnail for the entry. + + A Workbench() represents a local tempory dir. It is automatically + cleaned up when this function exits. + """ + entry = proc_state.entry + workbench = proc_state.workbench + video_config = mgg.global_config['media_type:mediagoblin.media_types.video'] + + queued_filepath = entry.queued_media_file + queued_filename = proc_state.get_queued_filename() + name_builder = FilenameBuilder(queued_filename) + + medium_filepath = create_pub_filepath( + entry, name_builder.fill('{basename}-640p.webm')) + + thumbnail_filepath = create_pub_filepath( + entry, name_builder.fill('{basename}.thumbnail.jpg')) + + # Create a temporary file for the video destination (cleaned up with workbench) + tmp_dst = NamedTemporaryFile(dir=workbench.dir, delete=False) + with tmp_dst: + # Transcode queued file to a VP8/vorbis file that fits in a 640x640 square + progress_callback = ProgressCallback(entry) + + dimensions = ( + mgg.global_config['media:medium']['max_width'], + mgg.global_config['media:medium']['max_height']) + + # Extract metadata and keep a record of it + metadata = transcoders.VideoTranscoder().discover(queued_filename) + store_metadata(entry, metadata) + + # Figure out whether or not we need to transcode this video or + # if we can skip it + if skip_transcode(metadata): + _log.debug('Skipping transcoding') + + dst_dimensions = metadata['videowidth'], metadata['videoheight'] + + # Push original file to public storage + _log.debug('Saving original...') + proc_state.copy_original(queued_filepath[-1]) + + did_transcode = False + else: + transcoder = transcoders.VideoTranscoder() + + transcoder.transcode(queued_filename, tmp_dst.name, + vp8_quality=video_config['vp8_quality'], + vp8_threads=video_config['vp8_threads'], + vorbis_quality=video_config['vorbis_quality'], + progress_callback=progress_callback, + dimensions=dimensions) + + dst_dimensions = transcoder.dst_data.videowidth,\ + transcoder.dst_data.videoheight + + # Push transcoded video to public storage + _log.debug('Saving medium...') + mgg.public_store.copy_local_to_storage(tmp_dst.name, medium_filepath) + _log.debug('Saved medium') + + entry.media_files['webm_640'] = medium_filepath + + did_transcode = True + + # Save the width and height of the transcoded video + entry.media_data_init( + width=dst_dimensions[0], + height=dst_dimensions[1]) + + # Temporary file for the video thumbnail (cleaned up with workbench) + tmp_thumb = NamedTemporaryFile(dir=workbench.dir, suffix='.jpg', delete=False) + + with tmp_thumb: + # Create a thumbnail.jpg that fits in a 180x180 square + transcoders.VideoThumbnailerMarkII( + queued_filename, + tmp_thumb.name, + 180) + + # Push the thumbnail to public storage + _log.debug('Saving thumbnail...') + mgg.public_store.copy_local_to_storage(tmp_thumb.name, thumbnail_filepath) + entry.media_files['thumb'] = thumbnail_filepath + + # save the original... but only if we did a transcoding + # (if we skipped transcoding and just kept the original anyway as the main + # media, then why would we save the original twice?) + if video_config['keep_original'] and did_transcode: + # Push original file to public storage + _log.debug('Saving original...') + proc_state.copy_original(queued_filepath[-1]) + + # Remove queued media file from storage and database + proc_state.delete_queue_file() + + +def store_metadata(media_entry, metadata): + """ + Store metadata from this video for this media entry. + """ + # Let's pull out the easy, not having to be converted ones first + stored_metadata = dict( + [(key, metadata[key]) + for key in [ + "videoheight", "videolength", "videowidth", + "audiorate", "audiolength", "audiochannels", "audiowidth", + "mimetype"] + if key in metadata]) + + # We have to convert videorate into a sequence because it's a + # special type normally.. + + if "videorate" in metadata: + videorate = metadata["videorate"] + stored_metadata["videorate"] = [videorate.num, videorate.denom] + + # Also make a whitelist conversion of the tags. + if "tags" in metadata: + tags_metadata = metadata['tags'] + + # we don't use *all* of these, but we know these ones are + # safe... + tags = dict( + [(key, tags_metadata[key]) + for key in [ + "application-name", "artist", "audio-codec", "bitrate", + "container-format", "copyright", "encoder", + "encoder-version", "license", "nominal-bitrate", "title", + "video-codec"] + if key in tags_metadata]) + if 'date' in tags_metadata: + date = tags_metadata['date'] + tags['date'] = "%s-%s-%s" % ( + date.year, date.month, date.day) + + # TODO: handle timezone info; gst.get_time_zone_offset + + # python's tzinfo should help + if 'datetime' in tags_metadata: + dt = tags_metadata['datetime'] + tags['datetime'] = datetime.datetime( + dt.get_year(), dt.get_month(), dt.get_day(), dt.get_hour(), + dt.get_minute(), dt.get_second(), + dt.get_microsecond()).isoformat() + + metadata['tags'] = tags + + # Only save this field if there's something to save + if len(stored_metadata): + media_entry.media_data_init( + orig_metadata=stored_metadata) diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py new file mode 100644 index 00000000..90a767dd --- /dev/null +++ b/mediagoblin/media_types/video/transcoders.py @@ -0,0 +1,776 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from __future__ import division + +import os +import sys +import logging +import urllib +import multiprocessing +import gobject +import pygst +pygst.require('0.10') +import gst +import struct +try: + from PIL import Image +except ImportError: + import Image + +from gst.extend import discoverer + +_log = logging.getLogger(__name__) + +gobject.threads_init() + +CPU_COUNT = 2 + +try: + CPU_COUNT = multiprocessing.cpu_count() +except NotImplementedError: + _log.warning('multiprocessing.cpu_count not implemented') + +os.putenv('GST_DEBUG_DUMP_DOT_DIR', '/tmp') + + +def pixbuf_to_pilbuf(buf): + data = list() + for i in range(0, len(buf), 3): + r, g, b = struct.unpack('BBB', buf[i:i + 3]) + data.append((r, g, b)) + + return data + + +class VideoThumbnailerMarkII(object): + ''' + Creates a thumbnail from a video file. Rewrite of VideoThumbnailer. + + Large parts of the functionality and overall architectue contained within + this object is taken from Participatory Culture Foundation's + `gst_extractor.Extractor` object last seen at + https://github.com/pculture/miro/blob/master/tv/lib/frontends/widgets/gst/gst_extractor.py + in the `miro` codebase. + + The `miro` codebase and the gst_extractor.py are licensed under the GNU + General Public License v2 or later. + ''' + STATE_NULL = 0 + STATE_HALTING = 1 + STATE_PROCESSING = 2 + STATE_PROCESSING_THUMBNAIL = 3 + + def __init__(self, source_path, dest_path, width=None, height=None, + position_callback=None): + self.state = self.STATE_NULL + + self.has_reached_playbin_pause = False + + self.thumbnail_pipeline = None + + self.permission_to_take_picture = False + + self.buffer_probes = {} + + self.errors = [] + + self.source_path = os.path.abspath(source_path) + self.dest_path = os.path.abspath(dest_path) + + self.width = width + self.height = height + self.position_callback = position_callback \ + or self.wadsworth_position_callback + + self.mainloop = gobject.MainLoop() + + self.playbin = gst.element_factory_make('playbin') + + self.videosink = gst.element_factory_make('fakesink', 'videosink') + self.audiosink = gst.element_factory_make('fakesink', 'audiosink') + + self.playbin.set_property('video-sink', self.videosink) + self.playbin.set_property('audio-sink', self.audiosink) + + self.playbin_message_bus = self.playbin.get_bus() + + self.playbin_message_bus.add_signal_watch() + self.playbin_bus_watch_id = self.playbin_message_bus.connect( + 'message', + self.on_playbin_message) + + self.playbin.set_property( + 'uri', + 'file:{0}'.format( + urllib.pathname2url(self.source_path))) + + self.playbin.set_state(gst.STATE_PAUSED) + + try: + self.run() + except Exception as exc: + _log.critical( + 'Exception "{0}" caught, shutting down mainloop and re-raising'\ + .format(exc)) + self.disconnect() + raise + + def wadsworth_position_callback(self, duration, gst): + return self.duration / 100 * 30 + + def run(self): + self.mainloop.run() + + def on_playbin_message(self, message_bus, message): + # Silenced to prevent clobbering of output + #_log.debug('playbin message: {0}'.format(message)) + + if message.type == gst.MESSAGE_ERROR: + _log.error('playbin error: {0}'.format(message)) + gobject.idle_add(self.on_playbin_error) + + if message.type == gst.MESSAGE_STATE_CHANGED: + prev_state, cur_state, pending_state = \ + message.parse_state_changed() + + _log.debug('playbin state changed: \nprev: {0}\ncur: {1}\n \ +pending: {2}'.format( + prev_state, + cur_state, + pending_state)) + + if cur_state == gst.STATE_PAUSED: + if message.src == self.playbin: + _log.info('playbin ready') + gobject.idle_add(self.on_playbin_paused) + + def on_playbin_paused(self): + if self.has_reached_playbin_pause: + _log.warn('Has already reached on_playbin_paused. Aborting \ +without doing anything this time.') + return False + + self.has_reached_playbin_pause = True + + # XXX: Why is this even needed at this point? + current_video = self.playbin.get_property('current-video') + + if not current_video: + _log.critical('Could not get any video data \ +from playbin') + else: + _log.info('Got video data from playbin') + + self.duration = self.get_duration(self.playbin) + self.permission_to_take_picture = True + self.buffer_probes = {} + + pipeline = ''.join([ + 'filesrc location="%s" ! decodebin ! ' % self.source_path, + 'ffmpegcolorspace ! videoscale ! ', + 'video/x-raw-rgb,depth=24,bpp=24,pixel-aspect-ratio=1/1', + ',width={0}'.format(self.width) if self.width else '', + ',height={0}'.format(self.height) if self.height else '', + ' ! ', + 'fakesink signal-handoffs=True']) + + _log.debug('thumbnail_pipeline: {0}'.format(pipeline)) + + self.thumbnail_pipeline = gst.parse_launch(pipeline) + self.thumbnail_message_bus = self.thumbnail_pipeline.get_bus() + self.thumbnail_message_bus.add_signal_watch() + self.thumbnail_bus_watch_id = self.thumbnail_message_bus.connect( + 'message', + self.on_thumbnail_message) + + self.thumbnail_pipeline.set_state(gst.STATE_PAUSED) + + gobject.timeout_add(3000, self.on_gobject_timeout) + + return False + + def on_thumbnail_message(self, message_bus, message): + # This is silenced to prevent clobbering of the terminal window + #_log.debug('thumbnail message: {0}'.format(message)) + + if message.type == gst.MESSAGE_ERROR: + _log.error('thumbnail error: {0}'.format(message.parse_error())) + gobject.idle_add(self.on_thumbnail_error, message) + + if message.type == gst.MESSAGE_STATE_CHANGED: + prev_state, cur_state, pending_state = \ + message.parse_state_changed() + + _log.debug('thumbnail state changed: \nprev: {0}\ncur: {1}\n \ +pending: {2}'.format( + prev_state, + cur_state, + pending_state)) + + if cur_state == gst.STATE_PAUSED and \ + not self.state == self.STATE_PROCESSING_THUMBNAIL: + # Find the fakesink sink pad and attach the on_buffer_probe + # handler to it. + seek_amount = self.position_callback(self.duration, gst) + + seek_result = self.thumbnail_pipeline.seek( + 1.0, + gst.FORMAT_TIME, + gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, + gst.SEEK_TYPE_SET, + seek_amount, + gst.SEEK_TYPE_NONE, + 0) + + if not seek_result: + _log.info('Could not seek.') + else: + _log.info('Seek successful, attaching buffer probe') + self.state = self.STATE_PROCESSING_THUMBNAIL + for sink in self.thumbnail_pipeline.sinks(): + sink_name = sink.get_name() + sink_factory_name = sink.get_factory().get_name() + + if sink_factory_name == 'fakesink': + sink_pad = sink.get_pad('sink') + + self.buffer_probes[sink_name] = sink_pad\ + .add_buffer_probe( + self.on_pad_buffer_probe, + sink_name) + + _log.info('Attached buffer probes: {0}'.format( + self.buffer_probes)) + + break + + + elif self.state == self.STATE_PROCESSING_THUMBNAIL: + _log.info('Already processing thumbnail') + + def on_pad_buffer_probe(self, *args): + _log.debug('buffer probe handler: {0}'.format(args)) + gobject.idle_add(lambda: self.take_snapshot(*args)) + + def take_snapshot(self, pad, buff, name): + if self.state == self.STATE_HALTING: + _log.debug('Pipeline is halting, will not take snapshot') + return False + + _log.info('Taking snapshot! ({0})'.format( + (pad, buff, name))) + try: + caps = buff.caps + if caps is None: + _log.error('No buffer caps present /take_snapshot') + self.disconnect() + + _log.debug('caps: {0}'.format(caps)) + + filters = caps[0] + width = filters['width'] + height = filters['height'] + + im = Image.new('RGB', (width, height)) + + data = pixbuf_to_pilbuf(buff.data) + + im.putdata(data) + + im.save(self.dest_path) + + _log.info('Saved snapshot!') + + self.disconnect() + + except gst.QueryError as exc: + _log.error('take_snapshot - QueryError: {0}'.format(exc)) + + return False + + def on_thumbnail_error(self, message): + scaling_failed = False + + if 'Error calculating the output scaled size - integer overflow' \ + in message.parse_error()[1]: + # GStreamer videoscale sometimes fails to calculate the dimensions + # given only one of the destination dimensions and the source + # dimensions. This is a workaround in case videoscale returns an + # error that indicates this has happened. + scaling_failed = True + _log.error('Thumbnailing failed because of videoscale integer' + ' overflow. Will retry with fallback.') + else: + _log.error('Thumbnailing failed: {0}'.format(message.parse_error())) + + # Kill the current mainloop + self.disconnect() + + if scaling_failed: + # Manually scale the destination dimensions + _log.info('Retrying with manually set sizes...') + + info = VideoTranscoder().discover(self.source_path) + + h = info['videoheight'] + w = info['videowidth'] + ratio = 180 / int(w) + h = int(h * ratio) + + self.__init__(self.source_path, self.dest_path, 180, h) + + def disconnect(self): + self.state = self.STATE_HALTING + + if self.playbin is not None: + self.playbin.set_state(gst.STATE_NULL) + + for sink in self.playbin.sinks(): + sink_name = sink.get_name() + sink_factory_name = sink.get_factory().get_name() + + if sink_factory_name == 'fakesink': + sink_pad = sink.get_pad('sink') + sink_pad.remove_buffer_probe(self.buffer_probes[sink_name]) + del self.buffer_probes[sink_name] + + self.playbin = None + + if self.thumbnail_pipeline is not None: + self.thumbnail_pipeline.set_state(gst.STATE_NULL) + self.thumbnail_pipeline = None + + if self.playbin_message_bus is not None: + self.playbin_message_bus.disconnect(self.playbin_bus_watch_id) + self.playbin_message_bus = None + + self.halt() + + def halt(self): + gobject.idle_add(self.mainloop.quit) + + def on_gobject_timeout(self): + _log.critical('Reached gobject timeout') + self.disconnect() + + def get_duration(self, pipeline, attempt=1): + if attempt == 5: + _log.critical('Pipeline duration query retry limit reached.') + return 0 + + try: + return pipeline.query_duration(gst.FORMAT_TIME)[0] + except gst.QueryError as exc: + _log.error('Could not get duration on attempt {0}: {1}'.format( + attempt, + exc)) + return self.get_duration(pipeline, attempt + 1) + + +class VideoTranscoder(object): + ''' + Video transcoder + + Transcodes the SRC video file to a VP8 WebM video file at DST + + - Does the same thing as VideoThumbnailer, but produces a WebM vp8 + and vorbis video file. + - The VideoTranscoder exceeds the VideoThumbnailer in the way + that it was refined afterwards and therefore is done more + correctly. + ''' + def __init__(self): + _log.info('Initializing VideoTranscoder...') + self.progress_percentage = None + self.loop = gobject.MainLoop() + + def transcode(self, src, dst, **kwargs): + ''' + Transcode a video file into a 'medium'-sized version. + ''' + self.source_path = src + self.destination_path = dst + + # vp8enc options + self.destination_dimensions = kwargs.get('dimensions', (640, 640)) + self.vp8_quality = kwargs.get('vp8_quality', 8) + # Number of threads used by vp8enc: + # number of real cores - 1 as per recommendation on + # <http://www.webmproject.org/tools/encoder-parameters/#6-multi-threaded-encode-and-decode> + self.vp8_threads = kwargs.get('vp8_threads', CPU_COUNT - 1) + + # 0 means auto-detect, but dict.get() only falls back to CPU_COUNT + # if value is None, this will correct our incompatibility with + # dict.get() + # This will also correct cases where there's only 1 CPU core, see + # original self.vp8_threads assignment above. + if self.vp8_threads == 0: + self.vp8_threads = CPU_COUNT + + # vorbisenc options + self.vorbis_quality = kwargs.get('vorbis_quality', 0.3) + + self._progress_callback = kwargs.get('progress_callback') or None + + if not type(self.destination_dimensions) == tuple: + raise Exception('dimensions must be tuple: (width, height)') + + self._setup() + self._run() + + # XXX: This could be a static method. + def discover(self, src): + ''' + Discover properties about a media file + ''' + _log.info('Discovering {0}'.format(src)) + + self.source_path = src + self._setup_discover(discovered_callback=self.__on_discovered) + + self.discoverer.discover() + + self.loop.run() + + if hasattr(self, '_discovered_data'): + return self._discovered_data.__dict__ + else: + return None + + def __on_discovered(self, data, is_media): + _log.debug('Discovered: {0}'.format(data)) + if not is_media: + self.__stop() + raise Exception('Could not discover {0}'.format(self.source_path)) + + self._discovered_data = data + + self.__stop_mainloop() + + def _setup(self): + self._setup_discover() + self._setup_pipeline() + + def _run(self): + _log.info('Discovering...') + self.discoverer.discover() + _log.info('Done') + + _log.debug('Initializing MainLoop()') + self.loop.run() + + def _setup_discover(self, **kw): + _log.debug('Setting up discoverer') + self.discoverer = discoverer.Discoverer(self.source_path) + + # Connect self.__discovered to the 'discovered' event + self.discoverer.connect( + 'discovered', + kw.get('discovered_callback', self.__discovered)) + + def __discovered(self, data, is_media): + ''' + Callback for media discoverer. + ''' + if not is_media: + self.__stop() + raise Exception('Could not discover {0}'.format(self.source_path)) + + _log.debug('__discovered, data: {0}'.format(data.__dict__)) + + self.data = data + + # Launch things that should be done after discovery + self._link_elements() + self.__setup_videoscale_capsfilter() + + # Tell the transcoding pipeline to start running + self.pipeline.set_state(gst.STATE_PLAYING) + _log.info('Transcoding...') + + def _setup_pipeline(self): + _log.debug('Setting up transcoding pipeline') + # Create the pipeline bin. + self.pipeline = gst.Pipeline('VideoTranscoderPipeline') + + # Create all GStreamer elements, starting with + # filesrc & decoder + self.filesrc = gst.element_factory_make('filesrc', 'filesrc') + self.filesrc.set_property('location', self.source_path) + self.pipeline.add(self.filesrc) + + self.decoder = gst.element_factory_make('decodebin2', 'decoder') + self.decoder.connect('new-decoded-pad', self._on_dynamic_pad) + self.pipeline.add(self.decoder) + + # Video elements + self.videoqueue = gst.element_factory_make('queue', 'videoqueue') + self.pipeline.add(self.videoqueue) + + self.videorate = gst.element_factory_make('videorate', 'videorate') + self.pipeline.add(self.videorate) + + self.ffmpegcolorspace = gst.element_factory_make( + 'ffmpegcolorspace', 'ffmpegcolorspace') + self.pipeline.add(self.ffmpegcolorspace) + + self.videoscale = gst.element_factory_make('ffvideoscale', 'videoscale') + #self.videoscale.set_property('method', 2) # I'm not sure this works + #self.videoscale.set_property('add-borders', 0) + self.pipeline.add(self.videoscale) + + self.capsfilter = gst.element_factory_make('capsfilter', 'capsfilter') + self.pipeline.add(self.capsfilter) + + self.vp8enc = gst.element_factory_make('vp8enc', 'vp8enc') + self.vp8enc.set_property('quality', self.vp8_quality) + self.vp8enc.set_property('threads', self.vp8_threads) + self.vp8enc.set_property('max-latency', 25) + self.pipeline.add(self.vp8enc) + + # Audio elements + self.audioqueue = gst.element_factory_make('queue', 'audioqueue') + self.pipeline.add(self.audioqueue) + + self.audiorate = gst.element_factory_make('audiorate', 'audiorate') + self.audiorate.set_property('tolerance', 80000000) + self.pipeline.add(self.audiorate) + + self.audioconvert = gst.element_factory_make('audioconvert', 'audioconvert') + self.pipeline.add(self.audioconvert) + + self.audiocapsfilter = gst.element_factory_make('capsfilter', + 'audiocapsfilter') + audiocaps = ['audio/x-raw-float'] + self.audiocapsfilter.set_property( + 'caps', + gst.caps_from_string( + ','.join(audiocaps))) + self.pipeline.add(self.audiocapsfilter) + + self.vorbisenc = gst.element_factory_make('vorbisenc', 'vorbisenc') + self.vorbisenc.set_property('quality', self.vorbis_quality) + self.pipeline.add(self.vorbisenc) + + # WebMmux & filesink + self.webmmux = gst.element_factory_make('webmmux', 'webmmux') + self.pipeline.add(self.webmmux) + + self.filesink = gst.element_factory_make('filesink', 'filesink') + self.filesink.set_property('location', self.destination_path) + self.pipeline.add(self.filesink) + + # Progressreport + self.progressreport = gst.element_factory_make( + 'progressreport', 'progressreport') + # Update every second + self.progressreport.set_property('update-freq', 1) + self.progressreport.set_property('silent', True) + self.pipeline.add(self.progressreport) + + def _link_elements(self): + ''' + Link all the elements + + This code depends on data from the discoverer and is called + from __discovered + ''' + _log.debug('linking elements') + # Link the filesrc element to the decoder. The decoder then emits + # 'new-decoded-pad' which links decoded src pads to either a video + # or audio sink + self.filesrc.link(self.decoder) + + # Link all the video elements in a row to webmmux + gst.element_link_many( + self.videoqueue, + self.videorate, + self.ffmpegcolorspace, + self.videoscale, + self.capsfilter, + self.vp8enc, + self.webmmux) + + if self.data.is_audio: + # Link all the audio elements in a row to webmux + gst.element_link_many( + self.audioqueue, + self.audiorate, + self.audioconvert, + self.audiocapsfilter, + self.vorbisenc, + self.webmmux) + + gst.element_link_many( + self.webmmux, + self.progressreport, + self.filesink) + + # Setup the message bus and connect _on_message to the pipeline + self._setup_bus() + + def _on_dynamic_pad(self, dbin, pad, islast): + ''' + Callback called when ``decodebin2`` has a pad that we can connect to + ''' + # Intersect the capabilities of the video sink and the pad src + # Then check if they have no common capabilities. + if self.ffmpegcolorspace.get_pad_template('sink')\ + .get_caps().intersect(pad.get_caps()).is_empty(): + # It is NOT a video src pad. + pad.link(self.audioqueue.get_pad('sink')) + else: + # It IS a video src pad. + pad.link(self.videoqueue.get_pad('sink')) + + def _setup_bus(self): + self.bus = self.pipeline.get_bus() + self.bus.add_signal_watch() + self.bus.connect('message', self._on_message) + + def __setup_videoscale_capsfilter(self): + ''' + Sets up the output format (width, height) for the video + ''' + caps = ['video/x-raw-yuv', 'pixel-aspect-ratio=1/1', 'framerate=30/1'] + + if self.data.videoheight > self.data.videowidth: + # Whoa! We have ourselves a portrait video! + caps.append('height={0}'.format( + self.destination_dimensions[1])) + else: + # It's a landscape, phew, how normal. + caps.append('width={0}'.format( + self.destination_dimensions[0])) + + self.capsfilter.set_property( + 'caps', + gst.caps_from_string( + ','.join(caps))) + + def _on_message(self, bus, message): + _log.debug((bus, message, message.type)) + + t = message.type + + if message.type == gst.MESSAGE_EOS: + self._discover_dst_and_stop() + _log.info('Done') + + elif message.type == gst.MESSAGE_ELEMENT: + if message.structure.get_name() == 'progress': + data = dict(message.structure) + # Update progress state if it has changed + if self.progress_percentage != data.get('percent'): + self.progress_percentage = data.get('percent') + if self._progress_callback: + self._progress_callback(data.get('percent')) + + _log.info('{percent}% done...'.format( + percent=data.get('percent'))) + _log.debug(data) + + elif t == gst.MESSAGE_ERROR: + _log.error((bus, message)) + self.__stop() + + def _discover_dst_and_stop(self): + self.dst_discoverer = discoverer.Discoverer(self.destination_path) + + self.dst_discoverer.connect('discovered', self.__dst_discovered) + + self.dst_discoverer.discover() + + def __dst_discovered(self, data, is_media): + self.dst_data = data + + self.__stop() + + def __stop(self): + _log.debug(self.loop) + + if hasattr(self, 'pipeline'): + # Stop executing the pipeline + self.pipeline.set_state(gst.STATE_NULL) + + # This kills the loop, mercifully + gobject.idle_add(self.__stop_mainloop) + + def __stop_mainloop(self): + ''' + Wrapper for gobject.MainLoop.quit() + + This wrapper makes us able to see if self.loop.quit has been called + ''' + _log.info('Terminating MainLoop') + + self.loop.quit() + + +if __name__ == '__main__': + os.nice(19) + logging.basicConfig() + from optparse import OptionParser + + parser = OptionParser( + usage='%prog [-v] -a [ video | thumbnail | discover ] SRC [ DEST ]') + + parser.add_option('-a', '--action', + dest='action', + help='One of "video", "discover" or "thumbnail"') + + parser.add_option('-v', + dest='verbose', + action='store_true', + help='Output debug information') + + parser.add_option('-q', + dest='quiet', + action='store_true', + help='Dear program, please be quiet unless *error*') + + parser.add_option('-w', '--width', + type=int, + default=180) + + (options, args) = parser.parse_args() + + if options.verbose: + _log.setLevel(logging.DEBUG) + else: + _log.setLevel(logging.INFO) + + if options.quiet: + _log.setLevel(logging.ERROR) + + _log.debug(args) + + if not len(args) == 2 and not options.action == 'discover': + parser.print_help() + sys.exit() + + transcoder = VideoTranscoder() + + if options.action == 'thumbnail': + args.append(options.width) + VideoThumbnailerMarkII(*args) + elif options.action == 'video': + def cb(data): + print('I\'m a callback!') + transcoder.transcode(*args, progress_callback=cb) + elif options.action == 'discover': + print transcoder.discover(*args) diff --git a/mediagoblin/media_types/video/util.py b/mediagoblin/media_types/video/util.py new file mode 100644 index 00000000..5765ecfb --- /dev/null +++ b/mediagoblin/media_types/video/util.py @@ -0,0 +1,59 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from mediagoblin import mg_globals as mgg + +_log = logging.getLogger(__name__) + + +def skip_transcode(metadata): + ''' + Checks video metadata against configuration values for skip_transcode. + + Returns True if the video matches the requirements in the configuration. + ''' + config = mgg.global_config['media_type:mediagoblin.media_types.video']\ + ['skip_transcode'] + + medium_config = mgg.global_config['media:medium'] + + _log.debug('skip_transcode config: {0}'.format(config)) + + if config['mime_types'] and metadata.get('mimetype'): + if not metadata['mimetype'] in config['mime_types']: + return False + + if config['container_formats'] and metadata['tags'].get('audio-codec'): + if not metadata['tags']['container-format'] in config['container_formats']: + return False + + if config['video_codecs'] and metadata['tags'].get('audio-codec'): + if not metadata['tags']['video-codec'] in config['video_codecs']: + return False + + if config['audio_codecs'] and metadata['tags'].get('audio-codec'): + if not metadata['tags']['audio-codec'] in config['audio_codecs']: + return False + + if config['dimensions_match']: + if not metadata['videoheight'] <= medium_config['max_height']: + return False + if not metadata['videowidth'] <= medium_config['max_width']: + return False + + return True diff --git a/mediagoblin/messages.py b/mediagoblin/messages.py new file mode 100644 index 00000000..d58f13d4 --- /dev/null +++ b/mediagoblin/messages.py @@ -0,0 +1,50 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools import common + +DEBUG = 'debug' +INFO = 'info' +SUCCESS = 'success' +WARNING = 'warning' +ERROR = 'error' + +ADD_MESSAGE_TEST = [] + + +def add_message(request, level, text): + messages = request.session.setdefault('messages', []) + messages.append({'level': level, 'text': text}) + + if common.TESTS_ENABLED: + ADD_MESSAGE_TEST.append(messages) + + request.session.save() + + +def fetch_messages(request, clear_from_session=True): + messages = request.session.get('messages') + if messages and clear_from_session: + # Save that we removed the messages from the session + request.session['messages'] = [] + request.session.save() + + return messages + + +def clear_add_message(): + global ADD_MESSAGE_TEST + ADD_MESSAGE_TEST = [] diff --git a/mediagoblin/mg_globals.py b/mediagoblin/mg_globals.py new file mode 100644 index 00000000..26ed66fa --- /dev/null +++ b/mediagoblin/mg_globals.py @@ -0,0 +1,70 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +In some places, we need to access the database, public_store, queue_store +""" + +import gettext +import pkg_resources +import threading + + +############################# +# General mediagoblin globals +############################# + +# SQL database engine +database = None + +# should be the same as the +public_store = None +queue_store = None + +# A WorkBenchManager +workbench_manager = None + +# A thread-local scope +thread_scope = threading.local() + +# gettext (this needs to default to English so it doesn't break +# in case we're running a script without the app like +# ./bin/gmg theme assetlink) +thread_scope.translations = gettext.translation( + 'mediagoblin', + pkg_resources.resource_filename( + 'mediagoblin', 'i18n'), ['en'], fallback=True) + +# app and global config objects +app_config = None +global_config = None + +# The actual app object +app = None + + +def setup_globals(**kwargs): + """ + Sets up a bunch of globals in this module. + + Takes the globals to setup as keyword arguments. If globals are + specified that aren't set as variables above, then throw an error. + """ + from mediagoblin import mg_globals + + for key, value in kwargs.iteritems(): + if not hasattr(mg_globals, key): + raise AssertionError("Global %s not known" % key) + setattr(mg_globals, key, value) diff --git a/mediagoblin/plugins/README b/mediagoblin/plugins/README new file mode 100644 index 00000000..a2b92334 --- /dev/null +++ b/mediagoblin/plugins/README @@ -0,0 +1,6 @@ +======== + README +======== + +This directory holds the MediaGoblin core plugins. These plugins are not +enabled by default. See documentation for enabling plugins. diff --git a/mediagoblin/plugins/__init__.py b/mediagoblin/plugins/__init__.py new file mode 100644 index 00000000..719b56e7 --- /dev/null +++ b/mediagoblin/plugins/__init__.py @@ -0,0 +1,16 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + diff --git a/mediagoblin/plugins/api/__init__.py b/mediagoblin/plugins/api/__init__.py new file mode 100644 index 00000000..1eddd9e0 --- /dev/null +++ b/mediagoblin/plugins/api/__init__.py @@ -0,0 +1,47 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import logging + +from mediagoblin.tools import pluginapi + +_log = logging.getLogger(__name__) + +PLUGIN_DIR = os.path.dirname(__file__) + +def setup_plugin(): + _log.info('Setting up API...') + + config = pluginapi.get_config(__name__) + + _log.debug('API config: {0}'.format(config)) + + routes = [ + ('mediagoblin.plugins.api.test', + '/api/test', + 'mediagoblin.plugins.api.views:api_test'), + ('mediagoblin.plugins.api.entries', + '/api/entries', + 'mediagoblin.plugins.api.views:get_entries'), + ('mediagoblin.plugins.api.post_entry', + '/api/submit', + 'mediagoblin.plugins.api.views:post_entry')] + + pluginapi.register_routes(routes) + +hooks = { + 'setup': setup_plugin} diff --git a/mediagoblin/plugins/api/tools.py b/mediagoblin/plugins/api/tools.py new file mode 100644 index 00000000..92411f4b --- /dev/null +++ b/mediagoblin/plugins/api/tools.py @@ -0,0 +1,164 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import json + +from functools import wraps +from urlparse import urljoin +from werkzeug.exceptions import Forbidden +from werkzeug.wrappers import Response +from mediagoblin import mg_globals +from mediagoblin.tools.pluginapi import PluginManager +from mediagoblin.storage.filestorage import BasicFileStorage + +_log = logging.getLogger(__name__) + + +class Auth(object): + ''' + An object with two significant methods, 'trigger' and 'run'. + + Using a similar object to this, plugins can register specific + authentication logic, for example the GET param 'access_token' for OAuth. + + - trigger: Analyze the 'request' argument, return True if you think you + can handle the request, otherwise return False + - run: The authentication logic, set the request.user object to the user + you intend to authenticate and return True, otherwise return False. + + If run() returns False, an HTTP 403 Forbidden error will be shown. + + You may also display custom errors, just raise them within the run() + method. + ''' + def trigger(self, request): + raise NotImplemented() + + def __call__(self, request, *args, **kw): + raise NotImplemented() + + +def json_response(serializable, _disable_cors=False, *args, **kw): + ''' + Serializes a json objects and returns a werkzeug Response object with the + serialized value as the response body and Content-Type: application/json. + + :param serializable: A json-serializable object + + Any extra arguments and keyword arguments are passed to the + Response.__init__ method. + ''' + response = Response(json.dumps(serializable), *args, content_type='application/json', **kw) + + if not _disable_cors: + cors_headers = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With'} + for key, value in cors_headers.iteritems(): + response.headers.set(key, value) + + return response + + +def get_entry_serializable(entry, urlgen): + ''' + Returns a serializable dict() of a MediaEntry instance. + + :param entry: A MediaEntry instance + :param urlgen: An urlgen instance, can be found on the request object passed + to views. + ''' + return { + 'user': entry.get_uploader.username, + 'user_id': entry.get_uploader.id, + 'user_bio': entry.get_uploader.bio, + 'user_bio_html': entry.get_uploader.bio_html, + 'user_permalink': urlgen('mediagoblin.user_pages.user_home', + user=entry.get_uploader.username, + qualified=True), + 'id': entry.id, + 'created': entry.created.isoformat(), + 'title': entry.title, + 'license': entry.license, + 'description': entry.description, + 'description_html': entry.description_html, + 'media_type': entry.media_type, + 'state': entry.state, + 'permalink': entry.url_for_self(urlgen, qualified=True), + 'media_files': get_media_file_paths(entry.media_files, urlgen)} + + +def get_media_file_paths(media_files, urlgen): + ''' + Returns a dictionary of media files with `file_handle` => `qualified URL` + + :param media_files: dict-like object consisting of `file_handle => `listy + filepath` pairs. + :param urlgen: An urlgen object, usually found on request.urlgen. + ''' + media_urls = {} + + for key, val in media_files.items(): + if isinstance(mg_globals.public_store, BasicFileStorage): + # BasicFileStorage does not provide a qualified URI + media_urls[key] = urljoin( + urlgen('index', qualified=True), + mg_globals.public_store.file_url(val)) + else: + media_urls[key] = mg_globals.public_store.file_url(val) + + return media_urls + + +def api_auth(controller): + ''' + Decorator, allows plugins to register auth methods that will then be + evaluated against the request, finally a worthy authenticator object is + chosen and used to decide whether to grant or deny access. + ''' + @wraps(controller) + def wrapper(request, *args, **kw): + auth_candidates = [] + + for auth in PluginManager().get_hook_callables('auth'): + if auth.trigger(request): + _log.debug('{0} believes it is capable of authenticating this request.'.format(auth)) + auth_candidates.append(auth) + + # If we can't find any authentication methods, we should not let them + # pass. + if not auth_candidates: + raise Forbidden() + + # For now, just select the first one in the list + auth = auth_candidates[0] + + _log.debug('Using {0} to authorize request {1}'.format( + auth, request.url)) + + if not auth(request, *args, **kw): + if getattr(auth, 'errors', []): + return json_response({ + 'status': 403, + 'errors': auth.errors}) + + raise Forbidden() + + return controller(request, *args, **kw) + + return wrapper diff --git a/mediagoblin/plugins/api/views.py b/mediagoblin/plugins/api/views.py new file mode 100644 index 00000000..9159fe65 --- /dev/null +++ b/mediagoblin/plugins/api/views.py @@ -0,0 +1,122 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import logging + +from os.path import splitext +from werkzeug.exceptions import BadRequest, Forbidden +from werkzeug.wrappers import Response + +from mediagoblin.decorators import require_active_login +from mediagoblin.meddleware.csrf import csrf_exempt +from mediagoblin.media_types import sniff_media +from mediagoblin.plugins.api.tools import api_auth, get_entry_serializable, \ + json_response +from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \ + run_process_media, new_upload_entry + +_log = logging.getLogger(__name__) + + +@csrf_exempt +@api_auth +@require_active_login +def post_entry(request): + _log.debug('Posting entry') + + if request.method == 'OPTIONS': + return json_response({'status': 200}) + + if request.method != 'POST': + _log.debug('Must POST against post_entry') + raise BadRequest() + + if not check_file_field(request, 'file'): + _log.debug('File field not found') + raise BadRequest() + + media_file = request.files['file'] + + media_type, media_manager = sniff_media(media_file) + + entry = new_upload_entry(request.user) + entry.media_type = unicode(media_type) + entry.title = unicode(request.form.get('title') + or splitext(media_file.filename)[0]) + + entry.description = unicode(request.form.get('description')) + entry.license = unicode(request.form.get('license', '')) + + entry.generate_slug() + + # queue appropriately + queue_file = prepare_queue_task(request.app, entry, media_file.filename) + + with queue_file: + queue_file.write(request.files['file'].stream.read()) + + # Save now so we have this data before kicking off processing + entry.save() + + if request.form.get('callback_url'): + metadata = request.db.ProcessingMetaData() + metadata.media_entry = entry + metadata.callback_url = unicode(request.form['callback_url']) + metadata.save() + + # Pass off to processing + # + # (... don't change entry after this point to avoid race + # conditions with changes to the document via processing code) + feed_url = request.urlgen( + 'mediagoblin.user_pages.atom_feed', + qualified=True, user=request.user.username) + run_process_media(entry, feed_url) + + return json_response(get_entry_serializable(entry, request.urlgen)) + + +@api_auth +@require_active_login +def api_test(request): + user_data = { + 'username': request.user.username, + 'email': request.user.email} + + # TODO: This is the *only* thing using Response() here, should that + # not simply use json_response()? + return Response(json.dumps(user_data)) + + +def get_entries(request): + entries = request.db.MediaEntry.query + + # TODO: Make it possible to fetch unprocessed media, or media in-processing + entries = entries.filter_by(state=u'processed') + + # TODO: Add sort order customization + entries = entries.order_by(request.db.MediaEntry.created.desc()) + + # TODO: Fetch default and upper limit from config + entries = entries.limit(int(request.GET.get('limit') or 10)) + + entries_serializable = [] + + for entry in entries: + entries_serializable.append(get_entry_serializable(entry, request.urlgen)) + + return json_response(entries_serializable) diff --git a/mediagoblin/plugins/flatpagesfile/README.rst b/mediagoblin/plugins/flatpagesfile/README.rst new file mode 100644 index 00000000..59cd6217 --- /dev/null +++ b/mediagoblin/plugins/flatpagesfile/README.rst @@ -0,0 +1,163 @@ +.. _flatpagesfile-chapter: + +====================== + flatpagesfile plugin +====================== + +This is the flatpages file plugin. It allows you to add pages to your +MediaGoblin instance which are not generated from user content. For +example, this is useful for these pages: + +* About this site +* Terms of service +* Privacy policy +* How to get an account here +* ... + + +How to configure +================ + +Add the following to your MediaGoblin .ini file in the ``[plugins]`` +section:: + + [[mediagoblin.plugins.flatpagesfile]] + + +This tells MediaGoblin to load the flatpagesfile plugin. This is the +subsection that you'll do all flatpagesfile plugin configuration in. + + +How to add pages +================ + +To add a new page to your site, you need to do two things: + +1. add a route to the MediaGoblin .ini file in the flatpagesfile + subsection + +2. write a template that will get served when that route is requested + + +Routes +------ + +First, let's talk about the route. + +A route is a key/value in your configuration file. + +The key for the route is the route name You can use this with `url()` +in templates to have MediaGoblin automatically build the urls for +you. It's very handy. + +It should be "unique" and it should be alphanumeric characters and +hyphens. I wouldn't put spaces in there. + +Examples: ``flatpages-about``, ``about-view``, ``contact-view``, ... + +The value has two parts separated by commas: + +1. **route path**: This is the url that this route matches. + + Examples: ``/about``, ``/contact``, ``/pages/about``, ... + + You can do anything with this that you can do with the routepath + parameter of `routes.Route`. For more details, see `the routes + documentation <http://routes.readthedocs.org/en/latest/>`_. + + Example: ``/siteadmin/{adminname:\w+}`` + + .. Note:: + + If you're doing something fancy, enclose the route in single + quotes. + + For example: ``'/siteadmin/{adminname:\w+}'`` + +2. **template**: The template to use for this url. The template is in + the flatpagesfile template directory, so you just need to specify + the file name. + + Like with other templates, if it's an HTML file, it's good to use + the ``.html`` extensions. + + Examples: ``index.html``, ``about.html``, ``contact.html``, ... + + +Here's an example configuration that adds two flat pages: one for an +"About this site" page and one for a "Terms of service" page:: + + [[mediagoblin.plugins.flatpagesfile]] + about-view = '/about', about.html + terms-view = '/terms', terms.html + + +.. Note:: + + The order in which you define the routes in the config file is the + order in which they're checked for incoming requests. + + +Templates +--------- + +To add pages, you must edit template files on the file system in your +`local_templates` directory. + +The directory structure looks kind of like this:: + + local_templates + |- flatpagesfile + |- flatpage1.html + |- flatpage2.html + |- ... + + +The ``.html`` file contains the content of your page. It's just a +template like all the other templates you have. + +Here's an example that extends the `flatpagesfile/base.html` +template:: + + {% extends "flatpagesfile/base.html" %} + {% block mediagoblin_content %} + <h1>About this site</h1> + <p> + This site is a MediaGoblin instance set up to host media for + me, my family and my friends. + </p> + {% endblock %} + + +.. Note:: + + If you have a bunch of flatpages that kind of look like one + another, take advantage of Jinja2 template extending and create a + base template that the others extend. + + +Recipes +======= + +Url variables +------------- + +You can handle urls like ``/about/{name}`` and access the name that's +passed in in the template. + +Sample route:: + + about-page = '/about/{name}', about.html + +Sample template:: + + {% extends "flatpagesfile/base.html" %} + {% block mediagoblin_content %} + + <h1>About page for {{ request.matchdict['name'] }}</h1> + + {% endblock %} + +See the `the routes documentation +<http://routes.readthedocs.org/en/latest/>`_ for syntax details for +the route. Values will end up in the ``request.matchdict`` dict. diff --git a/mediagoblin/plugins/flatpagesfile/__init__.py b/mediagoblin/plugins/flatpagesfile/__init__.py new file mode 100644 index 00000000..3d797809 --- /dev/null +++ b/mediagoblin/plugins/flatpagesfile/__init__.py @@ -0,0 +1,78 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import logging +import os + +import jinja2 + +from mediagoblin.tools import pluginapi +from mediagoblin.tools.response import render_to_response + + +PLUGIN_DIR = os.path.dirname(__file__) + + +_log = logging.getLogger(__name__) + + +@jinja2.contextfunction +def print_context(c): + s = [] + for key, val in c.items(): + s.append('%s: %s' % (key, repr(val))) + return '\n'.join(s) + + +def flatpage_handler_builder(template): + """Flatpage view generator + + Given a template, generates the controller function for handling that + route. + + """ + def _flatpage_handler_builder(request): + return render_to_response( + request, 'flatpagesfile/%s' % template, + {'request': request}) + return _flatpage_handler_builder + + +def setup_plugin(): + config = pluginapi.get_config('mediagoblin.plugins.flatpagesfile') + + _log.info('Setting up flatpagesfile....') + + # Register the template path. + pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates')) + + pages = config.items() + + routes = [] + for name, (url, template) in pages: + name = 'flatpagesfile.%s' % name.strip() + controller = flatpage_handler_builder(template) + routes.append( + (name, url, controller)) + + pluginapi.register_routes(routes) + _log.info('Done setting up flatpagesfile!') + + +hooks = { + 'setup': setup_plugin + } diff --git a/mediagoblin/plugins/flatpagesfile/templates/flatpagesfile/base.html b/mediagoblin/plugins/flatpagesfile/templates/flatpagesfile/base.html new file mode 100644 index 00000000..1cf9dd9d --- /dev/null +++ b/mediagoblin/plugins/flatpagesfile/templates/flatpagesfile/base.html @@ -0,0 +1,18 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +{% extends "mediagoblin/base.html" %} diff --git a/mediagoblin/plugins/geolocation/__init__.py b/mediagoblin/plugins/geolocation/__init__.py new file mode 100644 index 00000000..5d14590e --- /dev/null +++ b/mediagoblin/plugins/geolocation/__init__.py @@ -0,0 +1,35 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools import pluginapi +import os + +PLUGIN_DIR = os.path.dirname(__file__) + +def setup_plugin(): + config = pluginapi.get_config('mediagoblin.plugins.geolocation') + + # Register the template path. + pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates')) + + pluginapi.register_template_hooks( + {"image_sideinfo": "mediagoblin/plugins/geolocation/map.html", + "image_head": "mediagoblin/plugins/geolocation/map_js_head.html"}) + + +hooks = { + 'setup': setup_plugin + } diff --git a/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html new file mode 100644 index 00000000..70f837ff --- /dev/null +++ b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map.html @@ -0,0 +1,59 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% block geolocation_map %} + {% if media.media_data.gps_latitude is defined + and media.media_data.gps_latitude + and media.media_data.gps_longitude is defined + and media.media_data.gps_longitude %} + <h3>{% trans %}Location{% endtrans %}</h3> + <div> + {%- set lon = media.media_data.gps_longitude %} + {%- set lat = media.media_data.gps_latitude %} + {%- set osm_url = "http://openstreetmap.org/?mlat={lat}&mlon={lon}".format(lat=lat, lon=lon) %} + <div id="tile-map" style="width: 100%; height: 196px;"> + <input type="hidden" id="gps-longitude" + value="{{ lon }}" /> + <input type="hidden" id="gps-latitude" + value="{{ lat }}" /> + </div> + <script> <!-- pop up full OSM license when clicked --> + $(document).ready(function(){ + $("#osm_license_link").click(function () { + $("#osm_attrib").slideToggle("slow"); + }); + }); + </script> + <div id="osm_attrib" class="hidden"><ul><li> + Data ©<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 -%} + View on <a href="{{ osm_url }}">OpenStreetMap</a> + {%- endtrans %} + </small> + </p> + </div> + {% endif %} +{% endblock %} diff --git a/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map_js_head.html b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map_js_head.html new file mode 100644 index 00000000..aca0f730 --- /dev/null +++ b/mediagoblin/plugins/geolocation/templates/mediagoblin/plugins/geolocation/map_js_head.html @@ -0,0 +1,25 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +<link rel="stylesheet" + href="{{ request.staticdirect('/extlib/leaflet/leaflet.css') }}" /> + +<script type="text/javascript" + src="{{ request.staticdirect('/extlib/leaflet/leaflet.js') }}"></script> +<script type="text/javascript" + src="{{ request.staticdirect('/js/geolocation-map.js') }}"></script> diff --git a/mediagoblin/plugins/httpapiauth/__init__.py b/mediagoblin/plugins/httpapiauth/__init__.py new file mode 100644 index 00000000..2b2d593c --- /dev/null +++ b/mediagoblin/plugins/httpapiauth/__init__.py @@ -0,0 +1,58 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from werkzeug.exceptions import Unauthorized + +from mediagoblin.auth.tools import check_login_simple +from mediagoblin.plugins.api.tools import Auth + +_log = logging.getLogger(__name__) + + +def setup_http_api_auth(): + _log.info('Setting up HTTP API Auth...') + + +class HTTPAuth(Auth): + def trigger(self, request): + if request.authorization: + return True + + return False + + def __call__(self, request, *args, **kw): + _log.debug('Trying to authorize the user agent via HTTP Auth') + if not request.authorization: + return False + + user = check_login_simple(unicode(request.authorization['username']), + request.authorization['password']) + + if user: + request.user = user + return True + else: + raise Unauthorized() + + return False + + + +hooks = { + 'setup': setup_http_api_auth, + 'auth': HTTPAuth()} diff --git a/mediagoblin/plugins/oauth/README.rst b/mediagoblin/plugins/oauth/README.rst new file mode 100644 index 00000000..753b180f --- /dev/null +++ b/mediagoblin/plugins/oauth/README.rst @@ -0,0 +1,148 @@ +============== + OAuth plugin +============== + +.. warning:: + In its current state. This plugin has received no security audit. + Development has been entirely focused on Making It Work(TM). Use this + plugin with caution. + + Additionally, this and the API may break... consider it pre-alpha. + There's also a known issue that the OAuth client doesn't do + refresh tokens so this might result in issues for users. + +The OAuth plugin enables third party web applications to authenticate as one or +more GNU MediaGoblin users in a safe way in order retrieve, create and update +content stored on the GNU MediaGoblin instance. + +The OAuth plugin is based on the `oauth v2.25 draft`_ and is pointing by using +the ``oauthlib.oauth2.draft25.WebApplicationClient`` from oauthlib_ to a +mediagoblin instance and building the OAuth 2 provider logic around the client. + +There are surely some aspects of the OAuth v2.25 draft that haven't made it +into this plugin due to the technique used to develop it. + +.. _`oauth v2.25 draft`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25 +.. _oauthlib: http://pypi.python.org/pypi/oauthlib + + +Set up the OAuth plugin +======================= + +1. Add the following to your MediaGoblin .ini file in the ``[plugins]`` section:: + + [[mediagoblin.plugins.oauth]] + +2. Run:: + + gmg dbupdate + + in order to create and apply migrations to any database tables that the + plugin requires. + +.. note:: + This only enables the OAuth plugin. To be able to let clients fetch data + from the MediaGoblin instance you should also enable the API plugin or some + other plugin that supports authenticating with OAuth credentials. + + +Authenticate against GNU MediaGoblin +==================================== + +.. note:: + As mentioned in `capabilities`_ GNU MediaGoblin currently only supports the + `Authorization Code Grant`_ procedure for obtaining an OAuth access token. + +Authorization Code Grant +------------------------ + +.. note:: + As mentioned in `incapabilities`_ GNU MediaGoblin currently does not + support `client registration`_ + +The `authorization code grant`_ works in the following way: + +`Definitions` + + Authorization server + The GNU MediaGoblin instance + Resource server + Also the GNU MediaGoblin instance ;) + Client + The web application intended to use the data + Redirect uri + An URI pointing to a page controlled by the *client* + Resource owner + The GNU MediaGoblin user who's resources the client requests access to + User agent + Commonly the GNU MediaGoblin user's web browser + Authorization code + An intermediate token that is exchanged for an *access token* + Access token + A secret token that the *client* uses to authenticate itself agains the + *resource server* as a specific *resource owner*. + + +Brief description of the procedure +++++++++++++++++++++++++++++++++++ + +1. The *client* requests an *authorization code* from the *authorization + server* by redirecting the *user agent* to the `Authorization Endpoint`_. + Which parameters should be included in the redirect are covered later in + this document. +2. The *authorization server* authenticates the *resource owner* and redirects + the *user agent* back to the *redirect uri* (covered later in this + document). +3. The *client* receives the request from the *user agent*, attached is the + *authorization code*. +4. The *client* requests an *access token* from the *authorization server* +5. \?\?\?\?\? +6. Profit! + + +Detailed description of the procedure ++++++++++++++++++++++++++++++++++++++ + +TBD, in the meantime here is a proof-of-concept GNU MediaGoblin client: + +https://github.com/jwandborg/omgmg/ + +and here are some detailed descriptions from other OAuth 2 +providers: + +- https://developers.google.com/accounts/docs/OAuth2WebServer +- https://developers.facebook.com/docs/authentication/server-side/ + +and if you're unsure about anything, there's the `OAuth v2.25 draft +<http://tools.ietf.org/html/draft-ietf-oauth-v2-25>`_, the `OAuth plugin +source code +<http://gitorious.org/mediagoblin/mediagoblin/trees/master/mediagoblin/plugins/oauth>`_ +and the `#mediagoblin IRC channel <http://mediagoblin.org/pages/join.html#irc>`_. + + +Capabilities +============ + +- `Authorization endpoint`_ - Located at ``/oauth/authorize`` +- `Token endpoint`_ - Located at ``/oauth/access_token`` +- `Authorization Code Grant`_ +- `Client Registration`_ + +.. _`Authorization endpoint`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.1 +.. _`Token endpoint`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.2 +.. _`Authorization Code Grant`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.1 +.. _`Client Registration`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-2 + +Incapabilities +============== + +- Only `bearer tokens`_ are issued. +- `Implicit Grant`_ +- `Force TLS for token endpoint`_ - This one is up the the siteadmin +- Authorization `scope`_ and `state` +- ... + +.. _`bearer tokens`: http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08 +.. _`scope`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.3 +.. _`Implicit Grant`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.2 +.. _`Force TLS for token endpoint`: http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-3.2 diff --git a/mediagoblin/plugins/oauth/__init__.py b/mediagoblin/plugins/oauth/__init__.py new file mode 100644 index 00000000..5762379d --- /dev/null +++ b/mediagoblin/plugins/oauth/__init__.py @@ -0,0 +1,109 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import logging + +from mediagoblin.tools import pluginapi +from mediagoblin.plugins.oauth.models import OAuthToken, OAuthClient, \ + OAuthUserClient +from mediagoblin.plugins.api.tools import Auth + +_log = logging.getLogger(__name__) + +PLUGIN_DIR = os.path.dirname(__file__) + + +def setup_plugin(): + config = pluginapi.get_config('mediagoblin.plugins.oauth') + + _log.info('Setting up OAuth...') + _log.debug('OAuth config: {0}'.format(config)) + + routes = [ + ('mediagoblin.plugins.oauth.authorize', + '/oauth/authorize', + 'mediagoblin.plugins.oauth.views:authorize'), + ('mediagoblin.plugins.oauth.authorize_client', + '/oauth/client/authorize', + 'mediagoblin.plugins.oauth.views:authorize_client'), + ('mediagoblin.plugins.oauth.access_token', + '/oauth/access_token', + 'mediagoblin.plugins.oauth.views:access_token'), + ('mediagoblin.plugins.oauth.list_connections', + '/oauth/client/connections', + 'mediagoblin.plugins.oauth.views:list_connections'), + ('mediagoblin.plugins.oauth.register_client', + '/oauth/client/register', + 'mediagoblin.plugins.oauth.views:register_client'), + ('mediagoblin.plugins.oauth.list_clients', + '/oauth/client/list', + 'mediagoblin.plugins.oauth.views:list_clients')] + + pluginapi.register_routes(routes) + pluginapi.register_template_path(os.path.join(PLUGIN_DIR, 'templates')) + + +class OAuthAuth(Auth): + def trigger(self, request): + if 'access_token' in request.GET: + return True + + return False + + def __call__(self, request, *args, **kw): + self.errors = [] + # TODO: Add suport for client credentials authorization + client_id = request.GET.get('client_id') # TODO: Not used + client_secret = request.GET.get('client_secret') # TODO: Not used + access_token = request.GET.get('access_token') + + _log.debug('Authorizing request {0}'.format(request.url)) + + if access_token: + token = OAuthToken.query.filter(OAuthToken.token == access_token)\ + .first() + + if not token: + self.errors.append('Invalid access token') + return False + + _log.debug('Access token: {0}'.format(token)) + _log.debug('Client: {0}'.format(token.client)) + + relation = OAuthUserClient.query.filter( + (OAuthUserClient.user == token.user) + & (OAuthUserClient.client == token.client) + & (OAuthUserClient.state == u'approved')).first() + + _log.debug('Relation: {0}'.format(relation)) + + if not relation: + self.errors.append( + u'Client has not been approved by the resource owner') + return False + + request.user = token.user + return True + + self.errors.append(u'No access_token specified') + + return False + +hooks = { + 'setup': setup_plugin, + 'auth': OAuthAuth() + } diff --git a/mediagoblin/plugins/oauth/forms.py b/mediagoblin/plugins/oauth/forms.py new file mode 100644 index 00000000..5edd992a --- /dev/null +++ b/mediagoblin/plugins/oauth/forms.py @@ -0,0 +1,69 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import wtforms + +from urlparse import urlparse + +from mediagoblin.tools.extlib.wtf_html5 import URLField +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + + +class AuthorizationForm(wtforms.Form): + client_id = wtforms.HiddenField(u'', + validators=[wtforms.validators.Required()]) + next = wtforms.HiddenField(u'', validators=[wtforms.validators.Required()]) + allow = wtforms.SubmitField(_(u'Allow')) + deny = wtforms.SubmitField(_(u'Deny')) + + +class ClientRegistrationForm(wtforms.Form): + name = wtforms.TextField(_('Name'), [wtforms.validators.Required()], + description=_('The name of the OAuth client')) + description = wtforms.TextAreaField(_('Description'), + [wtforms.validators.Length(min=0, max=500)], + description=_('''This will be visible to users allowing your + application to authenticate as them.''')) + type = wtforms.SelectField(_('Type'), + [wtforms.validators.Required()], + choices=[ + ('confidential', 'Confidential'), + ('public', 'Public')], + description=_('''<strong>Confidential</strong> - The client can + make requests to the GNU MediaGoblin instance that can not be + intercepted by the user agent (e.g. server-side client).<br /> + <strong>Public</strong> - The client can't make confidential + requests to the GNU MediaGoblin instance (e.g. client-side + JavaScript client).''')) + + redirect_uri = URLField(_('Redirect URI'), + [wtforms.validators.Optional(), wtforms.validators.URL()], + description=_('''The redirect URI for the applications, this field + is <strong>required</strong> for public clients.''')) + + def __init__(self, *args, **kw): + wtforms.Form.__init__(self, *args, **kw) + + def validate(self): + if not wtforms.Form.validate(self): + return False + + if self.type.data == 'public' and not self.redirect_uri.data: + self.redirect_uri.errors.append( + _('This field is required for public clients')) + return False + + return True diff --git a/mediagoblin/plugins/oauth/migrations.py b/mediagoblin/plugins/oauth/migrations.py new file mode 100644 index 00000000..d7b89da3 --- /dev/null +++ b/mediagoblin/plugins/oauth/migrations.py @@ -0,0 +1,158 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from datetime import datetime, timedelta +from sqlalchemy import (MetaData, Table, Column, + Integer, Unicode, Enum, DateTime, ForeignKey) +from sqlalchemy.ext.declarative import declarative_base + +from mediagoblin.db.migration_tools import RegisterMigration +from mediagoblin.db.models import User + + +MIGRATIONS = {} + + +class OAuthClient_v0(declarative_base()): + __tablename__ = 'oauth__client' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + + name = Column(Unicode) + description = Column(Unicode) + + identifier = Column(Unicode, unique=True, index=True) + secret = Column(Unicode, index=True) + + owner_id = Column(Integer, ForeignKey(User.id)) + redirect_uri = Column(Unicode) + + type = Column(Enum( + u'confidential', + u'public', + name=u'oauth__client_type')) + + +class OAuthUserClient_v0(declarative_base()): + __tablename__ = 'oauth__user_client' + id = Column(Integer, primary_key=True) + + user_id = Column(Integer, ForeignKey(User.id)) + client_id = Column(Integer, ForeignKey(OAuthClient_v0.id)) + + state = Column(Enum( + u'approved', + u'rejected', + name=u'oauth__relation_state')) + + +class OAuthToken_v0(declarative_base()): + __tablename__ = 'oauth__tokens' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + expires = Column(DateTime, nullable=False, + default=lambda: datetime.now() + timedelta(days=30)) + token = Column(Unicode, index=True) + refresh_token = Column(Unicode, index=True) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + index=True) + + client_id = Column(Integer, ForeignKey(OAuthClient_v0.id), nullable=False) + + def __repr__(self): + return '<{0} #{1} expires {2} [{3}, {4}]>'.format( + self.__class__.__name__, + self.id, + self.expires.isoformat(), + self.user, + self.client) + + +class OAuthCode_v0(declarative_base()): + __tablename__ = 'oauth__codes' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + expires = Column(DateTime, nullable=False, + default=lambda: datetime.now() + timedelta(minutes=5)) + code = Column(Unicode, index=True) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + index=True) + + client_id = Column(Integer, ForeignKey(OAuthClient_v0.id), nullable=False) + + +class OAuthRefreshToken_v0(declarative_base()): + __tablename__ = 'oauth__refresh_tokens' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + + token = Column(Unicode, index=True) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False) + + # XXX: Is it OK to use OAuthClient_v0.id in this way? + client_id = Column(Integer, ForeignKey(OAuthClient_v0.id), nullable=False) + + +@RegisterMigration(1, MIGRATIONS) +def remove_and_replace_token_and_code(db): + metadata = MetaData(bind=db.bind) + + token_table = Table('oauth__tokens', metadata, autoload=True, + autoload_with=db.bind) + + token_table.drop() + + code_table = Table('oauth__codes', metadata, autoload=True, + autoload_with=db.bind) + + code_table.drop() + + OAuthClient_v0.__table__.create(db.bind) + OAuthUserClient_v0.__table__.create(db.bind) + OAuthToken_v0.__table__.create(db.bind) + OAuthCode_v0.__table__.create(db.bind) + + db.commit() + + +@RegisterMigration(2, MIGRATIONS) +def remove_refresh_token_field(db): + metadata = MetaData(bind=db.bind) + + token_table = Table('oauth__tokens', metadata, autoload=True, + autoload_with=db.bind) + + refresh_token = token_table.columns['refresh_token'] + + refresh_token.drop() + db.commit() + +@RegisterMigration(3, MIGRATIONS) +def create_refresh_token_table(db): + OAuthRefreshToken_v0.__table__.create(db.bind) + + db.commit() diff --git a/mediagoblin/plugins/oauth/models.py b/mediagoblin/plugins/oauth/models.py new file mode 100644 index 00000000..439424d3 --- /dev/null +++ b/mediagoblin/plugins/oauth/models.py @@ -0,0 +1,192 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from datetime import datetime, timedelta + + +from sqlalchemy import ( + Column, Unicode, Integer, DateTime, ForeignKey, Enum) +from sqlalchemy.orm import relationship, backref +from mediagoblin.db.base import Base +from mediagoblin.db.models import User +from mediagoblin.plugins.oauth.tools import generate_identifier, \ + generate_secret, generate_token, generate_code, generate_refresh_token + +# Don't remove this, I *think* it applies sqlalchemy-migrate functionality onto +# the models. +from migrate import changeset + + +class OAuthClient(Base): + __tablename__ = 'oauth__client' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + + name = Column(Unicode) + description = Column(Unicode) + + identifier = Column(Unicode, unique=True, index=True, + default=generate_identifier) + secret = Column(Unicode, index=True, default=generate_secret) + + owner_id = Column(Integer, ForeignKey(User.id)) + owner = relationship( + User, + backref=backref('registered_clients', cascade='all, delete-orphan')) + + redirect_uri = Column(Unicode) + + type = Column(Enum( + u'confidential', + u'public', + name=u'oauth__client_type')) + + def update_secret(self): + self.secret = generate_secret() + + def __repr__(self): + return '<{0} {1}:{2} ({3})>'.format( + self.__class__.__name__, + self.id, + self.name.encode('ascii', 'replace'), + self.owner.username.encode('ascii', 'replace')) + + +class OAuthUserClient(Base): + __tablename__ = 'oauth__user_client' + id = Column(Integer, primary_key=True) + + user_id = Column(Integer, ForeignKey(User.id)) + user = relationship( + User, + backref=backref('oauth_client_relations', + cascade='all, delete-orphan')) + + client_id = Column(Integer, ForeignKey(OAuthClient.id)) + client = relationship( + OAuthClient, + backref=backref('oauth_user_relations', cascade='all, delete-orphan')) + + state = Column(Enum( + u'approved', + u'rejected', + name=u'oauth__relation_state')) + + def __repr__(self): + return '<{0} #{1} {2} [{3}, {4}]>'.format( + self.__class__.__name__, + self.id, + self.state.encode('ascii', 'replace'), + self.user, + self.client) + + +class OAuthToken(Base): + __tablename__ = 'oauth__tokens' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + expires = Column(DateTime, nullable=False, + default=lambda: datetime.now() + timedelta(days=30)) + token = Column(Unicode, index=True, default=generate_token) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + index=True) + user = relationship( + User, + backref=backref('oauth_tokens', cascade='all, delete-orphan')) + + client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False) + client = relationship( + OAuthClient, + backref=backref('oauth_tokens', cascade='all, delete-orphan')) + + def __repr__(self): + return '<{0} #{1} expires {2} [{3}, {4}]>'.format( + self.__class__.__name__, + self.id, + self.expires.isoformat(), + self.user, + self.client) + +class OAuthRefreshToken(Base): + __tablename__ = 'oauth__refresh_tokens' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + + token = Column(Unicode, index=True, + default=generate_refresh_token) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False) + + user = relationship(User, backref=backref('oauth_refresh_tokens', + cascade='all, delete-orphan')) + + client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False) + client = relationship(OAuthClient, + backref=backref( + 'oauth_refresh_tokens', + cascade='all, delete-orphan')) + + def __repr__(self): + return '<{0} #{1} [{3}, {4}]>'.format( + self.__class__.__name__, + self.id, + self.user, + self.client) + + +class OAuthCode(Base): + __tablename__ = 'oauth__codes' + + id = Column(Integer, primary_key=True) + created = Column(DateTime, nullable=False, + default=datetime.now) + expires = Column(DateTime, nullable=False, + default=lambda: datetime.now() + timedelta(minutes=5)) + code = Column(Unicode, index=True, default=generate_code) + + user_id = Column(Integer, ForeignKey(User.id), nullable=False, + index=True) + user = relationship(User, backref=backref('oauth_codes', + cascade='all, delete-orphan')) + + client_id = Column(Integer, ForeignKey(OAuthClient.id), nullable=False) + client = relationship(OAuthClient, backref=backref( + 'oauth_codes', + cascade='all, delete-orphan')) + + def __repr__(self): + return '<{0} #{1} expires {2} [{3}, {4}]>'.format( + self.__class__.__name__, + self.id, + self.expires.isoformat(), + self.user, + self.client) + + +MODELS = [ + OAuthToken, + OAuthRefreshToken, + OAuthCode, + OAuthClient, + OAuthUserClient] diff --git a/mediagoblin/plugins/oauth/templates/oauth/authorize.html b/mediagoblin/plugins/oauth/templates/oauth/authorize.html new file mode 100644 index 00000000..8a00c925 --- /dev/null +++ b/mediagoblin/plugins/oauth/templates/oauth/authorize.html @@ -0,0 +1,31 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +#, se, seee +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +{% extends "mediagoblin/base.html" %} +{% import "mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} +<form action="{{ request.urlgen('mediagoblin.plugins.oauth.authorize_client') }}" + method="POST"> + <div class="form_box_xl"> + {{ csrf_token }} + <h2>Authorize {{ client.name }}?</h2> + <p class="client-description">{{ client.description }}</p> + {{ wtforms_util.render_divs(form) }} + </div> +</form> +{% endblock %} diff --git a/mediagoblin/plugins/oauth/templates/oauth/client/connections.html b/mediagoblin/plugins/oauth/templates/oauth/client/connections.html new file mode 100644 index 00000000..63b0230a --- /dev/null +++ b/mediagoblin/plugins/oauth/templates/oauth/client/connections.html @@ -0,0 +1,34 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +{% extends "mediagoblin/base.html" %} +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} +<h1>{% trans %}OAuth client connections{% endtrans %}</h1> +{% if connections %} +<ul> + {% for connection in connections %} + <li><span title="{{ connection.client.description }}">{{ + connection.client.name }}</span> - {{ connection.state }} + </li> + {% endfor %} +</ul> +{% else %} +<p>You haven't connected using an OAuth client before.</p> +{% endif %} +{% endblock %} diff --git a/mediagoblin/plugins/oauth/templates/oauth/client/list.html b/mediagoblin/plugins/oauth/templates/oauth/client/list.html new file mode 100644 index 00000000..21024bb7 --- /dev/null +++ b/mediagoblin/plugins/oauth/templates/oauth/client/list.html @@ -0,0 +1,45 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +{% extends "mediagoblin/base.html" %} +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} +<h1>{% trans %}Your OAuth clients{% endtrans %}</h1> +{% if clients %} +<ul> + {% for client in clients %} + <li>{{ client.name }} + <dl> + <dt>Type</dt> + <dd>{{ client.type }}</dd> + <dt>Description</dt> + <dd>{{ client.description }}</dd> + <dt>Identifier</dt> + <dd>{{ client.identifier }}</dd> + <dt>Secret</dt> + <dd>{{ client.secret }}</dd> + <dt>Redirect URI<dt> + <dd>{{ client.redirect_uri }}</dd> + </dl> + </li> + {% endfor %} +</ul> +{% else %} +<p>You don't have any clients yet. <a href="{{ request.urlgen('mediagoblin.plugins.oauth.register_client') }}">Add one</a>.</p> +{% endif %} +{% endblock %} diff --git a/mediagoblin/plugins/oauth/templates/oauth/client/register.html b/mediagoblin/plugins/oauth/templates/oauth/client/register.html new file mode 100644 index 00000000..6fd700d3 --- /dev/null +++ b/mediagoblin/plugins/oauth/templates/oauth/client/register.html @@ -0,0 +1,34 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +{% extends "mediagoblin/base.html" %} +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} +<form action="{{ request.urlgen('mediagoblin.plugins.oauth.register_client') }}" + method="POST"> + <div class="form_box_xl"> + <h1>Register OAuth client</h1> + {{ wtforms_util.render_divs(form) }} + <div class="form_submit_buttons"> + {{ csrf_token }} + <input type="submit" value="{% trans %}Add{% endtrans %}" + class="button_form" /> + </div> + </div> +</form> +{% endblock %} diff --git a/mediagoblin/plugins/oauth/tools.py b/mediagoblin/plugins/oauth/tools.py new file mode 100644 index 00000000..27ff32b4 --- /dev/null +++ b/mediagoblin/plugins/oauth/tools.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import uuid + +from random import getrandbits + +from datetime import datetime + +from functools import wraps + +from mediagoblin.plugins.api.tools import json_response + + +def require_client_auth(controller): + ''' + View decorator + + - Requires the presence of ``?client_id`` + ''' + # Avoid circular import + from mediagoblin.plugins.oauth.models import OAuthClient + + @wraps(controller) + def wrapper(request, *args, **kw): + if not request.GET.get('client_id'): + return json_response({ + 'status': 400, + 'errors': [u'No client identifier in URL']}, + _disable_cors=True) + + client = OAuthClient.query.filter( + OAuthClient.identifier == request.GET.get('client_id')).first() + + if not client: + return json_response({ + 'status': 400, + 'errors': [u'No such client identifier']}, + _disable_cors=True) + + return controller(request, client) + + return wrapper + + +def create_token(client, user): + ''' + Create an OAuthToken and an OAuthRefreshToken entry in the database + + Returns the data structure expected by the OAuth clients. + ''' + from mediagoblin.plugins.oauth.models import OAuthToken, OAuthRefreshToken + + token = OAuthToken() + token.user = user + token.client = client + token.save() + + refresh_token = OAuthRefreshToken() + refresh_token.user = user + refresh_token.client = client + refresh_token.save() + + # expire time of token in full seconds + # timedelta.total_seconds is python >= 2.7 or we would use that + td = token.expires - datetime.now() + exp_in = 86400*td.days + td.seconds # just ignore µsec + + return {'access_token': token.token, 'token_type': 'bearer', + 'refresh_token': refresh_token.token, 'expires_in': exp_in} + + +def generate_identifier(): + ''' Generates a ``uuid.uuid4()`` ''' + return unicode(uuid.uuid4()) + + +def generate_token(): + ''' Uses generate_identifier ''' + return generate_identifier() + + +def generate_refresh_token(): + ''' Uses generate_identifier ''' + return generate_identifier() + + +def generate_code(): + ''' Uses generate_identifier ''' + return generate_identifier() + + +def generate_secret(): + ''' + Generate a long string of pseudo-random characters + ''' + # XXX: We might not want it to use bcrypt, since bcrypt takes its time to + # generate the result. + return unicode(getrandbits(192)) + diff --git a/mediagoblin/plugins/oauth/views.py b/mediagoblin/plugins/oauth/views.py new file mode 100644 index 00000000..d6fd314f --- /dev/null +++ b/mediagoblin/plugins/oauth/views.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -*- +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from urllib import urlencode + +from werkzeug.exceptions import BadRequest + +from mediagoblin.tools.response import render_to_response, redirect +from mediagoblin.decorators import require_active_login +from mediagoblin.messages import add_message, SUCCESS +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.plugins.oauth.models import OAuthCode, OAuthClient, \ + OAuthUserClient, OAuthRefreshToken +from mediagoblin.plugins.oauth.forms import ClientRegistrationForm, \ + AuthorizationForm +from mediagoblin.plugins.oauth.tools import require_client_auth, \ + create_token +from mediagoblin.plugins.api.tools import json_response + +_log = logging.getLogger(__name__) + + +@require_active_login +def register_client(request): + ''' + Register an OAuth client + ''' + form = ClientRegistrationForm(request.form) + + if request.method == 'POST' and form.validate(): + client = OAuthClient() + client.name = unicode(form.name.data) + client.description = unicode(form.description.data) + client.type = unicode(form.type.data) + client.owner_id = request.user.id + client.redirect_uri = unicode(form.redirect_uri.data) + + client.save() + + add_message(request, SUCCESS, _('The client {0} has been registered!')\ + .format( + client.name)) + + return redirect(request, 'mediagoblin.plugins.oauth.list_clients') + + return render_to_response( + request, + 'oauth/client/register.html', + {'form': form}) + + +@require_active_login +def list_clients(request): + clients = request.db.OAuthClient.query.filter( + OAuthClient.owner_id == request.user.id).all() + return render_to_response(request, 'oauth/client/list.html', + {'clients': clients}) + + +@require_active_login +def list_connections(request): + connections = OAuthUserClient.query.filter( + OAuthUserClient.user == request.user).all() + return render_to_response(request, 'oauth/client/connections.html', + {'connections': connections}) + + +@require_active_login +def authorize_client(request): + form = AuthorizationForm(request.form) + + client = OAuthClient.query.filter(OAuthClient.id == + form.client_id.data).first() + + if not client: + _log.error('No such client id as received from client authorization \ +form.') + raise BadRequest() + + if form.validate(): + relation = OAuthUserClient() + relation.user_id = request.user.id + relation.client_id = form.client_id.data + if form.allow.data: + relation.state = u'approved' + elif form.deny.data: + relation.state = u'rejected' + else: + raise BadRequest() + + relation.save() + + return redirect(request, location=form.next.data) + + return render_to_response( + request, + 'oauth/authorize.html', + {'form': form, + 'client': client}) + + +@require_client_auth +@require_active_login +def authorize(request, client): + # TODO: Get rid of the JSON responses in this view, it's called by the + # user-agent, not the client. + user_client_relation = OAuthUserClient.query.filter( + (OAuthUserClient.user == request.user) + & (OAuthUserClient.client == client)) + + if user_client_relation.filter(OAuthUserClient.state == + u'approved').count(): + redirect_uri = None + + if client.type == u'public': + if not client.redirect_uri: + return json_response({ + 'status': 400, + 'errors': + [u'Public clients should have a redirect_uri pre-set.']}, + _disable_cors=True) + + redirect_uri = client.redirect_uri + + if client.type == u'confidential': + redirect_uri = request.GET.get('redirect_uri', client.redirect_uri) + if not redirect_uri: + return json_response({ + 'status': 400, + 'errors': [u'No redirect_uri supplied!']}, + _disable_cors=True) + + code = OAuthCode() + code.user = request.user + code.client = client + code.save() + + redirect_uri = ''.join([ + redirect_uri, + '?', + urlencode({'code': code.code})]) + + _log.debug('Redirecting to {0}'.format(redirect_uri)) + + return redirect(request, location=redirect_uri) + else: + # Show prompt to allow client to access data + # - on accept: send the user agent back to the redirect_uri with the + # code parameter + # - on deny: send the user agent back to the redirect uri with error + # information + form = AuthorizationForm(request.form) + form.client_id.data = client.id + form.next.data = request.url + return render_to_response( + request, + 'oauth/authorize.html', + {'form': form, + 'client': client}) + + +def access_token(request): + ''' + Access token endpoint provides access tokens to any clients that have the + right grants/credentials + ''' + + client = None + user = None + + if request.GET.get('code'): + # Validate the code arg, then get the client object from the db. + code = OAuthCode.query.filter(OAuthCode.code == + request.GET.get('code')).first() + + if not code: + return json_response({ + 'error': 'invalid_request', + 'error_description': + 'Invalid code.'}) + + client = code.client + user = code.user + + elif request.args.get('refresh_token'): + # Validate a refresh token, then get the client object from the db. + refresh_token = OAuthRefreshToken.query.filter( + OAuthRefreshToken.token == + request.args.get('refresh_token')).first() + + if not refresh_token: + return json_response({ + 'error': 'invalid_request', + 'error_description': + 'Invalid refresh token.'}) + + client = refresh_token.client + user = refresh_token.user + + if client: + client_identifier = request.GET.get('client_id') + + if not client_identifier: + return json_response({ + 'error': 'invalid_request', + 'error_description': + 'Missing client_id in request.'}) + + if not client_identifier == client.identifier: + return json_response({ + 'error': 'invalid_client', + 'error_description': + 'Mismatching client credentials.'}) + + if client.type == u'confidential': + client_secret = request.GET.get('client_secret') + + if not client_secret: + return json_response({ + 'error': 'invalid_request', + 'error_description': + 'Missing client_secret in request.'}) + + if not client_secret == client.secret: + return json_response({ + 'error': 'invalid_client', + 'error_description': + 'Mismatching client credentials.'}) + + + access_token_data = create_token(client, user) + + return json_response(access_token_data, _disable_cors=True) + + return json_response({ + 'error': 'invalid_request', + 'error_description': + 'Missing `code` or `refresh_token` parameter in request.'}) diff --git a/mediagoblin/plugins/piwigo/README.rst b/mediagoblin/plugins/piwigo/README.rst new file mode 100644 index 00000000..0c71ffbc --- /dev/null +++ b/mediagoblin/plugins/piwigo/README.rst @@ -0,0 +1,23 @@ +=================== + piwigo api plugin +=================== + +.. danger:: + This plugin does not work. + It might make your instance unstable or even insecure. + So do not use it, unless you want to help to develop it. + +.. warning:: + You should not depend on this plugin in any way for now. + It might even go away without any notice. + +Okay, so if you still want to test this plugin, +add the following to your mediagoblin_local.ini: + +.. code-block:: ini + + [plugins] + [[mediagoblin.plugins.piwigo]] + +Then try to connect using some piwigo client. +There should be some logging, that might help. diff --git a/mediagoblin/plugins/piwigo/__init__.py b/mediagoblin/plugins/piwigo/__init__.py new file mode 100644 index 00000000..c4da708a --- /dev/null +++ b/mediagoblin/plugins/piwigo/__init__.py @@ -0,0 +1,42 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from mediagoblin.tools import pluginapi +from mediagoblin.tools.session import SessionManager +from .tools import PWGSession + +_log = logging.getLogger(__name__) + + +def setup_plugin(): + _log.info('Setting up piwigo...') + + routes = [ + ('mediagoblin.plugins.piwigo.wsphp', + '/api/piwigo/ws.php', + 'mediagoblin.plugins.piwigo.views:ws_php'), + ] + + pluginapi.register_routes(routes) + + PWGSession.session_manager = SessionManager("pwg_id", "plugins.piwigo") + + +hooks = { + 'setup': setup_plugin +} diff --git a/mediagoblin/plugins/piwigo/forms.py b/mediagoblin/plugins/piwigo/forms.py new file mode 100644 index 00000000..fb04aa6a --- /dev/null +++ b/mediagoblin/plugins/piwigo/forms.py @@ -0,0 +1,44 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import wtforms + + +class AddSimpleForm(wtforms.Form): + image = wtforms.FileField() + name = wtforms.TextField( + validators=[wtforms.validators.Length(min=0, max=500)]) + comment = wtforms.TextField() + # tags = wtforms.FieldList(wtforms.TextField()) + category = wtforms.IntegerField() + level = wtforms.IntegerField() + + +_md5_validator = wtforms.validators.Regexp(r"^[0-9a-fA-F]{32}$") + + +class AddForm(wtforms.Form): + original_sum = wtforms.TextField(None, + [_md5_validator, + wtforms.validators.Required()]) + thumbnail_sum = wtforms.TextField(None, + [wtforms.validators.Optional(), + _md5_validator]) + file_sum = wtforms.TextField(None, [_md5_validator]) + name = wtforms.TextField() + date_creation = wtforms.TextField() + categories = wtforms.TextField() diff --git a/mediagoblin/plugins/piwigo/tools.py b/mediagoblin/plugins/piwigo/tools.py new file mode 100644 index 00000000..484ea531 --- /dev/null +++ b/mediagoblin/plugins/piwigo/tools.py @@ -0,0 +1,165 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from collections import namedtuple +import logging + +import six +import lxml.etree as ET +from werkzeug.exceptions import MethodNotAllowed, BadRequest + +from mediagoblin.tools.request import setup_user_in_request +from mediagoblin.tools.response import Response + + +_log = logging.getLogger(__name__) + + +PwgError = namedtuple("PwgError", ["code", "msg"]) + + +class PwgNamedArray(list): + def __init__(self, l, item_name, as_attrib=()): + self.item_name = item_name + self.as_attrib = as_attrib + list.__init__(self, l) + + def fill_element_xml(self, el): + for it in self: + n = ET.SubElement(el, self.item_name) + if isinstance(it, dict): + _fill_element_dict(n, it, self.as_attrib) + else: + _fill_element(n, it) + + +def _fill_element_dict(el, data, as_attr=()): + for k, v in data.iteritems(): + if k in as_attr: + if not isinstance(v, six.string_types): + v = str(v) + el.set(k, v) + else: + n = ET.SubElement(el, k) + _fill_element(n, v) + + +def _fill_element(el, data): + if isinstance(data, bool): + if data: + el.text = "1" + else: + el.text = "0" + elif isinstance(data, six.string_types): + el.text = data + elif isinstance(data, int): + el.text = str(data) + elif isinstance(data, dict): + _fill_element_dict(el, data) + elif isinstance(data, PwgNamedArray): + data.fill_element_xml(el) + else: + _log.warn("Can't convert to xml: %r", data) + + +def response_xml(result): + r = ET.Element("rsp") + r.set("stat", "ok") + status = None + if isinstance(result, PwgError): + r.set("stat", "fail") + err = ET.SubElement(r, "err") + err.set("code", str(result.code)) + err.set("msg", result.msg) + if result.code >= 100 and result.code < 600: + status = result.code + else: + _fill_element(r, result) + return Response(ET.tostring(r, encoding="utf-8", xml_declaration=True), + mimetype='text/xml', status=status) + + +class CmdTable(object): + _cmd_table = {} + + def __init__(self, cmd_name, only_post=False): + assert not cmd_name in self._cmd_table + self.cmd_name = cmd_name + self.only_post = only_post + + def __call__(self, to_be_wrapped): + assert not self.cmd_name in self._cmd_table + self._cmd_table[self.cmd_name] = (to_be_wrapped, self.only_post) + return to_be_wrapped + + @classmethod + def find_func(cls, request): + if request.method == "GET": + cmd_name = request.args.get("method") + else: + cmd_name = request.form.get("method") + entry = cls._cmd_table.get(cmd_name) + if not entry: + return entry + _log.debug("Found method %s", cmd_name) + func, only_post = entry + if only_post and request.method != "POST": + _log.warn("Method %s only allowed for POST", cmd_name) + raise MethodNotAllowed() + return func + + +def check_form(form): + if not form.validate(): + _log.error("form validation failed for form %r", form) + for f in form: + if len(f.errors): + _log.error("Errors for %s: %r", f.name, f.errors) + raise BadRequest() + dump = [] + for f in form: + dump.append("%s=%r" % (f.name, f.data)) + _log.debug("form: %s", " ".join(dump)) + + +class PWGSession(object): + session_manager = None + + def __init__(self, request): + self.request = request + self.in_pwg_session = False + + def __enter__(self): + # Backup old state + self.old_session = self.request.session + self.old_user = self.request.user + # Load piwigo session into state + self.request.session = self.session_manager.load_session_from_cookie( + self.request) + setup_user_in_request(self.request) + self.in_pwg_session = True + return self + + def __exit__(self, *args): + # Restore state + self.request.session = self.old_session + self.request.user = self.old_user + self.in_pwg_session = False + + def save_to_cookie(self, response): + assert self.in_pwg_session + self.session_manager.save_session_to_cookie(self.request.session, + self.request, response) diff --git a/mediagoblin/plugins/piwigo/views.py b/mediagoblin/plugins/piwigo/views.py new file mode 100644 index 00000000..ca723189 --- /dev/null +++ b/mediagoblin/plugins/piwigo/views.py @@ -0,0 +1,249 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import re +from os.path import splitext +import shutil + +from werkzeug.exceptions import MethodNotAllowed, BadRequest, NotImplemented +from werkzeug.wrappers import BaseResponse + +from mediagoblin.meddleware.csrf import csrf_exempt +from mediagoblin.auth.tools import check_login_simple +from mediagoblin.media_types import sniff_media +from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \ + run_process_media, new_upload_entry + +from mediagoblin.user_pages.lib import add_media_to_collection +from mediagoblin.db.models import Collection + +from .tools import CmdTable, response_xml, check_form, \ + PWGSession, PwgNamedArray, PwgError +from .forms import AddSimpleForm, AddForm + + +_log = logging.getLogger(__name__) + + +@CmdTable("pwg.session.login", True) +def pwg_login(request): + username = request.form.get("username") + password = request.form.get("password") + user = check_login_simple(username, password) + if not user: + return PwgError(999, 'Invalid username/password') + request.session["user_id"] = user.id + request.session.save() + return True + + +@CmdTable("pwg.session.logout") +def pwg_logout(request): + _log.info("Logout") + request.session.delete() + return True + + +@CmdTable("pwg.getVersion") +def pwg_getversion(request): + return "2.5.0 (MediaGoblin)" + + +@CmdTable("pwg.session.getStatus") +def pwg_session_getStatus(request): + if request.user: + username = request.user.username + else: + username = "guest" + return {'username': username} + + +@CmdTable("pwg.categories.getList") +def pwg_categories_getList(request): + catlist = [{'id': -29711, + 'uppercats': "-29711", + 'name': "All my images"}] + + if request.user: + collections = Collection.query.filter_by( + get_creator=request.user).order_by(Collection.title) + + for c in collections: + catlist.append({'id': c.id, + 'uppercats': str(c.id), + 'name': c.title, + 'comment': c.description + }) + + return { + 'categories': PwgNamedArray( + catlist, + 'category', + ( + 'id', + 'url', + 'nb_images', + 'total_nb_images', + 'nb_categories', + 'date_last', + 'max_date_last', + ) + ) + } + + +@CmdTable("pwg.images.exist") +def pwg_images_exist(request): + return {} + + +@CmdTable("pwg.images.addSimple", True) +def pwg_images_addSimple(request): + form = AddSimpleForm(request.form) + if not form.validate(): + _log.error("addSimple: form failed") + raise BadRequest() + dump = [] + for f in form: + dump.append("%s=%r" % (f.name, f.data)) + _log.info("addSimple: %r %s %r", request.form, " ".join(dump), + request.files) + + if not check_file_field(request, 'image'): + raise BadRequest() + + filename = request.files['image'].filename + + # Sniff the submitted media to determine which + # media plugin should handle processing + media_type, media_manager = sniff_media( + request.files['image']) + + # create entry and save in database + entry = new_upload_entry(request.user) + entry.media_type = unicode(media_type) + entry.title = ( + unicode(form.name.data) + or unicode(splitext(filename)[0])) + + entry.description = unicode(form.comment.data) + + ''' + # Process the user's folksonomy "tags" + entry.tags = convert_to_tag_list_of_dicts( + form.tags.data) + ''' + + # Generate a slug from the title + entry.generate_slug() + + queue_file = prepare_queue_task(request.app, entry, filename) + + with queue_file: + shutil.copyfileobj(request.files['image'].stream, + queue_file, + length=4 * 1048576) + + # Save now so we have this data before kicking off processing + entry.save() + + # Pass off to processing + # + # (... don't change entry after this point to avoid race + # conditions with changes to the document via processing code) + feed_url = request.urlgen( + 'mediagoblin.user_pages.atom_feed', + qualified=True, user=request.user.username) + run_process_media(entry, feed_url) + + collection_id = form.category.data + if collection_id > 0: + collection = Collection.query.get(collection_id) + if collection is not None and collection.creator == request.user.id: + add_media_to_collection(collection, entry, "") + + return {'image_id': entry.id, 'url': entry.url_for_self(request.urlgen, + qualified=True)} + + +md5sum_matcher = re.compile(r"^[0-9a-fA-F]{32}$") + + +def fetch_md5(request, parm_name, optional_parm=False): + val = request.form.get(parm_name) + if (val is None) and (not optional_parm): + _log.error("Parameter %s missing", parm_name) + raise BadRequest("Parameter %s missing" % parm_name) + if not md5sum_matcher.match(val): + _log.error("Parameter %s=%r has no valid md5 value", parm_name, val) + raise BadRequest("Parameter %s is not md5" % parm_name) + return val + + +@CmdTable("pwg.images.addChunk", True) +def pwg_images_addChunk(request): + o_sum = fetch_md5(request, 'original_sum') + typ = request.form.get('type') + pos = request.form.get('position') + data = request.form.get('data') + + # Validate params: + pos = int(pos) + if not typ in ("file", "thumb"): + _log.error("type %r not allowed for now", typ) + return False + + _log.info("addChunk for %r, type %r, position %d, len: %d", + o_sum, typ, pos, len(data)) + if typ == "thumb": + _log.info("addChunk: Ignoring thumb, because we create our own") + return True + + return True + + +@CmdTable("pwg.images.add", True) +def pwg_images_add(request): + _log.info("add: %r", request.form) + form = AddForm(request.form) + check_form(form) + + return {'image_id': 123456, 'url': ''} + + +@csrf_exempt +def ws_php(request): + if request.method not in ("GET", "POST"): + _log.error("Method %r not supported", request.method) + raise MethodNotAllowed() + + func = CmdTable.find_func(request) + if not func: + _log.warn("wsphp: Unhandled %s %r %r", request.method, + request.args, request.form) + raise NotImplemented() + + with PWGSession(request) as session: + result = func(request) + + if isinstance(result, BaseResponse): + return result + + response = response_xml(result) + session.save_to_cookie(response) + + return response diff --git a/mediagoblin/plugins/raven/README.rst b/mediagoblin/plugins/raven/README.rst new file mode 100644 index 00000000..4006060d --- /dev/null +++ b/mediagoblin/plugins/raven/README.rst @@ -0,0 +1,17 @@ +============== + raven plugin +============== + +.. _raven-setup: + +Warning: this plugin is somewhat experimental. + +Set up the raven plugin +======================= + +1. Add the following to your MediaGoblin .ini file in the ``[plugins]`` section:: + + [[mediagoblin.plugins.raven]] + sentry_dsn = <YOUR SENTRY DSN> + # Logging is very high-volume, set to 0 if you want to turn off logging + setup_logging = 1 diff --git a/mediagoblin/plugins/raven/__init__.py b/mediagoblin/plugins/raven/__init__.py new file mode 100644 index 00000000..8cfaed0a --- /dev/null +++ b/mediagoblin/plugins/raven/__init__.py @@ -0,0 +1,92 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import logging + +from mediagoblin.tools import pluginapi + +_log = logging.getLogger(__name__) + + +def get_client(): + from raven import Client + config = pluginapi.get_config('mediagoblin.plugins.raven') + + sentry_dsn = config.get('sentry_dsn') + + client = None + + if sentry_dsn: + _log.info('Setting up raven from plugin config: {0}'.format( + sentry_dsn)) + client = Client(sentry_dsn) + elif os.environ.get('SENTRY_DSN'): + _log.info('Setting up raven from SENTRY_DSN environment variable: {0}'\ + .format(os.environ.get('SENTRY_DSN'))) + client = Client() # Implicitly looks for SENTRY_DSN + + if not client: + _log.error('Could not set up client, missing sentry DSN') + return None + + return client + + +def setup_celery(): + from raven.contrib.celery import register_signal + + client = get_client() + + register_signal(client) + + +def setup_logging(): + config = pluginapi.get_config('mediagoblin.plugins.raven') + + conf_setup_logging = False + if config.get('setup_logging'): + conf_setup_logging = bool(int(config.get('setup_logging'))) + + if not conf_setup_logging: + return + + from raven.handlers.logging import SentryHandler + from raven.conf import setup_logging + + client = get_client() + + _log.info('Setting up raven logging handler') + + setup_logging(SentryHandler(client)) + + +def wrap_wsgi(app): + from raven.middleware import Sentry + + client = get_client() + + _log.info('Attaching raven middleware...') + + return Sentry(app, client) + + +hooks = { + 'setup': setup_logging, + 'wrap_wsgi': wrap_wsgi, + 'celery_logging_setup': setup_logging, + 'celery_setup': setup_celery, + } diff --git a/mediagoblin/plugins/sampleplugin/README.rst b/mediagoblin/plugins/sampleplugin/README.rst new file mode 100644 index 00000000..73897133 --- /dev/null +++ b/mediagoblin/plugins/sampleplugin/README.rst @@ -0,0 +1,8 @@ +============== + sampleplugin +============== + +This is a sample plugin. It does nothing interesting other than show +one way to structure a MediaGoblin plugin. + +The code for this plugin is in ``mediagoblin/plugins/sampleplugin/``. diff --git a/mediagoblin/plugins/sampleplugin/__init__.py b/mediagoblin/plugins/sampleplugin/__init__.py new file mode 100644 index 00000000..2cd077a2 --- /dev/null +++ b/mediagoblin/plugins/sampleplugin/__init__.py @@ -0,0 +1,42 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import logging + +from mediagoblin.tools.pluginapi import get_config + + +_log = logging.getLogger(__name__) + + +_setup_plugin_called = 0 + +def setup_plugin(): + global _setup_plugin_called + + _log.info('Sample plugin set up!') + config = get_config('mediagoblin.plugins.sampleplugin') + if config: + _log.info('%r' % config) + else: + _log.info('There is no configuration set.') + _setup_plugin_called += 1 + + +hooks = { + 'setup': setup_plugin + } diff --git a/mediagoblin/plugins/trim_whitespace/README.rst b/mediagoblin/plugins/trim_whitespace/README.rst new file mode 100644 index 00000000..b55ce35e --- /dev/null +++ b/mediagoblin/plugins/trim_whitespace/README.rst @@ -0,0 +1,25 @@ +======================= + Trim whitespace plugin +======================= + +Mediagoblin templates are written with 80 char limit for better +readability. However that means that the html output is very verbose +containing LOTS of whitespace. This plugin inserts a Middleware that +filters out whitespace from the returned HTML in the Response() objects. + +Simply enable this plugin by putting it somewhere where python can reach it and put it's path into the [plugins] section of your mediagoblin.ini or mediagoblin_local.ini like for example this: + + [plugins] + [[mediagoblin.plugins.trim_whitespace]] + +There is no further configuration required. If this plugin is enabled, +all text/html documents should not have lots of whitespace in between +elements, although it does a very naive filtering right now (just keep +the first whitespace and delete all subsequent ones). + +Nonetheless, it is a useful plugin that might serve as inspiration for +other plugin writers. + +It was originally conceived by Sebastian Spaeth. It is licensed under +the GNU AGPL v3 (or any later version) license. + diff --git a/mediagoblin/plugins/trim_whitespace/__init__.py b/mediagoblin/plugins/trim_whitespace/__init__.py new file mode 100644 index 00000000..3da1e8b4 --- /dev/null +++ b/mediagoblin/plugins/trim_whitespace/__init__.py @@ -0,0 +1,73 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +from __future__ import unicode_literals +import logging +import re + +from mediagoblin import meddleware + +_log = logging.getLogger(__name__) + +class TrimWhiteSpaceMeddleware(meddleware.BaseMeddleware): + _setup_plugin_called = 0 + RE_MULTI_WHITESPACE = re.compile(b'(\s)\s+', re.M) + + def process_response(self, request, response): + """Perform very naive html tidying by removing multiple whitespaces""" + # werkzeug.BaseResponse has no content_type attr, this comes via + # werkzeug.wrappers.CommonRequestDescriptorsMixin (part of + # wrappers.Response) + if getattr(response ,'content_type', None) != 'text/html': + return + + # This is a tad more complex than needed to be able to handle + # response.data and response.body, depending on whether we have + # a werkzeug Resonse or a webob one. Let's kill webob soon! + if hasattr(response, 'body') and not hasattr(response, 'data'): + # Old-style webob Response object. + # TODO: Remove this once we transition away from webob + resp_attr = 'body' + else: + resp_attr = 'data' + # Don't flatten iterator to list when we fudge the response body + # (see werkzeug.Response documentation) + response.implicit_sequence_conversion = False + + # Set the tidied text. Very naive tidying for now, just strip all + # subsequent whitespaces (this preserves most newlines) + setattr(response, resp_attr, re.sub( + TrimWhiteSpaceMeddleware.RE_MULTI_WHITESPACE, br'\1', + getattr(response, resp_attr))) + + @classmethod + def setup_plugin(cls): + """Set up this meddleware as a plugin during 'setup' hook""" + global _log + if cls._setup_plugin_called: + _log.info('Trim whitespace plugin was already set up.') + return + + _log.debug('Trim whitespace plugin set up.') + cls._setup_plugin_called += 1 + + # Append ourselves to the list of enabled Meddlewares + meddleware.ENABLED_MEDDLEWARE.append( + '{0}:{1}'.format(cls.__module__, cls.__name__)) + + +hooks = { + 'setup': TrimWhiteSpaceMeddleware.setup_plugin + } diff --git a/mediagoblin/processing/__init__.py b/mediagoblin/processing/__init__.py new file mode 100644 index 00000000..f3a85940 --- /dev/null +++ b/mediagoblin/processing/__init__.py @@ -0,0 +1,193 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import os + +from mediagoblin.db.util import atomic_update +from mediagoblin import mg_globals as mgg + +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +_log = logging.getLogger(__name__) + + +class ProgressCallback(object): + def __init__(self, entry): + self.entry = entry + + def __call__(self, progress): + if progress: + self.entry.transcoding_progress = progress + self.entry.save() + + +def create_pub_filepath(entry, filename): + return mgg.public_store.get_unique_filepath( + ['media_entries', + unicode(entry.id), + filename]) + + +class FilenameBuilder(object): + """Easily slice and dice filenames. + + Initialize this class with an original file path, then use the fill() + method to create new filenames based on the original. + + """ + MAX_FILENAME_LENGTH = 255 # VFAT's maximum filename length + + def __init__(self, path): + """Initialize a builder from an original file path.""" + self.dirpath, self.basename = os.path.split(path) + self.basename, self.ext = os.path.splitext(self.basename) + self.ext = self.ext.lower() + + def fill(self, fmtstr): + """Build a new filename based on the original. + + The fmtstr argument can include the following: + {basename} -- the original basename, with the extension removed + {ext} -- the original extension, always lowercase + + If necessary, {basename} will be truncated so the filename does not + exceed this class' MAX_FILENAME_LENGTH in length. + + """ + basename_len = (self.MAX_FILENAME_LENGTH - + len(fmtstr.format(basename='', ext=self.ext))) + return fmtstr.format(basename=self.basename[:basename_len], + ext=self.ext) + + +class ProcessingState(object): + """ + The first and only argument to the "processor" of a media type + + This could be thought of as a "request" to the processor + function. It has the main info for the request (media entry) + and a bunch of tools for the request on it. + It can get more fancy without impacting old media types. + """ + def __init__(self, entry): + self.entry = entry + self.workbench = None + self.queued_filename = None + + def set_workbench(self, wb): + self.workbench = wb + + def get_queued_filename(self): + """ + Get the a filename for the original, on local storage + """ + if self.queued_filename is not None: + return self.queued_filename + queued_filepath = self.entry.queued_media_file + queued_filename = self.workbench.localized_file( + mgg.queue_store, queued_filepath, + 'source') + self.queued_filename = queued_filename + return queued_filename + + def copy_original(self, target_name, keyname=u"original"): + self.store_public(keyname, self.get_queued_filename(), target_name) + + def store_public(self, keyname, local_file, target_name=None): + if target_name is None: + target_name = os.path.basename(local_file) + target_filepath = create_pub_filepath(self.entry, target_name) + if keyname in self.entry.media_files: + _log.warn("store_public: keyname %r already used for file %r, " + "replacing with %r", keyname, + self.entry.media_files[keyname], target_filepath) + mgg.public_store.copy_local_to_storage(local_file, target_filepath) + self.entry.media_files[keyname] = target_filepath + + def delete_queue_file(self): + # Remove queued media file from storage and database. + # queued_filepath is in the task_id directory which should + # be removed too, but fail if the directory is not empty to be on + # the super-safe side. + queued_filepath = self.entry.queued_media_file + mgg.queue_store.delete_file(queued_filepath) # rm file + mgg.queue_store.delete_dir(queued_filepath[:-1]) # rm dir + self.entry.queued_media_file = [] + + +def mark_entry_failed(entry_id, exc): + """ + Mark a media entry as having failed in its conversion. + + Uses the exception that was raised to mark more information. If + the exception is a derivative of BaseProcessingFail then we can + store extra information that can be useful for users telling them + why their media failed to process. + + Args: + - entry_id: The id of the media entry + + """ + # Was this a BaseProcessingFail? In other words, was this a + # type of error that we know how to handle? + if isinstance(exc, BaseProcessingFail): + # Looks like yes, so record information about that failure and any + # metadata the user might have supplied. + atomic_update(mgg.database.MediaEntry, + {'id': entry_id}, + {u'state': u'failed', + u'fail_error': unicode(exc.exception_path), + u'fail_metadata': exc.metadata}) + else: + _log.warn("No idea what happened here, but it failed: %r", exc) + # Looks like no, so just mark it as failed and don't record a + # failure_error (we'll assume it wasn't handled) and don't record + # metadata (in fact overwrite it if somehow it had previous info + # here) + atomic_update(mgg.database.MediaEntry, + {'id': entry_id}, + {u'state': u'failed', + u'fail_error': None, + u'fail_metadata': {}}) + + +class BaseProcessingFail(Exception): + """ + Base exception that all other processing failure messages should + subclass from. + + You shouldn't call this itself; instead you should subclass it + and provid the exception_path and general_message applicable to + this error. + """ + general_message = u'' + + @property + def exception_path(self): + return u"%s:%s" % ( + self.__class__.__module__, self.__class__.__name__) + + def __init__(self, **metadata): + self.metadata = metadata or {} + + +class BadMediaFail(BaseProcessingFail): + """ + Error that should be raised when an inappropriate file was given + for the media type specified. + """ + general_message = _(u'Invalid file given for media type.') diff --git a/mediagoblin/processing/task.py b/mediagoblin/processing/task.py new file mode 100644 index 00000000..9af192ed --- /dev/null +++ b/mediagoblin/processing/task.py @@ -0,0 +1,145 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import urllib +import urllib2 + +from celery import registry, task + +from mediagoblin import mg_globals as mgg +from mediagoblin.db.models import MediaEntry +from . import mark_entry_failed, BaseProcessingFail, ProcessingState +from mediagoblin.tools.processing import json_processing_callback + +_log = logging.getLogger(__name__) +logging.basicConfig() +_log.setLevel(logging.DEBUG) + + +@task.task(default_retry_delay=2 * 60) +def handle_push_urls(feed_url): + """Subtask, notifying the PuSH servers of new content + + Retry 3 times every 2 minutes if run in separate process before failing.""" + if not mgg.app_config["push_urls"]: + return # Nothing to do + _log.debug('Notifying Push servers for feed {0}'.format(feed_url)) + hubparameters = { + 'hub.mode': 'publish', + 'hub.url': feed_url} + hubdata = urllib.urlencode(hubparameters) + hubheaders = { + "Content-type": "application/x-www-form-urlencoded", + "Connection": "close"} + for huburl in mgg.app_config["push_urls"]: + hubrequest = urllib2.Request(huburl, hubdata, hubheaders) + try: + hubresponse = urllib2.urlopen(hubrequest) + except (urllib2.HTTPError, urllib2.URLError) as exc: + # We retry by default 3 times before failing + _log.info("PuSH url %r gave error %r", huburl, exc) + try: + return handle_push_urls.retry(exc=exc, throw=False) + except Exception as e: + # All retries failed, Failure is no tragedy here, probably. + _log.warn('Failed to notify PuSH server for feed {0}. ' + 'Giving up.'.format(feed_url)) + return False + +################################ +# Media processing initial steps +################################ + +class ProcessMedia(task.Task): + """ + Pass this entry off for processing. + """ + def run(self, media_id, feed_url): + """ + Pass the media entry off to the appropriate processing function + (for now just process_image...) + + :param feed_url: The feed URL that the PuSH server needs to be + updated for. + """ + entry = MediaEntry.query.get(media_id) + + # Try to process, and handle expected errors. + try: + entry.state = u'processing' + entry.save() + + _log.debug('Processing {0}'.format(entry)) + + proc_state = ProcessingState(entry) + with mgg.workbench_manager.create() as workbench: + proc_state.set_workbench(workbench) + # run the processing code + entry.media_manager.processor(proc_state) + + # We set the state to processed and save the entry here so there's + # no need to save at the end of the processing stage, probably ;) + entry.state = u'processed' + entry.save() + + # Notify the PuSH servers as async task + if mgg.app_config["push_urls"] and feed_url: + handle_push_urls.subtask().delay(feed_url) + + json_processing_callback(entry) + except BaseProcessingFail as exc: + mark_entry_failed(entry.id, exc) + json_processing_callback(entry) + return + + except ImportError as exc: + _log.error( + 'Entry {0} failed to process due to an import error: {1}'\ + .format( + entry.title, + exc)) + + mark_entry_failed(entry.id, exc) + json_processing_callback(entry) + + except Exception as exc: + _log.error('An unhandled exception was raised while' + + ' processing {0}'.format( + entry)) + + mark_entry_failed(entry.id, exc) + json_processing_callback(entry) + raise + + def on_failure(self, exc, task_id, args, kwargs, einfo): + """ + If the processing failed we should mark that in the database. + + Assuming that the exception raised is a subclass of + BaseProcessingFail, we can use that to get more information + about the failure and store that for conveying information to + users about the failure, etc. + """ + entry_id = args[0] + mark_entry_failed(entry_id, exc) + + entry = mgg.database.MediaEntry.query.filter_by(id=entry_id).first() + json_processing_callback(entry) + +# Register the task +process_media = registry.tasks[ProcessMedia.name] + diff --git a/mediagoblin/routing.py b/mediagoblin/routing.py new file mode 100644 index 00000000..a650f22f --- /dev/null +++ b/mediagoblin/routing.py @@ -0,0 +1,42 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +from mediagoblin.tools.routing import add_route, mount, url_map +from mediagoblin.tools.pluginapi import PluginManager +from mediagoblin.admin.routing import admin_routes +from mediagoblin.auth.routing import auth_routes + + +_log = logging.getLogger(__name__) + + +def get_url_map(): + add_route('index', '/', 'mediagoblin.views:root_view') + mount('/auth', auth_routes) + mount('/a', admin_routes) + + import mediagoblin.submit.routing + import mediagoblin.user_pages.routing + import mediagoblin.edit.routing + import mediagoblin.webfinger.routing + import mediagoblin.listings.routing + + for route in PluginManager().get_routes(): + add_route(*route) + + return url_map diff --git a/mediagoblin/static/css/audio.css b/mediagoblin/static/css/audio.css new file mode 100644 index 00000000..e007a0e1 --- /dev/null +++ b/mediagoblin/static/css/audio.css @@ -0,0 +1,84 @@ +.audio-spectrogram { + position: relative; +} +.playhead { + position: absolute; + top: 0; + left: 0; + background: rgba(134, 212, 177, 0.3); + border-right: thin solid #ffaa00; + height: 100%; + -webkit-transition: width .1s ease-out; + -moz-transition: width .1s ease-out; + transition: width .1s ease-out; +} +.audio-control-play-pause { + position: absolute; + bottom: 0; + left: 5px; + cursor: pointer; + /* background: rgba(0, 0, 0, 0.7); */ + font-size: 40px; + width: 50px; + text-shadow: 0 0 10px black; +} + .audio-control-play-pause.playing { + color: #b71500; + letter-spacing: -17px; + margin-left: -7px; + } + .audio-control-play-pause.paused { + /* Warning: this means the the play button shows! */ + color: rgb(134, 212, 177); + } + +.buffered-indicators { + position: absolute; + bottom: 0; + left: 0; + height: 2px; +} + .buffered-indicators div { + position: absolute; + height: 2px; + left: 0; + background: rgba(134, 177, 212, 1); + + -webkit-transition: left 1s ease-out; + -moz-transition: left 1s ease-out; + transition: left 1s ease-out; + + -webkit-transition: width 1s ease-out; + -moz-transition: width 1s ease-out; + transition: width 1s ease-out; + + cursor: pointer; + } + +.seekbar { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.audio-currentTime { + position: absolute; + bottom: 0; + right: 0; + background: rgba(0, 0, 0, 0.7); +} + +.audio-volume { + position: absolute; + left: 50px; + bottom: 10px; + opacity: 0.3; + -moz-transition: opacity .1s ease-in-out; + -webkit-transition: opacity .1s ease-in-out; + transition: opacity .1s ease-in-out; +} + .audio-spectrogram:hover .audio-volume { + opacity: 0.7; + } diff --git a/mediagoblin/static/css/base.css b/mediagoblin/static/css/base.css new file mode 100644 index 00000000..5b8226e6 --- /dev/null +++ b/mediagoblin/static/css/base.css @@ -0,0 +1,748 @@ +/* @font-face */ + +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 700; + src: local('Lato Bold'), local('Lato-Bold'), url('../fonts/Lato-Bold.ttf') format('truetype'); +} +@font-face { + font-family: 'Lato'; + font-style: italic; + font-weight: 400; + src: local('Lato Italic'), local('Lato-Italic'), url('../fonts/Lato-Italic.ttf') format('truetype'); +} +@font-face { + font-family: 'Lato'; + font-style: italic; + font-weight: 700; + src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url('../fonts/Lato-BoldItalic.ttf') format('truetype'); +} +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 400; + src: local('Lato Regular'), local('Lato-Regular'), url('../fonts/Lato-Regular.ttf') format('truetype'); +} + +body { + background-color: #161616; + color: #C3C3C3; + padding: 0; + margin: 0px; + height: 100%; + font: 16px 'Lato', 'Helvetica Neue', Arial, 'Liberation Sans', FreeSans, sans-serif; +} + +form { + margin: 0px; + padding: 0px; +} + +/* text styles */ + +h1,h2,h3,p { + margin-bottom: 20px; +} + +h1,h2,h3 { + font-weight: bold; +} + +h1 { + margin-top: 15px; + color: #fff; + font-size: 1.875em; +} + +h2 { + font-size: 1.375em; + margin-top: 20px; + color: #fff; +} + +h3 { + border-bottom: 1px solid #333; + font-size: 1.125em; +} + +p { + margin-top: 0px; +} + +a { + color: #86D4B1; +} + +a.highlight { + color: #fff; +} + +em { + font-style: italic; +} + +strong { + font-weight: bold; +} + +ul { + list-style: disc inside; +} + +ol { + list-style: decimal inside; +} + +label { + font-weight: normal; +} + +input, textarea { + font-size:1em; + font-family:'Lato', sans-serif; +} + +/* website structure */ + +.container { + margin: auto; + width: 96%; + max-width: 940px; +} + +header { + width: 100%; + max-width: 940px; + margin-left: auto; + margin-right: auto; + padding: 0; + margin-bottom: 42px; + border-bottom: 1px solid #333; +} + +.header_right { + margin: 8px; + display: inline-block; + float: right; +} + +.header_dropdown { + margin-bottom: 20px; +} + +.header_dropdown li { + margin: 4px 0; + list-style: none; +} + +.header_dropdown p { + margin-top: 12px; + margin-bottom: 10px; +} + +.dropdown_title { + font-size: 20px; +} + +a.logo { + color: #fff; + font-weight: bold; +} + +.logo img { + vertical-align: middle; + margin: 6px 8px 6px 0; +} + +.mediagoblin_content { + width: 100%; + padding-bottom: 74px; +} + +footer { + width: 100%; + height: 30px; + border-top: 1px solid #333; + bottom: 0px; + padding: 8px 0; + text-align: center; + font-size: 0.875em; + clear: both; +} + +.media_pane { + width: 640px; + margin-left: 0px; + margin-right: 10px; + float: left; +} + +.media_sidebar { + width: 280px; + margin-left: 10px; + float: left; +} + +.profile_sidebar { + width: 340px; + margin-right: 10px; + float: left; +} + +.profile_showcase { + width: 580px; + margin-left: 10px; + float: left; +} + +/* common website elements */ + +.button_action, .button_action_highlight, .button_form { + display: inline-block; + color: #c3c3c3; + background-color: #363636; + border: 1px solid; + border-color: #464646 #2B2B2B #252525; + border-radius: 4px; + padding: 3px 8px; + font-size: 16px; + text-decoration: none; + font-style: normal; + font-weight: bold; + cursor: pointer; +} + +.button_action_highlight, .button_form { + background-color: #86D4B1; + border-color: #A2DEC3 #6CAA8E #5C9179; + color: #283F35; +} + +.button_form { + min-width: 99px; + margin: 10px 0px 10px 15px; + text-align: center; + font-family: 'Lato', sans-serif; +} + +.pagination { +text-align: center; +} + +.pagination_arrow { + margin: 5px; +} + +.empty_space { + background-image: url("../images/empty_back.png"); + font-style: italic; + text-align: center; + height: 160px; + padding-top: 70px; +} + +.right_align { + float: right; +} + +.clear { + clear: both; + display: block; + overflow: hidden; + visibility: hidden; + width: 0; + height: 0; +} + +.hidden { + display: none; +} + +.media_sidebar h3 { + font-size: 1em; + margin: 0 0 5px; + border: none; +} + +.media_sidebar p { + margin-left: 8px; +} + +/* forms */ + +.form_box,.form_box_xl { + background-color: #222; + border-top: 6px solid #D49086; + padding: 3% 5%; + display: block; + float: none; + width: 90%; + max-width: 340px; + margin-left: auto; + margin-right: auto; +} + +.form_box_xl { + max-width: 460px; +} + +.edit_box { + border-top: 6px dashed #D49086 +} + +.form_field_input input, .form_field_input textarea { + width: 100%; +} + +.form_field_input { + margin-bottom: 10px; +} + +.form_field_label { + margin-bottom: 4px; +} + +.form_field_label { + font-size:1.125em; +} + +.form_field_description { + font-style: italic; +} + +.form_field_error { + background-color: #87453b; + color: #fff; + border: none; + padding: 9px; + margin-top: 8px; + margin-bottom: 8px; +} + +.form_submit_buttons { + text-align: right; +} + +.subform { + margin: 2em; +} + +#password_boolean { + margin-top: 4px; + width: 20px; +} + +textarea#description, textarea#bio { + resize: vertical; + height: 100px; +} + +.delete { + margin-top: 36px; + display: block; + text-align: center; +} + +/* comments */ + +.comment_wrapper { + margin-top: 20px; + margin-bottom: 20px; +} + +.comment_wrapper p { + margin-bottom: 2px; +} + +.comment_author { + padding-top: 4px; + font-size: 0.9em; +} + +a.comment_authorlink { + text-decoration: none; + padding-right: 5px; + font-weight: bold; + padding-left: 2px; +} + +a.comment_authorlink:hover { + text-decoration: underline; +} + +a.comment_whenlink { + text-decoration: none; +} + +a.comment_whenlink:hover { + text-decoration: underline; +} + +.comment_content { + margin-left: 8px; + margin-top: 8px; +} + +textarea#comment_content { + resize: vertical; + width: 100%; + height: 90px; + border: none; + background-color: #f1f1f1; + padding: 3px; +} + +#form_comment .form_field_input { + padding-right: 6px; +} + +/* media galleries */ + +.media_thumbnail { + float: left; + padding: 0px; + width: 180px; + overflow: hidden; + margin: 0px 3px 10px; + text-align: center; + font-size: 0.875em; + background-color: #222; + border-radius: 0 0 5px 5px; + padding: 0 0 6px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + border-color: #0D0D0D; + border-style: solid; + border-width: 1px 1px 2px; +} + +.media_thumbnail a { + color: #eee; + text-decoration: none; + display: block; +} + +.media_thumbnail a.remove { + color: #86D4B1; + text-decoration: underline; +} + +a.thumb_entry_title { + padding: 8px; +} + +/* For now, this is commented out since our thumbnails are actually 180px high. + * + * .media_thumbnail img { + * max-height: 135px; + * } + */ + +.thumb_entry_last { + margin-right: 0px; +} + +/* media detail */ + +h2.media_title { + margin-bottom: 0px; + display: inline-block; +} + +p.context { + display: inline-block; + padding-top: 4px; +} + +p.media_specs { + font-size: 0.9em; + border-top: 1px solid #222; + padding: 10px 0px; + color: #888; +} + +.no_html5 { + background: black; + color: white; + text-align: center; + height: 160px; + padding: 130px 10px 20px 10px; +} + +a img.media_image { + cursor: -webkit-zoom-in; + cursor: -moz-zoom-in; + cursor: zoom-in; +} + +/* icons */ + +img.media_icon { + margin: 0 4px; + vertical-align: sub; +} + +/* EXIF information */ + +#exif_content h3 { + border-bottom: 1px solid #333; +} + +#exif_camera_information { + margin-bottom: 20px; +} + +#exif_additional_info { + display: none; +} + +#exif_additional_info table { + font-size: 11px; + margin-top: 10px; +} + +#exif_additional_info td { + vertical-align: top; + padding-bottom: 5px; +} + +#exif_content .col1 { + padding-right: 20px; +} + +#exif_additional_info table tr { + margin-bottom: 10px; +} + +/* navigation */ + +.navigation { + float: right; +} + +.navigation_button { + width: 135px; + display: inline-block; + text-align: center; + background-color: #1d1d1d; + border: 1px solid; + border-color: #2c2c2c #232323 #1a1a1a; + border-radius: 4px; + text-decoration: none; + padding: 4px 0 8px; + margin: 0 0 6px; +} + +.navigation_left { + margin-right: 6px; +} + +/* messages */ + +ul.mediagoblin_messages { + list-style: none inside; + color: #f7f7f7; + padding: 0; +} + +.mediagoblin_messages li { + margin: 5px 0; + padding: 8px; + text-align: center; +} + +.message_success { + background-color: #378566; +} + +.message_warning { + background-color: #87453b; +} + +.message_error { + background-color: #87453b; +} + +.message_info { + background-color: #378566; +} + +.message_debug { + background-color: #f7f7f7; + color: #272727; +} + +ul.mediaentry_tags { + list-style-type: none; +} + +ul.mediaentry_tags li { + display: inline; + margin: 0px 5px 0px 0px; + padding: 0px; +} + + +/* media processing panel */ + +table.media_panel { + width: 100%; +} + +table.media_panel th { + font-weight: bold; + padding-bottom: 4px; + text-align: left; +} + + +/* Delete panel */ + +.delete_checkbox_box { + margin-top: 10px; + margin-left: 10px; +} + +/* ASCII art and code */ + +@font-face { + font-family: Inconsolata; + src: local('Inconsolata'), url('../fonts/Inconsolata.otf') format('opentype') +} + +pre, code { + font-family: Inconsolata, monospace; + line-height: 1em; +} + +pre { + overflow: auto; + margin-bottom: 20px; +} + +.comment_wrapper pre { + margin-bottom: 2px; +} + +.ascii-wrapper pre { + /* but it should not affect the ASCII art */ + margin: 0; +} + +/* Media queries and other responsivisivity */ +@media screen and (max-width: 940px) { + .media_pane { + width: 100%; + margin: 0px; + } + + .media_sidebar { + width: 100%; + margin: 0px; + } + + img.media_image { + width: 100%; + display: inline; + } + + .media_thumbnail { + width: 21%; + } + + .profile_sidebar { + width: 100%; + margin: 0px; + } + + .profile_showcase { + width: 100%; + margin: 0px; + } + + .navigation { + float: none; + } + + .navigation_button { + width: 49%; + float: right; + } + + .navigation_left { + margin-right: 0; + float: left; + } + + .navigation { + float: none; + } + + .navigation_button { + width: 49%; + float: right; + padding: 10px 0 14px; + } + + .navigation_left { + margin-right: 0; + float: left; + } + + .button_action, .button_action_highlight, .button_form { + padding: 9px 14px; + } + + header { + text-align: center; + } + + .header_right { + margin-right: 2%; + float: none; + } + + a.logo { + margin-left: 2%; + } +} + +@media screen and (max-width: 570px) { + .media_thumbnail { + width: 29%; + } +} + +@media screen and (max-width: 380px) { + .media_thumbnail { + width: 46%; + } +} + +/* Exif display */ +#exif_content h3 { + border-bottom: 1px solid #333; +} +#exif_camera_information { + margin-bottom: 20px; +} + +#exif_additional_info { + display: none; +} +#exif_additional_info table { + font-size: 11px; + margin-top: 10px; +} +#exif_additional_info td { + vertical-align: top; + padding-bottom: 5px; +} +#exif_content .col1 { + padding-right: 20px; +} +#exif_additional_info table tr { + margin-bottom: 10px; +} diff --git a/mediagoblin/static/css/extlib/reset.css b/mediagoblin/static/css/extlib/reset.css new file mode 120000 index 00000000..6084e137 --- /dev/null +++ b/mediagoblin/static/css/extlib/reset.css @@ -0,0 +1 @@ +../../../../extlib/reset/reset.css
\ No newline at end of file diff --git a/mediagoblin/static/css/vjs-mg-skin.css b/mediagoblin/static/css/vjs-mg-skin.css new file mode 100644 index 00000000..98c4eb95 --- /dev/null +++ b/mediagoblin/static/css/vjs-mg-skin.css @@ -0,0 +1,415 @@ +.video-js { + background-color: #000; position: relative; padding: 0; outline: none; + + /* Start with 10px for base font size so other dimensions can be em based and easily calculable. */ + font-size: 10px; + + width: 650px !important; + height: 366px !important; + + + /* Allow poster to be vertially aligned. */ + vertical-align: middle; + /* display: table-cell; */ /*This works in Safari but not Firefox.*/ +} + +/* Playback technology elements expand to the width/height of the containing div. <video> or <object> */ +.video-js .vjs-tech { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } + +/* Fix for Firefox 9 fullscreen (only if it is enabled). Not needed when checking fullScreenEnabled. */ +.video-js:-moz-full-screen { position: absolute; } + +/* Fullscreen Styles */ +body.vjs-full-window { + padding: 0; margin: 0; + height: 100%; overflow-y: auto; /* Fix for IE6 full-window. http://www.cssplay.co.uk/layouts/fixed.html */ +} +.video-js.vjs-fullscreen { + position: fixed; overflow: hidden; z-index: 1000; left: 0; top: 0; bottom: 0; right: 0; width: 100% !important; height: 100% !important; + _position: absolute; /* IE6 Full-window (underscore hack) */ +} +.video-js:-webkit-full-screen { + width: 100% !important; height: 100% !important; +} + +/* Poster Styles */ +.vjs-poster { + margin: 0 auto; padding: 0; cursor: pointer; + + /* Scale with the size of the player div. Works when poster is vertically shorter, but stretches when it's less wide. */ + position: relative; width: 100%; max-height: 100%; +} + +/* Subtiles Styles */ +.video-js .vjs-subtitles { color: #fff; font-size: 20px; text-align: center; position: absolute; bottom: 40px; left: 0; right: 0; } + +/* Fading sytles, used to fade control bar. */ +.vjs-fade-in { + visibility: visible !important; /* Needed to make sure things hide in older browsers too. */ + opacity: 0.9 !important; + + -webkit-transition: visibility 0s linear 0s, opacity 0.3s linear; + -moz-transition: visibility 0s linear 0s, opacity 0.3s linear; + -ms-transition: visibility 0s linear 0s, opacity 0.3s linear; + -o-transition: visibility 0s linear 0s, opacity 0.3s linear; + transition: visibility 0s linear 0s, opacity 0.3s linear; +} +.vjs-fade-out { + visibility: hidden !important; + opacity: 0 !important; + + -webkit-transition: visibility 0s linear 1.5s,opacity 1.5s linear; + -moz-transition: visibility 0s linear 1.5s,opacity 1.5s linear; + -ms-transition: visibility 0s linear 1.5s,opacity 1.5s linear; + -o-transition: visibility 0s linear 1.5s,opacity 1.5s linear; + transition: visibility 0s linear 1.5s,opacity 1.5s linear; +} + +/* The control bar +---------------------------------------------------------------------------------- */ +.vjs-mg-skin .vjs-controls { + position: absolute; + bottom: 0; /* Distance from the bottom of the box/video. Keep 0. Use height to add more bottom margin. */ + left: 0; right: 0; /* 100% width of div */ + margin: 0; padding: 0; /* Controls are absolutely position, so no padding necessary */ + height: 30px; /* Including any margin you want above or below control items */ + color: #fff; border-top: 1px solid #404040; border-bottom: 1px solid #1f1f1f; + + /* CSS Gradient */ + /* Can use the Ultimate CSS Gradient Generator: http://www.colorzilla.com/gradient-editor/ */ + background: #242424; /* Old browsers */ + background: -moz-linear-gradient(top, #242424 50%, #1f1f1f 50%, #171717 100%); /* FF3.6+ */ + background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(50%,#242424), color-stop(50%,#1f1f1f), color-stop(100%,#171717)); /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* Chrome10+,Safari5.1+ */ + background: -o-linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* Opera11.10+ */ + background: -ms-linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* IE10+ */ + /* Filter was causing a lot of weird issues in IE. Elements would stop showing up, or other styles would break. */ + /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#242424', endColorstr='#171717',GradientType=0 );*/ /* IE6-9 */ + background: linear-gradient(top, #242424 50%,#1f1f1f 50%,#171717 100%); /* W3C */ + + /* Start hidden and with 0 opacity. Opacity is used to fade in modern browsers. */ + /* Can't use display block to hide initially because widths of slider handles aren't calculated and avaialbe for positioning correctly. */ + visibility: hidden; + opacity: 0; +} + +/* General styles for individual controls. */ +.vjs-mg-skin .vjs-control { + position: relative; float: left; + text-align: center; margin: 0; padding: 0; +} + +.vjs-mg-skin .vjs-control:focus { + outline: 0; +} + +/* Hide control text visually, but have it available for screenreaders: h5bp.com/v */ +.vjs-mg-skin .vjs-control-text { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } + + + +/* Play/Pause +-------------------------------------------------------------------------------- */ +.vjs-mg-skin .vjs-play-control { width: 38px; height: 30px; border-right: 1px solid #101010; cursor: pointer !important; border-left: 1px solid #333; border-bottom: 1px solid #1F1F1F; } +/* Play Icon */ +.vjs-mg-skin.vjs-paused .vjs-play-control div { width: 15px; height: 17px; background: url('../images/video-js.png'); margin: 0; margin-left: 13px; margin-top: 7px; } +.vjs-mg-skin.vjs-playing .vjs-play-control div { width: 15px; height: 17px; background: url('../images/video-js.png') -25px 0; margin: 0; margin-left: 13px; margin-top: 7px; } + + +/* Rewind +-------------------------------------------------------------------------------- */ +.vjs-mg-skin .vjs-rewind-control { width: 5em; cursor: pointer !important; } +.vjs-mg-skin .vjs-rewind-control div { width: 19px; height: 16px; background: url('../images/video-js.png'); margin: 0.5em auto 0; } + +/* Volume/Mute +-------------------------------------------------------------------------------- */ +.vjs-mg-skin .vjs-mute-control { width: 38px; height: 30px; border-left: 1px solid #333; cursor: pointer !important; float: right; } +.vjs-mg-skin .vjs-mute-control div { width: 22px; height: 16px; background: url('../images/video-js.png') -75px -25px; margin:0; margin-left: 8px; margin-top: 8px; } +.vjs-mg-skin .vjs-mute-control.vjs-vol-0 div { background: url('../images/video-js.png') 0 -25px; } +.vjs-mg-skin .vjs-mute-control.vjs-vol-1 div { background: url('../images/video-js.png') -25px -25px; } +.vjs-mg-skin .vjs-mute-control.vjs-vol-2 div { background: url('../images/video-js.png') -50px -25px; } + + +.vjs-mg-skin .vjs-volume-control { width: 85px; height: 30px; float: right; border-right: 1px solid #333; } +.vjs-mg-skin .vjs-volume-bar { + position: relative; width: 70px; height: 0.6em; margin:0; margin-left: 2px; margin-top: 11px; cursor: pointer !important; + + /* -moz-border-radius: 0.3em; -webkit-border-radius: 0.3em; border-radius: 0.3em; */ + + background: #666; + background: -moz-linear-gradient(top, #333, #666); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#333), to(#666)); + background: -webkit-linear-gradient(top, #333, #666); + background: -o-linear-gradient(top, #333, #666); + background: -ms-linear-gradient(top, #333, #666); + background: linear-gradient(top, #333, #666); +} + +.video-js:-moz .vjs-volume-bar { margin-top: 12px; } + +.vjs-mg-skin .vjs-volume-level { + position: absolute; top: 0; left: 0; height: 0.6em; + + /* -moz-border-radius: 0.3em; -webkit-border-radius: 0.3em; border-radius: 0.3em; */ + /* CSS Gradient. */ + background: #86D4B1; /* Old browsers */ + background: -moz-linear-gradient(top, #86D4B1 0%, #5d937a 50%, #86D4B1 100%); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#86D4B1), color-stop(50%,#5d937a), color-stop(100%,#86D4B1)); + background: -webkit-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + background: -o-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + background: -ms-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + background: linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + + +} +.vjs-mg-skin .vjs-volume-handle { + position: absolute; top: -4px; width: 14px; height: 14px; left: 0; + background: url('../images/video-js.png') 0 -50px; +} + +.video-js:-moz .vjs-volume-handle { top: -1px;} + + + + +/* Progress +-------------------------------------------------------------------------------- */ +.vjs-mg-skin div.vjs-progress-control { + position: absolute; + top: -15px; + width: 100%; + height: 12px; +} + +/* Box containing play and load progresses. Also acts as seek scrubber. */ +.vjs-mg-skin .vjs-progress-holder { + position: relative; cursor: pointer !important; /*overflow: hidden;*/ + padding: 0; margin: 0; /* Placement within the progress control item */ + height: 12px; + border-top: 1px solid #333; + border-bottom: 1px solid #111; + + +/* -moz-border-radius: 0.6em; -webkit-border-radius: 0.6em; border-radius: 0.6em; */ + + /* CSS Gradient */ + background: #111; + background: -moz-linear-gradient(top, #111, #262626); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#111), to(#262626)); + background: -webkit-linear-gradient(top, #111, #262626); + background: -o-linear-gradient(top, #111, #262626); + background: -ms-linear-gradient(top, #111, #262626); + background: linear-gradient(top, #111, #262626); +} +.vjs-mg-skin .vjs-progress-holder .vjs-play-progress, +.vjs-mg-skin .vjs-progress-holder .vjs-load-progress { /* Progress Bars */ + position: absolute; display: block; height: 12px; margin: 0; padding: 0; + left: 0; top: 0; /*Needed for IE6*/ + /* -moz-border-radius: 0.6em; -webkit-border-radius: 0.6em; border-radius: 0.6em; */ + + /*width: 0;*/ +} + +.vjs-mg-skin .vjs-play-progress { + /* CSS Gradient. */ + background: #86D4B1; /* Old browsers */ + background: -moz-linear-gradient(top, #86D4B1 0%, #5d937a 50%, #86D4B1 100%); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#86D4B1), color-stop(50%,#5d937a), color-stop(100%,#86D4B1)); + background: -webkit-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + background: -o-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + background: -ms-linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + background: linear-gradient(top, #86D4B1 0%,#5d937a 50%,#86D4B1 100%); + + background: #86D4B1; + background: -moz-linear-gradient(top, #5d937a 0%, #5d937a 50%, #86D4B1 50%, #5d937a 100%); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#5d937a), color-stop(50%,#86D4B1), color-stop(50%,#86D4B1), color-stop(100%,#5d937a)); + background: -webkit-linear-gradient(top, #5d937a 0%,#86D4B1 50%,#86D4B1 50%,#5d937a 100%); + background: -o-linear-gradient(top, #5d937a 0%,#86D4B1 50%,#5d937a 50%, 100%); + background: -ms-linear-gradient(top, #5d937a 0%,#86D4B1 50%,#86D4B1 50%,#5d937a 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#86D4B1', endColorstr='#5d937a',GradientType=0 ); + background: linear-gradient(top, #5d937a 0%,#86D4B1 50%,#86D4B1 50%,#5d937a 100%); +} +.vjs-mg-skin .vjs-load-progress { + opacity: 0.8; + + /* CSS Gradient */ + background: #666; + background: -moz-linear-gradient(top, #666, #333); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#666), to(#333)); + background: -webkit-linear-gradient(top, #666, #333); + background: -o-linear-gradient(top, #666, #333); + background: -ms-linear-gradient(top, #666, #333); + background: linear-gradient(top, #666, #333); +} + +.vjs-mg-skin div.vjs-seek-handle { + position: absolute; + width: 16px; height: 16px; /* Match img pixles */ + margin-top: -0.2em; + left: 0; top: 0; /*Needed for IE6*/ + + background: url('../images/video-js.png') 0 -50px; + /* CSS Curved Corners. Needed to make shadows curved. */ + -moz-border-radius: 0.8em; -webkit-border-radius: 0.8em; border-radius: 0.8em; + /* CSS Shadows */ + -webkit-box-shadow: 0 2px 4px 0 #000; -moz-box-shadow: 0 2px 4px 0 #000; box-shadow: 0 2px 4px 0 #000; +} +/* Time Display +-------------------------------------------------------------------------------- */ +.vjs-mg-skin .vjs-time-controls { + height: 18px; width: 45px; + margin-top: 5px; + margin-left: 5px; + font-size: 14px; line-height: 18px; font-weight: normal; font-family: Helvetica, Arial, sans-serif; + border-left: 1px solid #000000; + border-top: 1px solid #000; + border-bottom: 1px solid #333; + border-right: 1px solid #333; + -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; + + /* CSS Gradient */ + background: #111; + background: -moz-linear-gradient(top, #111, #262626); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#111), to(#262626)); + background: -webkit-linear-gradient(top, #111, #262626); + background: -o-linear-gradient(top, #111, #262626); + background: -ms-linear-gradient(top, #111, #262626); + background: linear-gradient(top, #111, #262626); + + +} + +.vjs-mg-skin .vjs-current-time { } + +.vjs-mg-skin .vjs-duration { right: 0; display: none; } +.vjs-mg-skin .vjs-remaining-time { display: block; } + +.vjs-time-divider { } + +.vjs-mg-skin .vjs-time-control { font-size: 12px; line-height: 16px; font-weight: normal; font-family: Helvetica, Arial, sans-serif; } +.vjs-mg-skin .vjs-time-control span { line-height: 25px; /* Centering vertically */ } + +.vjs-mg-skin .vjs-time-divider { display: none; visibility: hidden; } + +/* Fullscreen +-------------------------------------------------------------------------------- */ +.vjs-secondary-controls { float: right; } + +.vjs-mg-skin .vjs-fullscreen-control { height: 30px; width: 38px; cursor: pointer !important; float: right; border-left: 1px solid #111; } +.vjs-mg-skin .vjs-fullscreen-control div { width: 16px; height: 16px; background: url('../images/video-js.png') -50px 0; margin: 0; margin-left: 11px; margin-top: 8px; } + +.video-js.vjs-fullscreen .vjs-fullscreen-control div { background: url('../images/video-js.png') -75px 0; } +.video-js:-webkit-full-screen .vjs-fullscreen-control div { background: url('../images/video-js.png') -75px 0; } + + + +/* Big Play Button (at start) +---------------------------------------------------------*/ +.vjs-mg-skin .vjs-big-play-button { + display: block; /* Start hidden */ z-index: 2; + position: absolute; top: 50%; left: 50%; width: 8.0em; height: 8.0em; margin: -43px 0 0 -43px; text-align: center; vertical-align: center; cursor: pointer !important; + border: 0.3em solid #86D4B1; opacity: 0.95; + -webkit-border-radius: 25px; -moz-border-radius: 25px; border-radius: 25px; + + background: #454545; + background: -moz-linear-gradient(top, #454545 0%, #232323 50%, #161616 50%, #3f3f3f 100%); + background: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%,#454545), color-stop(50%,#232323), color-stop(50%,#161616), color-stop(100%,#3f3f3f)); + background: -webkit-linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%); + background: -o-linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%); + background: -ms-linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#454545', endColorstr='#3f3f3f',GradientType=0 ); + background: linear-gradient(top, #454545 0%,#232323 50%,#161616 50%,#3f3f3f 100%); + + /* CSS Shadows */ + -webkit-box-shadow: 4px 4px 8px #000; -moz-box-shadow: 4px 4px 8px #000; box-shadow: 4px 4px 8px #000; +} + +.vjs-mg-skin div.vjs-big-play-button:hover { + -webkit-box-shadow: 0 0 80px #fff; -moz-box-shadow: 0 0 80px #fff; box-shadow: 0 0 80px #fff; +} + +.vjs-mg-skin div.vjs-big-play-button span { + position: absolute; top: 50%; left: 50%; + display: block; width: 35px; height: 42px; + margin: -20px 0 0 -15px; /* Using negative margin to center image. */ + background: url('../images/video-js.png') -100px 0; +} + +/* Loading Spinner +---------------------------------------------------------*/ +/* CSS Spinners by Kilian Valkhof - http://kilianvalkhof.com/2010/css-xhtml/css3-loading-spinners-without-images/ */ +.vjs-loading-spinner { + display: none; + position: absolute; top: 50%; left: 50%; width: 55px; height: 55px; + margin: -28px 0 0 -28px; + -webkit-animation-name: rotatethis; + -webkit-animation-duration:1s; + -webkit-animation-iteration-count:infinite; + -webkit-animation-timing-function:linear; + -moz-animation-name: rotatethis; + -moz-animation-duration:1s; + -moz-animation-iteration-count:infinite; + -moz-animation-timing-function:linear; +} + +@-webkit-keyframes rotatethis { + 0% {-webkit-transform:scale(0.6) rotate(0deg); } + 12.5% {-webkit-transform:scale(0.6) rotate(0deg); } + 12.51% {-webkit-transform:scale(0.6) rotate(45deg); } + 25% {-webkit-transform:scale(0.6) rotate(45deg); } + 25.01% {-webkit-transform:scale(0.6) rotate(90deg);} + 37.5% {-webkit-transform:scale(0.6) rotate(90deg);} + 37.51% {-webkit-transform:scale(0.6) rotate(135deg);} + 50% {-webkit-transform:scale(0.6) rotate(135deg);} + 50.01% {-webkit-transform:scale(0.6) rotate(180deg);} + 62.5% {-webkit-transform:scale(0.6) rotate(180deg);} + 62.51% {-webkit-transform:scale(0.6) rotate(225deg);} + 75% {-webkit-transform:scale(0.6) rotate(225deg);} + 75.01% {-webkit-transform:scale(0.6) rotate(270deg);} + 87.5% {-webkit-transform:scale(0.6) rotate(270deg);} + 87.51% {-webkit-transform:scale(0.6) rotate(315deg);} + 100% {-webkit-transform:scale(0.6) rotate(315deg);} +} + +@-moz-keyframes rotatethis { + 0% {-moz-transform:scale(0.6) rotate(0deg);} + 12.5% {-moz-transform:scale(0.6) rotate(0deg);} + 12.51% {-moz-transform:scale(0.6) rotate(45deg);} + 25% {-moz-transform:scale(0.6) rotate(45deg);} + 25.01% {-moz-transform:scale(0.6) rotate(90deg);} + 37.5% {-moz-transform:scale(0.6) rotate(90deg);} + 37.51% {-moz-transform:scale(0.6) rotate(135deg);} + 50% {-moz-transform:scale(0.6) rotate(135deg);} + 50.01% {-moz-transform:scale(0.6) rotate(180deg);} + 62.5% {-moz-transform:scale(0.6) rotate(180deg);} + 62.51% {-moz-transform:scale(0.6) rotate(225deg);} + 75% {-moz-transform:scale(0.6) rotate(225deg);} + 75.01% {-moz-transform:scale(0.6) rotate(270deg);} + 87.5% {-moz-transform:scale(0.6) rotate(270deg);} + 87.51% {-moz-transform:scale(0.6) rotate(315deg);} + 100% {-moz-transform:scale(0.6) rotate(315deg);} +} +/* Each circle */ +div.vjs-loading-spinner .ball1 { opacity: 0.12; position:absolute; left: 20px; top: 0px; width: 13px; height: 13px; background: #fff; + border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; } + +div.vjs-loading-spinner .ball2 { opacity: 0.25; position:absolute; left: 34px; top: 6px; width: 13px; height: 13px; background: #fff; + border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; } + +div.vjs-loading-spinner .ball3 { opacity: 0.37; position:absolute; left: 40px; top: 20px; width: 13px; height: 13px; background: #fff; + border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; } + +div.vjs-loading-spinner .ball4 { opacity: 0.50; position:absolute; left: 34px; top: 34px; width: 13px; height: 13px; background: #fff; + border-radius: 10px; -webkit-border-radius: 10px; -moz-border-radius: 15px; border: 1px solid #ccc; } + +div.vjs-loading-spinner .ball5 { opacity: 0.62; position:absolute; left: 20px; top: 40px; width: 13px; height: 13px; background: #fff; + border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; } + +div.vjs-loading-spinner .ball6 { opacity: 0.75; position:absolute; left: 6px; top: 34px; width: 13px; height: 13px; background: #fff; + border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; } + +div.vjs-loading-spinner .ball7 { opacity: 0.87; position:absolute; left: 0px; top: 20px; width: 13px; height: 13px; background: #fff; + border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; } + +div.vjs-loading-spinner .ball8 { opacity: 1.00; position:absolute; left: 6px; top: 6px; width: 13px; height: 13px; background: #fff; + border-radius: 13px; -webkit-border-radius: 13px; -moz-border-radius: 13px; border: 1px solid #ccc; } diff --git a/mediagoblin/static/extlib/leaflet b/mediagoblin/static/extlib/leaflet new file mode 120000 index 00000000..b47e2b1b --- /dev/null +++ b/mediagoblin/static/extlib/leaflet @@ -0,0 +1 @@ +../../../extlib/leaflet/dist/
\ No newline at end of file diff --git a/mediagoblin/static/extlib/pdf.js b/mediagoblin/static/extlib/pdf.js new file mode 120000 index 00000000..f829660a --- /dev/null +++ b/mediagoblin/static/extlib/pdf.js @@ -0,0 +1 @@ +../../../extlib/pdf.js
\ No newline at end of file diff --git a/mediagoblin/static/extlib/video-js b/mediagoblin/static/extlib/video-js new file mode 120000 index 00000000..65652d6e --- /dev/null +++ b/mediagoblin/static/extlib/video-js @@ -0,0 +1 @@ +../../../extlib/video-js/
\ No newline at end of file diff --git a/mediagoblin/static/fonts/Inconsolata.otf b/mediagoblin/static/fonts/Inconsolata.otf new file mode 120000 index 00000000..777be657 --- /dev/null +++ b/mediagoblin/static/fonts/Inconsolata.otf @@ -0,0 +1 @@ +../../../extlib/inconsolata/Inconsolata.otf
\ No newline at end of file diff --git a/mediagoblin/static/fonts/Lato-Bold.ttf b/mediagoblin/static/fonts/Lato-Bold.ttf new file mode 120000 index 00000000..8b747690 --- /dev/null +++ b/mediagoblin/static/fonts/Lato-Bold.ttf @@ -0,0 +1 @@ +../../../extlib/lato/Lato-Bold.ttf
\ No newline at end of file diff --git a/mediagoblin/static/fonts/Lato-BoldItalic.ttf b/mediagoblin/static/fonts/Lato-BoldItalic.ttf new file mode 120000 index 00000000..20886f02 --- /dev/null +++ b/mediagoblin/static/fonts/Lato-BoldItalic.ttf @@ -0,0 +1 @@ +../../../extlib/lato/Lato-BoldItalic.ttf
\ No newline at end of file diff --git a/mediagoblin/static/fonts/Lato-Italic.ttf b/mediagoblin/static/fonts/Lato-Italic.ttf new file mode 120000 index 00000000..3e4ee80c --- /dev/null +++ b/mediagoblin/static/fonts/Lato-Italic.ttf @@ -0,0 +1 @@ +../../../extlib/lato/Lato-Italic.ttf
\ No newline at end of file diff --git a/mediagoblin/static/fonts/Lato-Regular.ttf b/mediagoblin/static/fonts/Lato-Regular.ttf new file mode 120000 index 00000000..ff8e9d2c --- /dev/null +++ b/mediagoblin/static/fonts/Lato-Regular.ttf @@ -0,0 +1 @@ +../../../extlib/lato/Lato-Regular.ttf
\ No newline at end of file diff --git a/mediagoblin/static/images/404.png b/mediagoblin/static/images/404.png Binary files differnew file mode 100644 index 00000000..78d746ba --- /dev/null +++ b/mediagoblin/static/images/404.png diff --git a/mediagoblin/static/images/background.png b/mediagoblin/static/images/background.png Binary files differnew file mode 100644 index 00000000..aa101308 --- /dev/null +++ b/mediagoblin/static/images/background.png diff --git a/mediagoblin/static/images/empty_back.png b/mediagoblin/static/images/empty_back.png Binary files differnew file mode 100644 index 00000000..3522ddd3 --- /dev/null +++ b/mediagoblin/static/images/empty_back.png diff --git a/mediagoblin/static/images/frontpage_image.png b/mediagoblin/static/images/frontpage_image.png Binary files differnew file mode 100644 index 00000000..689eb2c2 --- /dev/null +++ b/mediagoblin/static/images/frontpage_image.png diff --git a/mediagoblin/static/images/goblin.ico b/mediagoblin/static/images/goblin.ico Binary files differnew file mode 100644 index 00000000..ae5a1b12 --- /dev/null +++ b/mediagoblin/static/images/goblin.ico diff --git a/mediagoblin/static/images/goblin.png b/mediagoblin/static/images/goblin.png Binary files differnew file mode 100644 index 00000000..672ed61a --- /dev/null +++ b/mediagoblin/static/images/goblin.png diff --git a/mediagoblin/static/images/icon_comment.png b/mediagoblin/static/images/icon_comment.png Binary files differnew file mode 100644 index 00000000..76860a92 --- /dev/null +++ b/mediagoblin/static/images/icon_comment.png diff --git a/mediagoblin/static/images/icon_feed.png b/mediagoblin/static/images/icon_feed.png Binary files differnew file mode 100644 index 00000000..81889473 --- /dev/null +++ b/mediagoblin/static/images/icon_feed.png diff --git a/mediagoblin/static/images/logo.png b/mediagoblin/static/images/logo.png Binary files differnew file mode 100644 index 00000000..b40e58fb --- /dev/null +++ b/mediagoblin/static/images/logo.png diff --git a/mediagoblin/static/images/logo.svg b/mediagoblin/static/images/logo.svg new file mode 100644 index 00000000..ad750c97 --- /dev/null +++ b/mediagoblin/static/images/logo.svg @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="554" + height="101.99998" + id="svg2" + version="1.1" + inkscape:version="0.48.1 r9760" + sodipodi:docname="logo.svg" + inkscape:export-filename="/home/jef/mediagoblin/user_dev/themes/airy/assets/images/logo.png" + inkscape:export-xdpi="17.889999" + inkscape:export-ydpi="17.889999"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="100.13053" + inkscape:cy="65.605728" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="false" + inkscape:window-width="1680" + inkscape:window-height="992" + inkscape:window-x="0" + inkscape:window-y="26" + inkscape:window-maximized="1" + inkscape:snap-bbox="true" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-midpoints="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:object-paths="true" + inkscape:object-nodes="true" + inkscape:snap-midpoints="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-intersection-paths="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-center="true" + inkscape:snap-page="true" + showguides="false" + inkscape:guide-bbox="true" + inkscape:snap-global="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + showborder="false"> + <sodipodi:guide + orientation="1,0" + position="1062.0155,252.54874" + id="guide3002" /> + <inkscape:grid + type="xygrid" + id="grid4021" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" /> + <sodipodi:guide + orientation="0,1" + position="62.322892,118.88571" + id="guide3814" /> + <sodipodi:guide + orientation="0,1" + position="154.25003,65.249969" + id="guide3816" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="main" + inkscape:groupmode="layer" + id="layer1" + transform="translate(865.93433,-886.66071)"> + <g + id="g3010" + transform="matrix(0.68692139,0,0,0.52224846,-26.609327,197.42947)" /> + <g + id="g3951" + transform="matrix(0.75599155,0,0,0.75599155,-269.59547,339.3242)" /> + <g + transform="matrix(0.75599155,0,0,0.75599155,5.8142558,339.3242)" + id="g3969" /> + <path + id="path3051" + style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#797979;fill-opacity:1;stroke:none;stroke-width:11;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans" + d="m -498.47812,909.8741 c -14.29997,-0.25966 -24.30551,9.34241 -26.50888,22.91161 -2.4313,14.97291 6.8419,28.27685 20.6875,30.5 13.84559,2.22316 27.00531,-7.50109 29.46875,-22.46875 2.41774,-14.68996 -7.14681,-30.64325 -23.64737,-30.94286 z m -1.10263,8.03661 c 11.82552,0.0748 16.8356,12.60864 15.5625,21.78125 -1.4685,10.58046 -9.88714,17.25696 -18.65625,15.6875 -8.7691,-1.56946 -14.66487,-10.77344 -13.125,-21.34375 1.3955,-9.57934 8.39284,-16.17451 16.21875,-16.125 z m 130.09735,-24.04343 c 0,3.62036 -2.93488,6.55525 -6.55524,6.55525 -3.62036,0 -6.55524,-2.93489 -6.55524,-6.55525 0,-3.62036 2.93488,-6.55524 6.55524,-6.55524 3.62036,0 6.55524,2.93488 6.55524,6.55524 z m -11.15801,16.87173 0,51.92717 9.20553,0 0,-51.92717 z m -280.41892,6e-5 0,51.92717 9.20553,0 0,-51.92717 z m 11.15802,-16.87173 c 0,3.62036 -2.93488,6.55525 -6.55524,6.55525 -3.62037,0 -6.55525,-2.93489 -6.55525,-6.55525 0,-3.62036 2.93488,-6.55524 6.55525,-6.55524 3.62036,0 6.55524,2.93488 6.55524,6.55524 z m 91.82766,15.94962 c -17.68301,0 -26.96853,15.66588 -26.96875,26.8125 -3.2e-4,16.07708 10.57093,26.13091 23.6875,26.875 4.79063,0.27178 11.67595,-0.19435 17.96875,-6.34375 0.25,15.44904 -3.39617,22.87085 -13.875,23.25 -7.92125,0.28661 -13.39214,-3.93545 -15.75595,-10.70298 l -7.46875,0 c 1.52404,7.39528 6.81992,18.97887 23.75595,18.64048 16.96269,-0.33892 22.5625,-13.84938 22.5625,-30.6875 l 0,-29.5625 c -0.0225,-6.25597 0.90001,-12.00727 2.71875,-17.34375 l -7.4375,0 c -0.95827,2.12785 -1.63064,4.94081 -2.27681,7.63925 -3.53047,-5.63263 -9.91399,-8.57675 -16.91069,-8.57675 z m 15.09375,27.75 c 0,9.35634 -7.1389,18.15625 -17.21875,18.15625 -10.36378,0 -15.59375,-9.243 -15.59375,-18.28125 0,-7.95083 4.08876,-18.88021 16.15625,-19.65625 7.84943,-0.50479 16.39174,7.25265 16.65625,19.78125 z m -143.1507,-50.84375 0,29.03125 c -3.26199,-4.21342 -10.81819,-6.62398 -16.34375,-6.3125 -17.88151,1.00802 -25.3123,17.2168 -25.3125,27.25 -3.2e-4,16.07708 10.47719,26.7559 23.59375,27.5 5.19493,0.29471 13.81969,-1.57948 19.625,-9.1875 0.60295,2.26134 1.86305,5.37114 3.15625,7.65625 l 7.4375,0 c -2.12044,-5.15404 -2.9375,-10.37795 -2.9375,-18.53125 l 0,-57.40625 z m -16.34375,30.875 c 12.02937,-0.0869 16.24638,9.43483 16.65625,19.59375 0,11.03464 -8.39635,19.0625 -17.21875,19.0625 -9.45765,0 -15.76506,-9.58978 -15.53125,-18.625 0.20843,-8.0541 4.61687,-19.94837 16.09375,-20.03125 z m -54.5723,-7.71875 c -14.69916,0 -25.74033,13.03618 -25.75,27.4375 -0.01,14.78695 10.244,26.125 23.65625,26.125 13.17686,0 17.98713,-4.04194 21.40625,-7.1875 l -4.3125,-5.0625 c -3.0707,2.07296 -7.16015,4.5625 -15.96875,4.5625 -7.45652,0 -14.61337,-5.22342 -15.5625,-14.375 12.17349,2.42928 34.34076,3.97324 38.34375,-8.3125 4.15154,-12.74162 -9.23233,-23.1875 -21.8125,-23.1875 z m -0.1875,7.875 c 7.44103,-0.28295 15.86681,7.08099 13.34375,12.5625 -1.07884,2.34384 -4.36121,3.84759 -11.0625,4.1875 -4.72973,0.2399 -11.67545,0.0602 -18.53125,-1.53125 1.38994,-7.13675 6.98538,-14.86646 16.25,-15.21875 z m 138.94915,-7.84375 c -6.22358,0 -12.35769,2.10385 -17.4375,5.78125 l 3.75,5.65625 c 4.03537,-2.17319 9.28716,-3.79268 14.34375,-3.46875 6.55828,0.42013 14.7598,2.73287 14.7598,14.233 -1.85084,-1.38482 -9.08908,-2.44485 -13.54105,-2.57675 -13.63282,-0.40393 -22.07092,5.70413 -22.86428,15.3125 -1.14325,13.84598 10.26769,18.625 20.33303,18.625 7.39807,5.2e-4 15.35814,-5.72151 17,-8.4375 0.43984,2.49906 2.01961,5.57335 3.125,7.625 l 7.46875,0 c -2.12043,-5.15404 -2.96875,-10.37795 -2.96875,-18.53125 l 0,-9.84375 c 0,-24.39387 -18.81089,-24.375 -23.96875,-24.375 z m 0.78125,27.4375 c 8.05656,-0.0156 14.8125,1.66674 14.8125,4.01961 0,6.72106 -7.55371,13.8524 -16.4375,14.23039 -4.54841,0.19353 -11.57496,-2.09719 -11.48928,-9.59375 0.0808,-7.06964 7.87645,-8.64609 13.11428,-8.65625 z m -223.80458,-27.53125 c -7.78959,0 -12.57316,3.37364 -14.53125,6.625 -0.75834,-1.89688 -1.49654,-3.84633 -2.375,-5.6875 l -7.4375,0 c 2.20887,5.93379 2.73712,12.19296 2.71875,17.3125 l 0,34.59375 9.21875,0 0,-34.78125 c 0,-6.65558 6.44423,-10.26976 12.4375,-10.40625 6.57556,-0.14975 9.05752,3.56709 9.125,8.90625 l 0,36.28125 9.1875,0 0,-34.78125 c 0,-6.65558 6.44424,-10.26976 12.4375,-10.40625 6.57556,-0.14975 9.18251,3.56709 9.25,8.90625 l 0,36.28125 9.21875,0 0,-36.28125 c 0,-11.16352 -8.04836,-16.5625 -18.5,-16.5625 -7.68395,0 -13.41885,3.49982 -15.34375,6.53125 -3.21828,-4.38545 -8.81153,-6.53125 -15.40625,-6.53125 z m 435.58675,-23.11502 0,60.98954 c 0,4.50655 1.82444,11.36243 4.13429,14.95446 l 7.46542,0 c -1.71595,-4.95183 -2.38614,-11.95254 -2.38614,-18.52179 l 0,-57.42221 z m -59.85447,0.0213 0,57.40625 c 0,8.48573 -0.72207,13.98198 -2.5625,18.53125 l 7.46875,0 c 0.49268,-1.18272 1.50645,-4.57275 2.125,-6.375 3.2358,3.63861 9.91843,7.15293 16.6875,7.28125 17.0179,0 26.83165,-13.62855 27.09375,-25.84375 0.32424,-15.11836 -9.23098,-28.09919 -25.40625,-27.96875 -7.10965,0.0573 -12.81683,3.2018 -16.1875,7.1875 l 0,-30.21875 -9.21875,0 z m 25.125,30.96875 c 0.34959,-0.0146 0.70152,-0.008 1.0625,0 6.22899,0.13299 15.65387,5.56214 15.4375,19.65625 -0.16934,11.02718 -8.18402,18.37679 -16.84375,18.34375 -11.39711,-0.0435 -15.84382,-8.59471 -15.84375,-18.8125 0,-7.75719 5.35029,-18.73358 16.1875,-19.1875 z m 107.7562,-7.80926 c -7.78958,0 -13.83625,3.29749 -15.79433,6.54885 -0.67753,-1.63171 -1.5038,-3.80006 -2.38135,-5.68127 l -7.4404,0 c 2.20887,5.93379 2.75814,12.20005 2.73977,17.31959 l 0,34.58661 9.20554,0 0,-34.77561 c 0,-6.65558 7.70716,-9.7658 13.70041,-9.90229 6.57556,-0.14975 10.98867,3.05115 11.05615,8.39031 l 0,36.28759 9.20552,0 0,-36.28759 c 0,-11.16352 -9.83967,-16.48619 -20.29131,-16.48619 z" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/mediagoblin/static/images/media_thumbs/image.png b/mediagoblin/static/images/media_thumbs/image.png Binary files differnew file mode 100644 index 00000000..8437a298 --- /dev/null +++ b/mediagoblin/static/images/media_thumbs/image.png diff --git a/mediagoblin/static/images/media_thumbs/video.jpg b/mediagoblin/static/images/media_thumbs/video.jpg Binary files differnew file mode 100644 index 00000000..841dc796 --- /dev/null +++ b/mediagoblin/static/images/media_thumbs/video.jpg diff --git a/mediagoblin/static/images/video-js.png b/mediagoblin/static/images/video-js.png Binary files differnew file mode 100644 index 00000000..58cd813d --- /dev/null +++ b/mediagoblin/static/images/video-js.png diff --git a/mediagoblin/static/js/audio.js b/mediagoblin/static/js/audio.js new file mode 100644 index 00000000..50d58cd9 --- /dev/null +++ b/mediagoblin/static/js/audio.js @@ -0,0 +1,229 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +var audioPlayer = new Object(); + +(function (audioPlayer) { + audioPlayer.init = function (audioElement) { + audioPlayer.audioElement = audioElement; + + console.log(audioElement); + + attachEvents(); + + $(audioElement).hide(); + }; + + function attachEvents () { + audioPlayer.audioElement.addEventListener( + 'durationchange', audioPlayer.durationChange, true); + audioPlayer.audioElement.addEventListener( + 'timeupdate', audioPlayer.timeUpdate, true); + audioPlayer.audioElement.addEventListener( + 'progress', audioPlayer.onProgress, true); + audioPlayer.audioElement.addEventListener( + 'ended', audioPlayer.onEnded, true); + + $(document).ready( function () { + $('.audio-spectrogram').delegate( + '.seekbar', 'click', audioPlayer.onSeek); + $('.audio-spectrogram').delegate( + '.audio-control-play-pause', 'click', audioPlayer.playPause); + $('.audio-spectrogram').delegate( + '.audio-volume', 'change', audioPlayer.onVolumeChange); + $('.audio-media').delegate( + '.audio-spectrogram', 'attachedControls', + audioPlayer.onControlsAttached); + }); + } + + audioPlayer.onVolumeChange = function(e) { + console.log('volume change', e); + audioPlayer.audioElement.volume = e.target.value; + } + + audioPlayer.onControlsAttached = function(e) { + console.log('Controls attached', e); + $('.audio-spectrogram .audio-volume').val( + Math.round(audioPlayer.audioElement.volume, 2)); + } + + audioPlayer.onProgress = function(e) { + /** + * Handler for file download progress + */ + console.log(e); + + var buffered = audioPlayer.audioElement.buffered; + + ranges = new Array(); + + var indicators = $('.audio-spectrogram .buffered-indicators div'); + + for (var i = 0; i < buffered.length; i++) { + if (!(i in indicators)) { + $('<div style="display: none;"></div>') + .appendTo($('.audio-spectrogram .buffered-indicators')) + .fadeIn(500); + indicators = $('.audio-spectrogram .buffered-indicators div'); + } + var posStart = ((buffered.start(i) / audioPlayer.audioElement.duration) + * audioPlayer.imageElement.width()); + var posStop = ((buffered.end(i) / audioPlayer.audioElement.duration) + * audioPlayer.imageElement.width()); + console.log('indicators', indicators); + + var indicator = $(indicators[i]); + + indicator.css('left', posStart); + indicator.css('width', posStop - posStart); + } + + /* + * Clean up unused indicators + */ + if (indicators.length > buffered.length) { + for (var i = buffered.length; i < indicators.length; i++) { + $(indicators[i]).fadeOut(500, function () { + this.remove(); + }); + } + } + }; + + audioPlayer.onSeek = function (e) { + /** + * Callback handler for seek event, which is a .click() event on the + * .seekbar element + */ + console.log('onSeek', e); + + var im = audioPlayer.imageElement; + var pos = (e.offsetX || e.originalEvent.layerX) / im.width(); + + audioPlayer.audioElement.currentTime = pos * audioPlayer.audioElement.duration; + audioPlayer.audioElement.play(); + audioPlayer.setState(audioPlayer.PLAYING); + }; + + audioPlayer.onEnded = function (e) { + audioPlayer.setState(audioPlayer.PAUSED); + } + + audioPlayer.playPause = function (e) { + console.log('playPause', e); + if (audioPlayer.audioElement.paused) { + audioPlayer.audioElement.play(); + audioPlayer.setState(audioPlayer.PLAYING); + } else { + audioPlayer.audioElement.pause(); + audioPlayer.setState(audioPlayer.PAUSED); + } + }; + + audioPlayer.NULL = null; + audioPlayer.PLAYING = 2; + audioPlayer.PAUSED = 4; + + audioPlayer.state = audioPlayer.NULL; + + audioPlayer.setState = function (state) { + if (state == audioPlayer.state) { + return; + } else { + audioPlayer.state = state; + } + + switch (state) { + case audioPlayer.PLAYING: + $('.audio-spectrogram .audio-control-play-pause') + .removeClass('paused').addClass('playing') + .text('▮▮'); + break; + case audioPlayer.PAUSED: + $('.audio-spectrogram .audio-control-play-pause') + .removeClass('playing').addClass('paused') + .text('▶'); + break; + } + }; + + audioPlayer.durationChange = function () { + // ??? + }; + + audioPlayer.timeUpdate = function () { + /** + * Callback handler for the timeupdate event, responsible for + * updating the playhead + */ + var currentTime = audioPlayer.audioElement.currentTime; + var playhead = audioPlayer.imageElement.parent().find('.playhead'); + playhead.css('width', (currentTime / audioPlayer.audioElement.duration) + * audioPlayer.imageElement.width()); + var time = formatTime(currentTime); + var duration = formatTime(audioPlayer.audioElement.duration); + audioPlayer.imageElement.parent() + .find('.audio-currentTime') + .text(time + '/' + duration); + }; + + function formatTime(seconds) { + /** + * Format a time duration in (hh:)?mm:ss manner + */ + var h = Math.floor(seconds / (60 * 60)); + var m = Math.floor((seconds - h * 60 * 60) / 60); + var s = Math.round(seconds - h * 60 * 60 - m * 60); + return '' + (h ? (h < 10 ? '0' + h : h) + ':' : '') + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s); + } + + audioPlayer.formatTime = formatTime; + + audioPlayer.attachToImage = function (imageElement) { + /** + * Attach the player to an image element + */ + console.log(imageElement); + + var im = $(imageElement); + + audioPlayer.imageElement = im; + + $('<div class="playhead"></div>').appendTo(im.parent()); + $('<div class="buffered-indicators"></div>').appendTo(im.parent()); + $('<div class="seekbar"></div>').appendTo(im.parent()); + $('<div class="audio-control-play-pause paused">▶</div>').appendTo(im.parent()); + $('<div class="audio-currentTime">00:00</div>').appendTo(im.parent()); + $('<input type="range" class="audio-volume"' + +'value="1" min="0" max="1" step="0.001" />').appendTo(im.parent()); + $('.audio-spectrogram').trigger('attachedControls'); + }; +})(audioPlayer); + +$(document).ready(function () { + if (!$('.audio-media').length) { + return; + } + + console.log('Initializing audio player'); + + audioElements = $('.audio-media .audio-player'); + audioPlayer.init(audioElements[0]); + audioPlayer.attachToImage($('.audio-spectrogram img')[0]); +}); diff --git a/mediagoblin/static/js/autofilledin_password.js b/mediagoblin/static/js/autofilledin_password.js new file mode 100644 index 00000000..45e867fe --- /dev/null +++ b/mediagoblin/static/js/autofilledin_password.js @@ -0,0 +1,25 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +$(document).ready(function(){ + $('#forgot_password').click(function(event){ + event.preventDefault(); + window.location.pathname = $(this).attr('href') + '?username=' + + $('#username').val(); + }); +}); diff --git a/mediagoblin/static/js/collection_form_show.js b/mediagoblin/static/js/collection_form_show.js new file mode 100644 index 00000000..03a4906b --- /dev/null +++ b/mediagoblin/static/js/collection_form_show.js @@ -0,0 +1,26 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +$(document).ready(function(){ + $('#new_collection').hide(); + $('#button_addcollection').click(function(){ + $('#new_collection').slideToggle('fast', function(){ + $('#collection_title').focus(); + }); + }); +}); diff --git a/mediagoblin/static/js/comment_show.js b/mediagoblin/static/js/comment_show.js new file mode 100644 index 00000000..c5ccee66 --- /dev/null +++ b/mediagoblin/static/js/comment_show.js @@ -0,0 +1,27 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +$(document).ready(function(){ + $('#form_comment').hide(); + $('#button_addcomment').click(function(){ + $(this).fadeOut('fast'); + $('#form_comment').slideDown(function(){ + $('#comment_content').focus(); + }); + }); +}); diff --git a/mediagoblin/static/js/extlib/html5slider.js b/mediagoblin/static/js/extlib/html5slider.js new file mode 120000 index 00000000..feae2cb8 --- /dev/null +++ b/mediagoblin/static/js/extlib/html5slider.js @@ -0,0 +1 @@ +../../../../extlib/html5slider/html5slider.js
\ No newline at end of file diff --git a/mediagoblin/static/js/extlib/jquery.js b/mediagoblin/static/js/extlib/jquery.js new file mode 120000 index 00000000..d78f5cc3 --- /dev/null +++ b/mediagoblin/static/js/extlib/jquery.js @@ -0,0 +1 @@ +../../../../extlib/jquery/jquery.js
\ No newline at end of file diff --git a/mediagoblin/static/js/extlib/leaflet b/mediagoblin/static/js/extlib/leaflet new file mode 120000 index 00000000..2fc302d7 --- /dev/null +++ b/mediagoblin/static/js/extlib/leaflet @@ -0,0 +1 @@ +../../../../extlib/leaflet/dist/
\ No newline at end of file diff --git a/mediagoblin/static/js/extlib/thingiview.js b/mediagoblin/static/js/extlib/thingiview.js new file mode 120000 index 00000000..b7c842ba --- /dev/null +++ b/mediagoblin/static/js/extlib/thingiview.js @@ -0,0 +1 @@ +../../../../extlib/thingiview.js/
\ No newline at end of file diff --git a/mediagoblin/static/js/geolocation-map.js b/mediagoblin/static/js/geolocation-map.js new file mode 100644 index 00000000..26d94c5d --- /dev/null +++ b/mediagoblin/static/js/geolocation-map.js @@ -0,0 +1,47 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +$(document).ready(function () { + if (!$('#tile-map').length) { + return; + } + console.log('Initializing map'); + + var longitude = Number( + $('#tile-map #gps-longitude').val()); + var latitude = Number( + $('#tile-map #gps-latitude').val()); + + // Get a new map instance attached and element with id="tile-map" + var map = new L.Map('tile-map'); + + var mqtileUrl = 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg'; + var mqtileAttrib = '<a id="osm_license_link">see map license</a>'; + var mqtile = new L.TileLayer( + mqtileUrl, + {maxZoom: 18, + attribution: mqtileAttrib, + subdomains: '1234'}); + + map.attributionControl.setPrefix(''); + var location = new L.LatLng(latitude, longitude); + map.setView(location, 13).addLayer(mqtile); + + var marker = new L.Marker(location); + map.addLayer(marker); +}); diff --git a/mediagoblin/static/js/header_dropdown.js b/mediagoblin/static/js/header_dropdown.js new file mode 100644 index 00000000..1b2fb00f --- /dev/null +++ b/mediagoblin/static/js/header_dropdown.js @@ -0,0 +1,27 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +$(document).ready(function(){ + $(".header_dropdown").hide(); + $(".header_dropdown_up").hide(); + $(".header_dropdown_down,.header_dropdown_up").click(function() { + $(".header_dropdown_down").toggle(); + $(".header_dropdown_up").toggle(); + $(".header_dropdown").slideToggle(); + }); +}); diff --git a/mediagoblin/static/js/keyboard_navigation.js b/mediagoblin/static/js/keyboard_navigation.js new file mode 100644 index 00000000..7401e4d8 --- /dev/null +++ b/mediagoblin/static/js/keyboard_navigation.js @@ -0,0 +1,41 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* It must be wrapped into a function and you also cannot use + * $(':not(textarea, input)') because of some reason. */ + +$(document).ready(function(){ + $('textarea, input').keydown(function(event){ + event.stopPropagation(); + }); +}); + +$(document).keydown(function(event){ + switch(event.which){ + case 37: + if($('a.navigation_left').length) { + window.location = $('a.navigation_left').attr('href'); + } + break; + case 39: + if($('a.navigation_right').length) { + window.location = $('a.navigation_right').attr('href'); + } + break; + } +}); diff --git a/mediagoblin/static/js/show_password.js b/mediagoblin/static/js/show_password.js new file mode 100644 index 00000000..b3fbc862 --- /dev/null +++ b/mediagoblin/static/js/show_password.js @@ -0,0 +1,38 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +$(document).ready(function(){ + //Create a duplicate password field. We could change the input type dynamically, but this angers the IE gods (not just IE6). + $("#password").after('<input type="text" value="" name="password_clear" id="password_clear" /><label><input type="checkbox" id="password_boolean" />Show password</label>'); + $('#password_clear').hide(); + $('#password_boolean').click(function(){ + if($('#password_boolean').prop("checked")) { + $('#password_clear').val($('#password').val()); + $('#password').hide(); + $('#password_clear').show(); + } else { + $('#password').val($('#password_clear').val()); + $('#password_clear').hide(); + $('#password').show(); + }; + }); + $('#password,#password_clear').keyup(function(){ + $('#password').val($(this).val()); + $('#password_clear').val($(this).val()); + }); +}); diff --git a/mediagoblin/storage/__init__.py b/mediagoblin/storage/__init__.py new file mode 100644 index 00000000..bbe134a7 --- /dev/null +++ b/mediagoblin/storage/__init__.py @@ -0,0 +1,264 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import shutil +import uuid + +from werkzeug.utils import secure_filename + +from mediagoblin.tools import common + +######## +# Errors +######## + + +class Error(Exception): + pass + + +class InvalidFilepath(Error): + pass + + +class NoWebServing(Error): + pass + + +class NotImplementedError(Error): + pass + + +############################################### +# Storage interface & basic file implementation +############################################### + +class StorageInterface(object): + """ + Interface for the storage API. + + This interface doesn't actually provide behavior, but it defines + what kind of storage patterns subclasses should provide. + + It is important to note that the storage API idea of a "filepath" + is actually like ['dir1', 'dir2', 'file.jpg'], so keep that in + mind while reading method documentation. + + You should set up your __init__ method with whatever keyword + arguments are appropriate to your storage system, but you should + also passively accept all extraneous keyword arguments like: + + def __init__(self, **kwargs): + pass + + See BasicFileStorage as a simple implementation of the + StorageInterface. + """ + + # Whether this file store is on the local filesystem. + local_storage = False + + def __raise_not_implemented(self): + """ + Raise a warning about some component not implemented by a + subclass of this interface. + """ + raise NotImplementedError( + "This feature not implemented in this storage API implementation.") + + def file_exists(self, filepath): + """ + Return a boolean asserting whether or not file at filepath + exists in our storage system. + + Returns: + True / False depending on whether file exists or not. + """ + # Subclasses should override this method. + self.__raise_not_implemented() + + def get_file(self, filepath, mode='r'): + """ + Return a file-like object for reading/writing from this filepath. + + Should create directories, buckets, whatever, as necessary. + """ + # Subclasses should override this method. + self.__raise_not_implemented() + + def delete_file(self, filepath): + """ + Delete or dereference the file (not directory) at filepath. + """ + # Subclasses should override this method. + self.__raise_not_implemented() + + def delete_dir(self, dirpath, recursive=False): + """Delete the directory at dirpath + + :param recursive: Usually, a directory must not contain any + files for the delete to succeed. If True, containing files + and subdirectories within dirpath will be recursively + deleted. + + :returns: True in case of success, False otherwise. + """ + # Subclasses should override this method. + self.__raise_not_implemented() + + def file_url(self, filepath): + """ + Get the URL for this file. This assumes our storage has been + mounted with some kind of URL which makes this possible. + """ + # Subclasses should override this method. + self.__raise_not_implemented() + + def get_unique_filepath(self, filepath): + """ + If a filename at filepath already exists, generate a new name. + + Eg, if the filename doesn't exist: + >>> storage_handler.get_unique_filename(['dir1', 'dir2', 'fname.jpg']) + [u'dir1', u'dir2', u'fname.jpg'] + + But if a file does exist, let's get one back with at uuid tacked on: + >>> storage_handler.get_unique_filename(['dir1', 'dir2', 'fname.jpg']) + [u'dir1', u'dir2', u'd02c3571-dd62-4479-9d62-9e3012dada29-fname.jpg'] + """ + # Make sure we have a clean filepath to start with, since + # we'll be possibly tacking on stuff to the filename. + filepath = clean_listy_filepath(filepath) + + if self.file_exists(filepath): + return filepath[:-1] + ["%s-%s" % (uuid.uuid4(), filepath[-1])] + else: + return filepath + + def get_local_path(self, filepath): + """ + If this is a local_storage implementation, give us a link to + the local filesystem reference to this file. + + >>> storage_handler.get_local_path(['foo', 'bar', 'baz.jpg']) + u'/path/to/mounting/foo/bar/baz.jpg' + """ + # Subclasses should override this method, if applicable. + self.__raise_not_implemented() + + def copy_locally(self, filepath, dest_path): + """ + Copy this file locally. + + A basic working method for this is provided that should + function both for local_storage systems and remote storge + systems, but if more efficient systems for copying locally + apply to your system, override this method with something more + appropriate. + """ + if self.local_storage: + # Note: this will copy in small chunks + shutil.copy(self.get_local_path(filepath), dest_path) + else: + with self.get_file(filepath, 'rb') as source_file: + with file(dest_path, 'wb') as dest_file: + # Copy from remote storage in 4M chunks + shutil.copyfileobj(source_file, dest_file, length=4*1048576) + + def copy_local_to_storage(self, filename, filepath): + """ + Copy this file from locally to the storage system. + + This is kind of the opposite of copy_locally. It's likely you + could override this method with something more appropriate to + your storage system. + """ + with self.get_file(filepath, 'wb') as dest_file: + with file(filename, 'rb') as source_file: + # Copy to storage system in 4M chunks + shutil.copyfileobj(source_file, dest_file, length=4*1048576) + + +########### +# Utilities +########### + +def clean_listy_filepath(listy_filepath): + """ + Take a listy filepath (like ['dir1', 'dir2', 'filename.jpg']) and + clean out any nastiness from it. + + + >>> clean_listy_filepath([u'/dir1/', u'foo/../nasty', u'linooks.jpg']) + [u'dir1', u'foo_.._nasty', u'linooks.jpg'] + + Args: + - listy_filepath: a list of filepath components, mediagoblin + storage API style. + + Returns: + A cleaned list of unicode objects. + """ + cleaned_filepath = [ + unicode(secure_filename(filepath)) + for filepath in listy_filepath] + + if u'' in cleaned_filepath: + raise InvalidFilepath( + "A filename component could not be resolved into a usable name.") + + return cleaned_filepath + + +def storage_system_from_config(config_section): + """ + Utility for setting up a storage system from a config section. + + Note that a special argument may be passed in to + the config_section which is "storage_class" which will provide an + import path to a storage system. This defaults to + "mediagoblin.storage:BasicFileStorage" if otherwise undefined. + + Arguments: + - config_section: dictionary of config parameters + + Returns: + An instantiated storage system. + + Example: + storage_system_from_config( + {'base_url': '/media/', + 'base_dir': '/var/whatever/media/'}) + + Will return: + BasicFileStorage( + base_url='/media/', + base_dir='/var/whatever/media') + """ + # This construct is needed, because dict(config) does + # not replace the variables in the config items. + config_params = dict(config_section.iteritems()) + + if 'storage_class' in config_params: + storage_class = config_params['storage_class'] + config_params.pop('storage_class') + else: + storage_class = 'mediagoblin.storage.filestorage:BasicFileStorage' + + storage_class = common.import_component(storage_class) + return storage_class(**config_params) + +import filestorage diff --git a/mediagoblin/storage/cloudfiles.py b/mediagoblin/storage/cloudfiles.py new file mode 100644 index 00000000..250f06d4 --- /dev/null +++ b/mediagoblin/storage/cloudfiles.py @@ -0,0 +1,246 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +''' +Make it so that ``import cloudfiles`` does not pick THIS file, but the +python-cloudfiles one. + +http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports +''' +from __future__ import absolute_import + +from mediagoblin.storage import StorageInterface, clean_listy_filepath + +import cloudfiles +import mimetypes +import logging + +_log = logging.getLogger(__name__) + + +class CloudFilesStorage(StorageInterface): + ''' + OpenStack/Rackspace Cloud's Swift/CloudFiles support + ''' + + local_storage = False + + def __init__(self, **kwargs): + self.param_container = kwargs.get('cloudfiles_container') + self.param_user = kwargs.get('cloudfiles_user') + self.param_api_key = kwargs.get('cloudfiles_api_key') + self.param_host = kwargs.get('cloudfiles_host') + self.param_use_servicenet = kwargs.get('cloudfiles_use_servicenet') + + # the Mime Type webm doesn't exists, let's add it + mimetypes.add_type("video/webm", "webm") + + if not self.param_host: + _log.info('No CloudFiles host URL specified, ' + 'defaulting to Rackspace US') + + self.connection = cloudfiles.get_connection( + username=self.param_user, + api_key=self.param_api_key, + servicenet=True if self.param_use_servicenet == 'true' or \ + self.param_use_servicenet == True else False) + + _log.debug('Connected to {0} (auth: {1})'.format( + self.connection.connection.host, + self.connection.auth.host)) + + if not self.param_container == \ + self.connection.get_container(self.param_container): + self.container = self.connection.create_container( + self.param_container) + self.container.make_public( + ttl=60 * 60 * 2) + else: + self.container = self.connection.get_container( + self.param_container) + + _log.debug('Container: {0}'.format( + self.container.name)) + + self.container_uri = self.container.public_ssl_uri() + + def _resolve_filepath(self, filepath): + return '/'.join( + clean_listy_filepath(filepath)) + + def file_exists(self, filepath): + try: + self.container.get_object(self._resolve_filepath(filepath)) + return True + except cloudfiles.errors.NoSuchObject: + return False + + def get_file(self, filepath, *args, **kwargs): + """ + - Doesn't care about the "mode" argument. + """ + try: + obj = self.container.get_object( + self._resolve_filepath(filepath)) + except cloudfiles.errors.NoSuchObject: + obj = self.container.create_object( + self._resolve_filepath(filepath)) + + # Detect the mimetype ourselves, since some extensions (webm) + # may not be universally accepted as video/webm + mimetype = mimetypes.guess_type( + filepath[-1]) + + if mimetype[0]: + # Set the mimetype on the CloudFiles object + obj.content_type = mimetype[0] + obj.metadata = {'mime-type': mimetype[0]} + else: + obj.content_type = 'application/octet-stream' + obj.metadata = {'mime-type': 'application/octet-stream'} + + return CloudFilesStorageObjectWrapper(obj, *args, **kwargs) + + def delete_file(self, filepath): + # TODO: Also delete unused directories if empty (safely, with + # checks to avoid race conditions). + try: + self.container.delete_object( + self._resolve_filepath(filepath)) + except cloudfiles.container.ResponseError: + pass + finally: + pass + + def file_url(self, filepath): + return '/'.join([ + self.container_uri, + self._resolve_filepath(filepath)]) + + + def copy_locally(self, filepath, dest_path): + """ + Copy this file locally. + + A basic working method for this is provided that should + function both for local_storage systems and remote storge + systems, but if more efficient systems for copying locally + apply to your system, override this method with something more + appropriate. + """ + # Override this method, using the "stream" iterator for efficient streaming + with self.get_file(filepath, 'rb') as source_file: + with file(dest_path, 'wb') as dest_file: + for data in source_file: + dest_file.write(data) + + def copy_local_to_storage(self, filename, filepath): + """ + Copy this file from locally to the storage system. + + This is kind of the opposite of copy_locally. It's likely you + could override this method with something more appropriate to + your storage system. + """ + # It seems that (our implementation of) cloudfiles.write() takes + # all existing data and appends write(data) to it, sending the + # full monty over the wire everytime. This would of course + # absolutely kill chunked writes with some O(1^n) performance + # and bandwidth usage. So, override this method and use the + # Cloudfile's "send" interface instead. + # TODO: Fixing write() still seems worthwhile though. + _log.debug('Sending {0} to cloudfiles...'.format(filepath)) + with self.get_file(filepath, 'wb') as dest_file: + with file(filename, 'rb') as source_file: + # Copy to storage system in 4096 byte chunks + dest_file.send(source_file) + +class CloudFilesStorageObjectWrapper(): + """ + Wrapper for python-cloudfiles's cloudfiles.storage_object.Object + used to circumvent the mystic `medium.jpg` corruption issue, where + we had both python-cloudfiles and PIL doing buffering on both + ends and causing breakage. + + This wrapper currently meets mediagoblin's needs for a public_store + file-like object. + """ + def __init__(self, storage_object, *args, **kwargs): + self.storage_object = storage_object + + def read(self, *args, **kwargs): + _log.debug('Reading {0}'.format( + self.storage_object.name)) + return self.storage_object.read(*args, **kwargs) + + def write(self, data, *args, **kwargs): + """ + write data to the cloudfiles storage object + + The original motivation for this wrapper is to ensure + that buffered writing to a cloudfiles storage object does not overwrite + any preexisting data. + + Currently this method does not support any write modes except "append". + However if we should need it it would be easy implement. + """ + _log.warn( + '{0}.write() has bad performance! Use .send instead for now'\ + .format(self.__class__.__name__)) + + if self.storage_object.size and type(data) == str: + _log.debug('{0} is > 0 in size, appending data'.format( + self.storage_object.name)) + data = self.read() + data + + _log.debug('Writing {0}'.format( + self.storage_object.name)) + self.storage_object.write(data, *args, **kwargs) + + def send(self, *args, **kw): + self.storage_object.send(*args, **kw) + + def close(self): + """ + Not sure we need anything here. + """ + pass + + def __enter__(self): + """ + Context Manager API implementation + http://docs.python.org/library/stdtypes.html#context-manager-types + """ + return self + + def __exit__(self, *exc_info): + """ + Context Manger API implementation + see self.__enter__() + """ + self.close() + + + def __iter__(self, **kwargs): + """Make CloudFile an iterator, yielding 8192 bytes by default + + This returns a generator object that can be used to getting the + object's content in a memory efficient way. + + Warning: The HTTP response is only complete after this generator + has raised a StopIteration. No other methods can be called until + this has occurred.""" + return self.storage_object.stream(**kwargs) diff --git a/mediagoblin/storage/filestorage.py b/mediagoblin/storage/filestorage.py new file mode 100644 index 00000000..3d6e0753 --- /dev/null +++ b/mediagoblin/storage/filestorage.py @@ -0,0 +1,113 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.storage import ( + StorageInterface, + clean_listy_filepath, + NoWebServing) + +import os +import shutil +import urlparse + + +class BasicFileStorage(StorageInterface): + """ + Basic local filesystem implementation of storage API + """ + + local_storage = True + + def __init__(self, base_dir, base_url=None, **kwargs): + """ + Keyword arguments: + - base_dir: Base directory things will be served out of. MUST + be an absolute path. + - base_url: URL files will be served from + """ + self.base_dir = base_dir + self.base_url = base_url + + def _resolve_filepath(self, filepath): + """ + Transform the given filepath into a local filesystem filepath. + """ + return os.path.join( + self.base_dir, *clean_listy_filepath(filepath)) + + def file_exists(self, filepath): + return os.path.exists(self._resolve_filepath(filepath)) + + def get_file(self, filepath, mode='r'): + # Make directories if necessary + if len(filepath) > 1: + directory = self._resolve_filepath(filepath[:-1]) + if not os.path.exists(directory): + os.makedirs(directory) + + # Grab and return the file in the mode specified + return open(self._resolve_filepath(filepath), mode) + + def delete_file(self, filepath): + """Delete file at filepath + + Raises OSError in case filepath is a directory.""" + #TODO: log error + os.remove(self._resolve_filepath(filepath)) + + def delete_dir(self, dirpath, recursive=False): + """returns True on succes, False on failure""" + + dirpath = self._resolve_filepath(dirpath) + + # Shortcut the default and simple case of nonempty=F, recursive=F + if recursive: + try: + shutil.rmtree(dirpath) + except OSError as e: + #TODO: log something here + return False + else: # recursively delete everything + try: + os.rmdir(dirpath) + except OSError as e: + #TODO: log something here + return False + return True + + def file_url(self, filepath): + if not self.base_url: + raise NoWebServing( + "base_url not set, cannot provide file urls") + + return urlparse.urljoin( + self.base_url, + '/'.join(clean_listy_filepath(filepath))) + + def get_local_path(self, filepath): + return self._resolve_filepath(filepath) + + def copy_local_to_storage(self, filename, filepath): + """ + Copy this file from locally to the storage system. + """ + # Make directories if necessary + if len(filepath) > 1: + directory = self._resolve_filepath(filepath[:-1]) + if not os.path.exists(directory): + os.makedirs(directory) + # This uses chunked copying of 16kb buffers (Py2.7): + shutil.copy(filename, self.get_local_path(filepath)) diff --git a/mediagoblin/storage/mountstorage.py b/mediagoblin/storage/mountstorage.py new file mode 100644 index 00000000..dffc619b --- /dev/null +++ b/mediagoblin/storage/mountstorage.py @@ -0,0 +1,160 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.storage import StorageInterface, clean_listy_filepath + + +class MountError(Exception): + pass + + +class MountStorage(StorageInterface): + """ + Experimental "Mount" virtual Storage Interface + + This isn't an interface to some real storage, instead it's a + redirecting interface, that redirects requests to other + "StorageInterface"s. + + For example, say you have the paths: + + 1. ['user_data', 'cwebber', 'avatar.jpg'] + 2. ['user_data', 'elrond', 'avatar.jpg'] + 3. ['media_entries', '34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg'] + + You could mount media_entries under CloudFileStorage and user_data + under BasicFileStorage. Then 1 would be passed to + BasicFileStorage under the path ['cwebber', 'avatar.jpg'] and 3 + would be passed to CloudFileStorage under + ['34352f304c3f4d0ad8ad0f043522b6f2', 'thumb.jpg']. + + In other words, this is kind of like mounting /home/ and /etc/ + under different filesystems on your operating system... but with + mediagoblin filestorages :) + + To set this up, you currently need to call the mount() method with + the target path and a backend, that shall be available under that + target path. You have to mount things in a sensible order, + especially you can't mount ["a", "b"] before ["a"]. + """ + def __init__(self, **kwargs): + self.mounttab = {} + + def mount(self, dirpath, backend): + """ + Mount a new backend under dirpath + """ + new_ent = clean_listy_filepath(dirpath) + + print "Mounting:", repr(new_ent) + already, rem_1, table, rem_2 = self._resolve_to_backend(new_ent, True) + print "===", repr(already), repr(rem_1), repr(rem_2), len(table) + + assert (len(rem_2) > 0) or (None not in table), \ + "That path is already mounted" + assert (len(rem_2) > 0) or (len(table) == 0), \ + "A longer path is already mounted here" + + for part in rem_2: + table[part] = {} + table = table[part] + table[None] = backend + + def _resolve_to_backend(self, filepath, extra_info=False): + """ + extra_info = True is for internal use! + + Normally, returns the backend and the filepath inside that backend. + + With extra_info = True it returns the last directory node and the + remaining filepath from there in addition. + """ + table = self.mounttab + filepath = filepath[:] + res_fp = None + while True: + new_be = table.get(None) + if (new_be is not None) or res_fp is None: + res_be = new_be + res_fp = filepath[:] + res_extra = (table, filepath[:]) + # print "... New res: %r, %r, %r" % (res_be, res_fp, res_extra) + if len(filepath) == 0: + break + query = filepath.pop(0) + entry = table.get(query) + if entry is not None: + table = entry + res_extra = (table, filepath[:]) + else: + break + if extra_info: + return (res_be, res_fp) + res_extra + else: + return (res_be, res_fp) + + def resolve_to_backend(self, filepath): + backend, filepath = self._resolve_to_backend(filepath) + if backend is None: + raise MountError("Path not mounted") + return backend, filepath + + def __repr__(self, table=None, indent=[]): + res = [] + if table is None: + res.append("MountStorage<") + table = self.mounttab + v = table.get(None) + if v: + res.append(" " * len(indent) + repr(indent) + ": " + repr(v)) + for k, v in table.iteritems(): + if k == None: + continue + res.append(" " * len(indent) + repr(k) + ":") + res += self.__repr__(v, indent + [k]) + if table is self.mounttab: + res.append(">") + return "\n".join(res) + else: + return res + + def file_exists(self, filepath): + backend, filepath = self.resolve_to_backend(filepath) + return backend.file_exists(filepath) + + def get_file(self, filepath, mode='r'): + backend, filepath = self.resolve_to_backend(filepath) + return backend.get_file(filepath, mode) + + def delete_file(self, filepath): + backend, filepath = self.resolve_to_backend(filepath) + return backend.delete_file(filepath) + + def file_url(self, filepath): + backend, filepath = self.resolve_to_backend(filepath) + return backend.file_url(filepath) + + def get_local_path(self, filepath): + backend, filepath = self.resolve_to_backend(filepath) + return backend.get_local_path(filepath) + + def copy_locally(self, filepath, dest_path): + """ + Need to override copy_locally, because the local_storage + attribute is not correct. + """ + backend, filepath = self.resolve_to_backend(filepath) + backend.copy_locally(filepath, dest_path) diff --git a/mediagoblin/submit/__init__.py b/mediagoblin/submit/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/submit/__init__.py @@ -0,0 +1,15 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/mediagoblin/submit/forms.py b/mediagoblin/submit/forms.py new file mode 100644 index 00000000..e9bd93fd --- /dev/null +++ b/mediagoblin/submit/forms.py @@ -0,0 +1,53 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import wtforms + +from mediagoblin.tools.text import tag_length_validator +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ +from mediagoblin.tools.licenses import licenses_as_choices + + +class SubmitStartForm(wtforms.Form): + file = wtforms.FileField(_('File')) + title = wtforms.TextField( + _('Title'), + [wtforms.validators.Length(min=0, max=500)]) + description = wtforms.TextAreaField( + _('Description of this work'), + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) + tags = wtforms.TextField( + _('Tags'), + [tag_length_validator], + description=_( + "Separate tags by commas.")) + license = wtforms.SelectField( + _('License'), + [wtforms.validators.Optional(),], + choices=licenses_as_choices()) + +class AddCollectionForm(wtforms.Form): + title = wtforms.TextField( + _('Title'), + [wtforms.validators.Length(min=0, max=500), wtforms.validators.Required()]) + description = wtforms.TextAreaField( + _('Description of this collection'), + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) diff --git a/mediagoblin/submit/lib.py b/mediagoblin/submit/lib.py new file mode 100644 index 00000000..7e85696b --- /dev/null +++ b/mediagoblin/submit/lib.py @@ -0,0 +1,102 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import uuid +from werkzeug.utils import secure_filename +from werkzeug.datastructures import FileStorage + +from mediagoblin.db.models import MediaEntry +from mediagoblin.processing import mark_entry_failed +from mediagoblin.processing.task import process_media + + +_log = logging.getLogger(__name__) + + +def check_file_field(request, field_name): + """Check if a file field meets minimal criteria""" + retval = (field_name in request.files + and isinstance(request.files[field_name], FileStorage) + and request.files[field_name].stream) + if not retval: + _log.debug("Form did not contain proper file field %s", field_name) + return retval + + +def new_upload_entry(user): + """ + Create a new MediaEntry for uploading + """ + entry = MediaEntry() + entry.uploader = user.id + entry.license = user.license_preference + return entry + + +def prepare_queue_task(app, entry, filename): + """ + Prepare a MediaEntry for the processing queue and get a queue file + """ + # We generate this ourselves so we know what the task id is for + # retrieval later. + + # (If we got it off the task's auto-generation, there'd be + # a risk of a race condition when we'd save after sending + # off the task) + task_id = unicode(uuid.uuid4()) + entry.queued_task_id = task_id + + # Now store generate the queueing related filename + queue_filepath = app.queue_store.get_unique_filepath( + ['media_entries', + task_id, + secure_filename(filename)]) + + # queue appropriately + queue_file = app.queue_store.get_file( + queue_filepath, 'wb') + + # Add queued filename to the entry + entry.queued_media_file = queue_filepath + + return queue_file + + +def run_process_media(entry, feed_url=None): + """Process the media asynchronously + + :param entry: MediaEntry() instance to be processed. + :param feed_url: A string indicating the feed_url that the PuSH servers + should be notified of. This will be sth like: `request.urlgen( + 'mediagoblin.user_pages.atom_feed',qualified=True, + user=request.user.username)`""" + try: + process_media.apply_async( + [entry.id, feed_url], {}, + task_id=entry.queued_task_id) + except BaseException as exc: + # The purpose of this section is because when running in "lazy" + # or always-eager-with-exceptions-propagated celery mode that + # the failure handling won't happen on Celery end. Since we + # expect a lot of users to run things in this way we have to + # capture stuff here. + # + # ... not completely the diaper pattern because the + # exception is re-raised :) + mark_entry_failed(entry.id, exc) + # re-raise the exception + raise diff --git a/mediagoblin/submit/routing.py b/mediagoblin/submit/routing.py new file mode 100644 index 00000000..085344fd --- /dev/null +++ b/mediagoblin/submit/routing.py @@ -0,0 +1,21 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.routing import add_route + +add_route('mediagoblin.submit.start', + '/submit/', 'mediagoblin.submit.views:submit_start') +add_route('mediagoblin.submit.collection', '/submit/collection', 'mediagoblin.submit.views:add_collection') diff --git a/mediagoblin/submit/views.py b/mediagoblin/submit/views.py new file mode 100644 index 00000000..a70c89b4 --- /dev/null +++ b/mediagoblin/submit/views.py @@ -0,0 +1,153 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin import messages +import mediagoblin.mg_globals as mg_globals +from os.path import splitext + +import logging + +_log = logging.getLogger(__name__) + + +from mediagoblin.tools.text import convert_to_tag_list_of_dicts +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.tools.response import render_to_response, redirect +from mediagoblin.decorators import require_active_login +from mediagoblin.submit import forms as submit_forms +from mediagoblin.messages import add_message, SUCCESS +from mediagoblin.media_types import sniff_media, \ + InvalidFileType, FileTypeNotSupported +from mediagoblin.submit.lib import check_file_field, prepare_queue_task, \ + run_process_media, new_upload_entry + + +@require_active_login +def submit_start(request): + """ + First view for submitting a file. + """ + submit_form = submit_forms.SubmitStartForm(request.form, + license=request.user.license_preference) + + if request.method == 'POST' and submit_form.validate(): + if not check_file_field(request, 'file'): + submit_form.file.errors.append( + _(u'You must provide a file.')) + else: + try: + filename = request.files['file'].filename + + # Sniff the submitted media to determine which + # media plugin should handle processing + media_type, media_manager = sniff_media( + request.files['file']) + + # create entry and save in database + entry = new_upload_entry(request.user) + entry.media_type = unicode(media_type) + entry.title = ( + unicode(submit_form.title.data) + or unicode(splitext(filename)[0])) + + entry.description = unicode(submit_form.description.data) + + entry.license = unicode(submit_form.license.data) or None + + # Process the user's folksonomy "tags" + entry.tags = convert_to_tag_list_of_dicts( + submit_form.tags.data) + + # Generate a slug from the title + entry.generate_slug() + + queue_file = prepare_queue_task(request.app, entry, filename) + + with queue_file: + queue_file.write(request.files['file'].stream.read()) + + # Save now so we have this data before kicking off processing + entry.save() + + # Pass off to processing + # + # (... don't change entry after this point to avoid race + # conditions with changes to the document via processing code) + feed_url = request.urlgen( + 'mediagoblin.user_pages.atom_feed', + qualified=True, user=request.user.username) + run_process_media(entry, feed_url) + add_message(request, SUCCESS, _('Woohoo! Submitted!')) + + return redirect(request, "mediagoblin.user_pages.user_home", + user=request.user.username) + except Exception as e: + ''' + This section is intended to catch exceptions raised in + mediagoblin.media_types + ''' + if isinstance(e, InvalidFileType) or \ + isinstance(e, FileTypeNotSupported): + submit_form.file.errors.append( + e) + else: + raise + + return render_to_response( + request, + 'mediagoblin/submit/start.html', + {'submit_form': submit_form, + 'app_config': mg_globals.app_config}) + + +@require_active_login +def add_collection(request, media=None): + """ + View to create a new collection + """ + submit_form = submit_forms.AddCollectionForm(request.form) + + if request.method == 'POST' and submit_form.validate(): + collection = request.db.Collection() + + collection.title = unicode(submit_form.title.data) + collection.description = unicode(submit_form.description.data) + collection.creator = request.user.id + collection.generate_slug() + + # Make sure this user isn't duplicating an existing collection + existing_collection = request.db.Collection.find_one({ + 'creator': request.user.id, + 'title':collection.title}) + + if existing_collection: + add_message(request, messages.ERROR, + _('You already have a collection called "%s"!') \ + % collection.title) + else: + collection.save() + + add_message(request, SUCCESS, + _('Collection "%s" added!') % collection.title) + + return redirect(request, "mediagoblin.user_pages.user_home", + user=request.user.username) + + return render_to_response( + request, + 'mediagoblin/submit/collection.html', + {'submit_form': submit_form, + 'app_config': mg_globals.app_config}) diff --git a/mediagoblin/templates/mediagoblin/admin/panel.html b/mediagoblin/templates/mediagoblin/admin/panel.html new file mode 100644 index 00000000..1c3c866e --- /dev/null +++ b/mediagoblin/templates/mediagoblin/admin/panel.html @@ -0,0 +1,114 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% block title -%} + {% trans %}Media processing panel{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + +<h1>{% trans %}Media processing panel{% endtrans %}</h1> + +<p> + {% trans %}Here you can track the state of media being processed on this instance.{% endtrans %} +</p> + +<h2>{% trans %}Media in-processing{% endtrans %}</h2> + +{% if processing_entries.count() %} + <table class="media_panel processing"> + <tr> + <th>ID</th> + <th>User</th> + <th>Title</th> + <th>When submitted</th> + <th>Transcoding progress</th> + </tr> + {% for media_entry in processing_entries %} + <tr> + <td>{{ media_entry.id }}</td> + <td>{{ media_entry.get_uploader.username }}</td> + <td>{{ media_entry.title }}</td> + <td>{{ media_entry.created.strftime("%F %R") }}</td> + {% if media_entry.transcoding_progress %} + <td>{{ media_entry.transcoding_progress }}%</td> + {% else %} + <td>Unknown</td> + {% endif %} + </tr> + {% endfor %} + </table> +{% else %} + <p><em>{% trans %}No media in-processing{% endtrans %}</em></p> +{% endif %} + +<h2>{% trans %}These uploads failed to process:{% endtrans %}</h2> +{% if failed_entries.count() %} + + <table class="media_panel failed"> + <tr> + <th>ID</th> + <th>User</th> + <th>Title</th> + <th>When submitted</th> + <th>Reason for failure</th> + <th>Failure metadata</th> + </tr> + {% for media_entry in failed_entries %} + <tr> + <td>{{ media_entry.id }}</td> + <td>{{ media_entry.get_uploader.username }}</td> + <td>{{ media_entry.title }}</td> + <td>{{ media_entry.created.strftime("%F %R") }}</td> + {% if media_entry.get_fail_exception() %} + <td>{{ media_entry.get_fail_exception().general_message }}</td> + <td>{{ media_entry.fail_metadata }}</td> + {% else %} + <td> </td> + <td> </td> + {% endif %} + </tr> + {% endfor %} + </table> +{% else %} + <p><em>{% trans %}No failed entries!{% endtrans %}</em></p> +{% endif %} +<h2>{% trans %}Last 10 successful uploads{% endtrans %}</h2> +{% if processed_entries.count() %} + + <table class="media_panel processed"> + <tr> + <th>ID</th> + <th>User</th> + <th>Title</th> + <th>Submitted</th> + </tr> + {% for media_entry in processed_entries %} + <tr> + <td>{{ media_entry.id }}</td> + <td>{{ media_entry.get_uploader.username }}</td> + <td><a href="{{ media_entry.url_for_self(request.urlgen) }}">{{ media_entry.title }}</a></td> + <td><span title='{{ media_entry.created.strftime("%F %R") }}'>{{ timesince(media_entry.created) }}</span></td> + </tr> + {% endfor %} + </table> +{% else %} + <p><em>{% trans %}No processed entries, yet!{% endtrans %}</em></p> +{% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/auth/change_fp.html b/mediagoblin/templates/mediagoblin/auth/change_fp.html new file mode 100644 index 00000000..1f7d9aca --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/change_fp.html @@ -0,0 +1,44 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_head %} + <script type="text/javascript" + src="{{ request.staticdirect('/js/show_password.js') }}"></script> +{% endblock mediagoblin_head %} + +{% block title -%} + {% trans %}Set your new password{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.auth.verify_forgot_password') }}" + method="POST" enctype="multipart/form-data"> + {{ csrf_token }} + <div class="form_box"> + <h1>{% trans %}Set your new password{% endtrans %}</h1> + {{ wtforms_util.render_divs(cp_form) }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Set password{% endtrans %}" class="button_form"/> + </div> + </div> + </form> +{% endblock %} + diff --git a/mediagoblin/templates/mediagoblin/auth/forgot_password.html b/mediagoblin/templates/mediagoblin/auth/forgot_password.html new file mode 100644 index 00000000..46aeddef --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/forgot_password.html @@ -0,0 +1,38 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block title -%} + {% trans %}Recover password{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.auth.forgot_password') }}" + method="POST" enctype="multipart/form-data"> + {{ csrf_token }} + <div class="form_box"> + <h1>{% trans %}Recover password{% endtrans %}</h1> + {{ wtforms_util.render_divs(fp_form) }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Send instructions{% endtrans %}" class="button_form"/> + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt b/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt new file mode 100644 index 00000000..fb5e1674 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/fp_verification_email.txt @@ -0,0 +1,30 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% trans username=username, verification_url=verification_url|safe -%} +Hi {{ username }}, + +to change your GNU MediaGoblin password, open the following URL in +your web browser: + +{{ verification_url }} + +If you think this is an error, just ignore this email and continue being +a happy goblin! +{%- endtrans %} + diff --git a/mediagoblin/templates/mediagoblin/auth/login.html b/mediagoblin/templates/mediagoblin/auth/login.html new file mode 100644 index 00000000..4a39059d --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/login.html @@ -0,0 +1,62 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_head %} + <script type="text/javascript" + src="{{ request.staticdirect('/js/autofilledin_password.js') }}"></script> +{% endblock %} + +{% block title -%} + {% trans %}Log in{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.auth.login') }}" + method="POST" enctype="multipart/form-data"> + {{ csrf_token }} + <div class="form_box"> + <h1>{% trans %}Log in{% endtrans %}</h1> + {% if login_failed %} + <div class="form_field_error"> + {% trans %}Logging in failed!{% endtrans %} + </div> + {% endif %} + {% if allow_registration %} + <p> + {% trans %}Don't have an account yet?{% endtrans %} <a href="{{ request.urlgen('mediagoblin.auth.register') }}"> + {%- trans %}Create one here!{% endtrans %}</a> + </p> + {% endif %} + {{ wtforms_util.render_divs(login_form) }} + <p> + <a href="{{ request.urlgen('mediagoblin.auth.forgot_password') }}" id="forgot_password"> + {% trans %}Forgot your password?{% endtrans %}</a> + </p> + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Log in{% endtrans %}" class="button_form"/> + </div> + {% if next %} + <input type="hidden" name="next" value="{{ next }}" class="button_form" + style="display: none;"/> + {% endif %} + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/auth/register.html b/mediagoblin/templates/mediagoblin/auth/register.html new file mode 100644 index 00000000..6dff0207 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/register.html @@ -0,0 +1,47 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_head %} + <script type="text/javascript" + src="{{ request.staticdirect('/js/show_password.js') }}"></script> +{% endblock mediagoblin_head %} + +{% block title -%} + {% trans %}Create an account!{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.auth.register') }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box"> + <h1>{% trans %}Create an account!{% endtrans %}</h1> + {{ wtforms_util.render_divs(register_form) }} + {{ csrf_token }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Create{% endtrans %}" + class="button_form" /> + </div> + </div> + </form> +<!-- Focus the username field by default --> +<script>$(document).ready(function(){$("#username").focus();});</script> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/auth/verification_email.txt b/mediagoblin/templates/mediagoblin/auth/verification_email.txt new file mode 100644 index 00000000..969ef96a --- /dev/null +++ b/mediagoblin/templates/mediagoblin/auth/verification_email.txt @@ -0,0 +1,26 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} + +{% trans username=username, verification_url=verification_url|safe -%} +Hi {{ username }}, + +to activate your GNU MediaGoblin account, open the following URL in +your web browser: + +{{ verification_url }} +{%- endtrans %} diff --git a/mediagoblin/templates/mediagoblin/base.html b/mediagoblin/templates/mediagoblin/base.html new file mode 100644 index 00000000..6c7c07d0 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/base.html @@ -0,0 +1,128 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +<!doctype html> +<html +{% block mediagoblin_html_tag %} +{% endblock mediagoblin_html_tag %} +> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>{% block title %}{{ app_config['html_title'] }}{% endblock %}</title> + <link rel="stylesheet" type="text/css" + href="{{ request.staticdirect('/css/extlib/reset.css') }}"/> + <link rel="stylesheet" type="text/css" + href="{{ request.staticdirect('/css/base.css') }}"/> + <link rel="shortcut icon" + href="{{ request.staticdirect('/images/goblin.ico') }}" /> + <script type="text/javascript" + src="{{ request.staticdirect('/js/extlib/jquery.js') }}"></script> + <script type="text/javascript" + src="{{ request.staticdirect('/js/header_dropdown.js') }}"></script> + + {# For clarification, the difference between the extra_head.html template + # and the head template hook is that the former should be used by + # themes and the latter should be used by plugins. + # The reason is that only one thing can override extra_head.html... + # but multiple plugins can hook into the template hook. + #} + {% include "mediagoblin/extra_head.html" %} + {% template_hook("head") %} + + {% block mediagoblin_head %} + {% endblock mediagoblin_head %} + </head> + <body> + {% include 'mediagoblin/bits/body_start.html' %} + {% block mediagoblin_body %} + {% block mediagoblin_header %} + <header> + {%- include "mediagoblin/bits/logo.html" -%} + {% block mediagoblin_header_title %}{% endblock %} + <div class="header_right"> + {%- if request.user %} + {% if request.user and request.user.status == 'active' %} + <div class="button_action header_dropdown_down">▼</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') }}?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"> + {% include 'mediagoblin/bits/above_content.html' %} + <div class="mediagoblin_content"> + {% include "mediagoblin/utils/messages.html" %} + {% block mediagoblin_content %} + {% endblock mediagoblin_content %} + </div> + {%- include "mediagoblin/bits/base_footer.html" %} + </div> + {%- endblock mediagoblin_body %} + {% include 'mediagoblin/bits/body_end.html' %} + </body> +</html> diff --git a/mediagoblin/templates/mediagoblin/bits/above_content.html b/mediagoblin/templates/mediagoblin/bits/above_content.html new file mode 100644 index 00000000..bb7b9762 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/bits/above_content.html @@ -0,0 +1,17 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} diff --git a/mediagoblin/templates/mediagoblin/bits/base_footer.html b/mediagoblin/templates/mediagoblin/bits/base_footer.html new file mode 100644 index 00000000..80cd41b0 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/bits/base_footer.html @@ -0,0 +1,28 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011-2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{%- block mediagoblin_footer %} + <footer> + {% trans -%} + Powered by <a href="http://mediagoblin.org/" title='Version {{ version }}'>MediaGoblin</a>, a <a href="http://gnu.org/">GNU</a> project. + {%- endtrans %} + {% trans source_link=app_config['source_link'] -%} + Released under the <a href="http://www.fsf.org/licensing/licenses/agpl-3.0.html">AGPL</a>. <a href="{{ source_link }}">Source code</a> available. + {%- endtrans %} + </footer> +{%- endblock mediagoblin_footer -%} diff --git a/mediagoblin/templates/mediagoblin/bits/body_end.html b/mediagoblin/templates/mediagoblin/bits/body_end.html new file mode 100644 index 00000000..bb7b9762 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/bits/body_end.html @@ -0,0 +1,17 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} diff --git a/mediagoblin/templates/mediagoblin/bits/body_start.html b/mediagoblin/templates/mediagoblin/bits/body_start.html new file mode 100644 index 00000000..bb7b9762 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/bits/body_start.html @@ -0,0 +1,17 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} diff --git a/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html b/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html new file mode 100644 index 00000000..544ee146 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/bits/frontpage_welcome.html @@ -0,0 +1,35 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% if request.user %} + <h1>{% trans %}Explore{% endtrans %}</h1> +{% else %} + <h1>{% trans %}Hi there, welcome to this MediaGoblin site!{% endtrans %}</h1> + <img class="right_align" src="{{ request.staticdirect('/images/frontpage_image.png') }}" /> + <p>{% trans %}This site is running <a href="http://mediagoblin.org">MediaGoblin</a>, an extraordinarily great piece of media hosting software.{% endtrans %}</p> + <p>{% trans %}To add your own media, place comments, and more, you can log in with your MediaGoblin account.{% endtrans %}</p> + {% if allow_registration %} + <p>{% trans %}Don't have one yet? It's easy!{% endtrans %}</p> + {% trans register_url=request.urlgen('mediagoblin.auth.register') -%} + <a class="button_action_highlight" href="{{ register_url }}">Create an account at this site</a> + or + <a class="button_action" href="http://wiki.mediagoblin.org/HackingHowto">Set up MediaGoblin on your own server</a> + {%- endtrans %} + {% endif %} + <div class="clear"></div> +{% endif %} diff --git a/mediagoblin/templates/mediagoblin/bits/logo.html b/mediagoblin/templates/mediagoblin/bits/logo.html new file mode 100644 index 00000000..5bd8edd8 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/bits/logo.html @@ -0,0 +1,25 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} + +{% block mediagoblin_logo %} + <a class="logo" + href="{{ request.urlgen('index') }}" + ><img src="{{ request.staticdirect('/images/logo.png') }}" + alt="{% trans %}MediaGoblin logo{% endtrans %}" /> + </a> +{% endblock mediagoblin_logo -%} diff --git a/mediagoblin/templates/mediagoblin/edit/attachments.html b/mediagoblin/templates/mediagoblin/edit/attachments.html new file mode 100644 index 00000000..3fbea3be --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/attachments.html @@ -0,0 +1,69 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{%- extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block title -%} + {% trans media_title=media.title -%} + Editing attachments for {{ media_title }} + {%- endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.edit.attachments', + user= media.get_uploader.username, + media_id=media.id) }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box"> + <h1> + {%- trans media_title=media.title -%} + Editing attachments for {{ media_title }} + {%- endtrans -%} + </h1> + <div style="text-align: center;" > + <img src="{{ media.thumb_url }}" /> + </div> + + {% if media.attachment_files|count %} + <h2>{% trans %}Attachments{% endtrans %}</h2> + <ul> + {%- for attachment in media.attachment_files %} + <li> + <a target="_blank" href="{{ request.app.public_store.file_url( + attachment['filepath']) }}"> + {{- attachment.name -}} + </a> + </li> + {%- endfor %} + </ul> + {% endif %} + + <h2>{% trans %}Add attachment{% endtrans %}</h2> + {{- wtforms_util.render_divs(form) }} + <div class="form_submit_buttons"> + <a href="{{ media.url_for_self(request.urlgen) }}"> + {%- trans %}Cancel{% endtrans -%} + </a> + <input type="submit" value="{% trans %}Save changes{% endtrans %}" + class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/edit/change_pass.html b/mediagoblin/templates/mediagoblin/edit/change_pass.html new file mode 100644 index 00000000..ff909b07 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/change_pass.html @@ -0,0 +1,52 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_head %} + <script type="text/javascript" + src="{{ request.staticdirect('/js/show_password.js') }}"></script> +{% endblock mediagoblin_head %} + +{% block title -%} + {% trans username=user.username -%} + Changing {{ username }}'s password + {%- endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.edit.pass') }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box edit_box"> + <h1> + {%- trans username=user.username -%} + Changing {{ username }}'s password + {%- endtrans -%} + </h1> + {{ wtforms_util.render_divs(form) }} + {{ csrf_token }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Save{% endtrans %}" + class="button_form" /> + </div> + </div> + </form> +{% endblock %} + + diff --git a/mediagoblin/templates/mediagoblin/edit/delete_account.html b/mediagoblin/templates/mediagoblin/edit/delete_account.html new file mode 100644 index 00000000..84d0b580 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/delete_account.html @@ -0,0 +1,48 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.edit.delete_account') }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box"> + <h1> + {%- trans user_name=user.username -%} + Really delete user '{{ user_name }}' and all related media/comments? + {%- endtrans -%} + </h1> + <p class="delete_checkbox_box"> + <input type="checkbox" name="confirmed"/> + <label for="confirmed"> + {%- trans %}Yes, really delete my account{% endtrans -%} + </label> + </p> + + <div class="form_submit_buttons"> + <a class="button_action" href="{{ request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username) }}">{% trans %}Cancel{% endtrans %}</a> + {{ csrf_token }} + <input type="submit" value="{% trans %}Delete permanently{% endtrans %}" class="button_form" /> + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/edit/edit.html b/mediagoblin/templates/mediagoblin/edit/edit.html new file mode 100644 index 00000000..9a040095 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/edit.html @@ -0,0 +1,48 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block title -%} + {% trans media_title=media.title -%} + Editing {{ media_title }} + {%- endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.edit.edit_media', + user= media.get_uploader.username, + media_id=media.id) }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box_xl edit_box"> + <h1>{% trans media_title=media.title %}Editing {{ media_title }}{% endtrans %}</h1> + <div style="text-align: center;" > + <img src="{{ media.thumb_url }}" /> + </div> + {{ wtforms_util.render_divs(form) }} + <div class="form_submit_buttons"> + <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a> + <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> + +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/edit/edit_account.html b/mediagoblin/templates/mediagoblin/edit/edit_account.html new file mode 100644 index 00000000..4c4aaf95 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/edit_account.html @@ -0,0 +1,65 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{%- extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_head %} + <script type="text/javascript" + src="{{ request.staticdirect('/js/show_password.js') }}"></script> +{% endblock mediagoblin_head %} + +{% block title -%} + {% trans username=user.username -%} + Changing {{ username }}'s account settings + {%- endtrans %} — {{ super() }} +{%- endblock %} + + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.edit.account') }}?username={{ + user.username }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box edit_box"> + <h1> + {%- trans username=user.username -%} + Changing {{ username }}'s account settings + {%- endtrans -%} + </h1> + <p> + <a href="{{ request.urlgen('mediagoblin.edit.pass') }}"> + {% trans %}Change your password.{% endtrans %} + </a> + </p> + <div class="form_field_input"> + <p>{{ form.wants_comment_notification }} + {{ wtforms_util.render_label(form.wants_comment_notification) }}</p> + </div> + {{- wtforms_util.render_field_div(form.license_preference) }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> + <div class="delete"> + <a href="{{ request.urlgen('mediagoblin.edit.delete_account') }}"> + {%- trans %}Delete my account{% endtrans -%} + </a> + </div> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/edit/edit_collection.html b/mediagoblin/templates/mediagoblin/edit/edit_collection.html new file mode 100644 index 00000000..5cf5bae8 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/edit_collection.html @@ -0,0 +1,39 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.edit.edit_collection', + user= collection.get_creator.username, + collection= collection.slug) }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box_xl edit_box"> + <h1>{% trans collection_title=collection.title %}Editing {{ collection_title }}{% endtrans %}</h1> + {{ wtforms_util.render_divs(form) }} + <div class="form_submit_buttons"> + <a class="button_action" href="{{ collection.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a> + <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> + +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/edit/edit_profile.html b/mediagoblin/templates/mediagoblin/edit/edit_profile.html new file mode 100644 index 00000000..163fe186 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/edit/edit_profile.html @@ -0,0 +1,45 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block title -%} + {% trans username=user.username -%} + Editing {{ username }}'s profile + {%- endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.edit.profile', + user=user.username) }}" method="POST" enctype="multipart/form-data"> + <div class="form_box edit_box"> + <h1> + {%- trans username=user.username -%} + Editing {{ username }}'s profile + {%- endtrans %} + </h1> + {{ wtforms_util.render_divs(form) }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Save changes{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/error.html b/mediagoblin/templates/mediagoblin/error.html new file mode 100644 index 00000000..c16b650f --- /dev/null +++ b/mediagoblin/templates/mediagoblin/error.html @@ -0,0 +1,28 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% block title %}{{err_code}} — {{ super() }}{% endblock %} + +{% block mediagoblin_content %} + <img class="right_align" src="{{ request.staticdirect('/images/404.png') }}" + alt="{% trans %}Image of goblin stressing out{% endtrans %}" /> + <h1>{{ title }}</h1> + <p>{{ err_msg|safe }}</p> + <div class="clear"></div> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/extra_head.html b/mediagoblin/templates/mediagoblin/extra_head.html new file mode 100644 index 00000000..973e2b48 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/extra_head.html @@ -0,0 +1,19 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} + +{# Add extra head declarations here for your theme, if appropriate #} diff --git a/mediagoblin/templates/mediagoblin/listings/collection.html b/mediagoblin/templates/mediagoblin/listings/collection.html new file mode 100644 index 00000000..4d502201 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/listings/collection.html @@ -0,0 +1,43 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% from "mediagoblin/utils/object_gallery.html" import object_gallery %} + +{% block mediagoblin_head %} + <link rel="alternate" type="application/atom+xml" + href="{{ request.urlgen( + 'mediagoblin.listings.tag_atom_feed', + tag=tag_slug) }}"> +{% endblock mediagoblin_head %} + +{% block title %} + {% trans %}Media tagged with: {{ tag_name }}{% endtrans %} — {{ super() }} +{% endblock %} + +{% block mediagoblin_content -%} + <h1> + {% trans %}Media tagged with: {{ tag_name }}{% endtrans %} + </h1> + + {{ object_gallery(request, media_entries, pagination) }} + + {% set feed_url = request.urlgen('mediagoblin.listings.tag_atom_feed', + tag=tag_slug) %} + {% include "mediagoblin/utils/feed_link.html" %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/listings/tag.html b/mediagoblin/templates/mediagoblin/listings/tag.html new file mode 100644 index 00000000..4d502201 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/listings/tag.html @@ -0,0 +1,43 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% from "mediagoblin/utils/object_gallery.html" import object_gallery %} + +{% block mediagoblin_head %} + <link rel="alternate" type="application/atom+xml" + href="{{ request.urlgen( + 'mediagoblin.listings.tag_atom_feed', + tag=tag_slug) }}"> +{% endblock mediagoblin_head %} + +{% block title %} + {% trans %}Media tagged with: {{ tag_name }}{% endtrans %} — {{ super() }} +{% endblock %} + +{% block mediagoblin_content -%} + <h1> + {% trans %}Media tagged with: {{ tag_name }}{% endtrans %} + </h1> + + {{ object_gallery(request, media_entries, pagination) }} + + {% set feed_url = request.urlgen('mediagoblin.listings.tag_atom_feed', + tag=tag_slug) %} + {% include "mediagoblin/utils/feed_link.html" %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/ascii.html b/mediagoblin/templates/mediagoblin/media_displays/ascii.html new file mode 100644 index 00000000..3cc5e0ab --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/ascii.html @@ -0,0 +1,44 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + +{% block mediagoblin_media %} + <div class="ascii-wrapper"> + <pre> + {%- autoescape False -%} + {{- request.app.public_store.get_file( + media.media_files['unicode']).read()|string -}} + {%- endautoescape -%} + </pre> + </div> +{% endblock %} + +{% block mediagoblin_sidebar %} + {% if 'original' in media.media_files %} + <h3>{% trans %}Download{% endtrans %}</h3> + <p> + <a href="{{ request.app.public_store.file_url( + media.media_files['original']) }}"> + {%- trans -%} + Original + {%- endtrans -%} + </a> + </p> + {% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/audio.html b/mediagoblin/templates/mediagoblin/media_displays/audio.html new file mode 100644 index 00000000..95bc6e88 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/audio.html @@ -0,0 +1,65 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + +{% block mediagoblin_head %} + {{ super() }} + <link rel="stylesheet" type="text/css" href="{{ request.staticdirect('/css/audio.css') }}" /> + <script type="text/javascript" src="{{ request.staticdirect( + '/js/extlib/html5slider.js') }}"></script> + <script type="text/javascript" src="{{ request.staticdirect( + '/js/audio.js') }}"></script> +{% endblock %} + +{% block mediagoblin_media %} + <div class="audio-media"> + {% if 'spectrogram' in media.media_files %} + <div class="audio-spectrogram"> + <img src="{{ request.app.public_store.file_url( + media.media_files.spectrogram) }}" + alt="Spectrogram" /> + </div> + {% endif %} + <audio class="audio-player" controls="controls" + preload="metadata"> + <source src="{{ request.app.public_store.file_url( + media.media_files.webm_audio) }}" type="audio/webm; codecs=vorbis" /> + <div class="no_html5"> + {%- trans -%}Sorry, this audio will not work because + your web browser does not support HTML5 + audio.{%- endtrans -%}<br/> + {%- trans -%}You can get a modern web browser that + can play the audio at <a href="http://getfirefox.com"> + http://getfirefox.com</a>!{%- endtrans -%} + </div> + </audio> + </div> +{% endblock %} + +{% block mediagoblin_sidebar %} + <h3>{% trans %}Download{% endtrans %}</h3> + <ul> + {% if 'original' in media.media_files %} + <li><a href="{{ request.app.public_store.file_url( + media.media_files.original) }}">{% trans %}Original file{% endtrans %}</a> + {% endif %} + <li><a href="{{ request.app.public_store.file_url( + media.media_files.webm_audio) }}">{% trans %}WebM file (Vorbis codec){% endtrans %}</a> + </ul> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/image.html b/mediagoblin/templates/mediagoblin/media_displays/image.html new file mode 100644 index 00000000..d0050f50 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/image.html @@ -0,0 +1,46 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + +{% block mediagoblin_head %} + {{ super() }} + {% template_hook("image_head") %} +{% endblock mediagoblin_head %} + +{% block mediagoblin_sidebar %} + {{ super() }} + {% template_hook("image_sideinfo") %} +{% endblock %} + +{% block mediagoblin_after_added_sidebar %} + {% if app_config['original_date_visible'] %} + {% set original_date = media.media_manager.get_original_date() %} + + {% if original_date %} + <h3>{% trans %}Created{% endtrans %}</h3> + + <p><span title="{{ original_date.strftime("%I:%M%p %Y-%m-%d") }}"> + {%- trans formatted_time=timesince(original_date) -%} + {{ formatted_time }} ago + {%- endtrans -%} + </span></p> + {%- endif %} + {% endif %} +{% endblock %} + diff --git a/mediagoblin/templates/mediagoblin/media_displays/pdf.html b/mediagoblin/templates/mediagoblin/media_displays/pdf.html new file mode 100644 index 00000000..9319e87c --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/pdf.html @@ -0,0 +1,86 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + +{% set medium_view = request.app.public_store.file_url( + media.media_files['medium']) %} + +{% if 'pdf' in media.media_files %} + {% set pdf_view = request.app.public_store.file_url( + media.media_files['pdf']) %} +{% else %} + {% set pdf_view = request.app.public_store.file_url( + media.media_files['original']) %} +{% endif %} + +{% set pdf_js = global_config.get('media_type:mediagoblin.media_types.pdf', {}).get('pdf_js', False) %} + +{% if pdf_js %} + {% block mediagoblin_html_tag %} + dir="ltr" mozdisallowselectionprint moznomarginboxes + {% endblock mediagoblin_html_tag %} +{% endif %} + +{% block mediagoblin_head -%} + {{ super() }} + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> + +{%- endblock %} + +{% block mediagoblin_media %} + {% if pdf_js %} + <iframe width="640px" height="480px" + src="{{ request.staticdirect('/extlib/pdf.js/web/viewer.html') }}?file={{ pdf_view }} "> + </iframe> + {% else %} + <a href="{{ pdf_view }}"> + <img id="medium" + class="media_image" + src="{{ medium_view }}" + alt=" + {%- trans media_title=media.title -%} + Image for {{ media_title}} + {%- endtrans %}"/> + </a> + {% endif %} +{% endblock %} + +{% block mediagoblin_sidebar %} + <h3>{% trans %}Download{% endtrans %}</h3> + <ul> + {% if 'original' in media.media_files %} + <li> + <a href="{{ request.app.public_store.file_url( + media.media_files.original) }}"> + {%- trans %}Original file{% endtrans -%} + </a> + </li> + {% endif %} + {% if 'pdf' in media.media_files %} + <li> + <a href="{{ request.app.public_store.file_url( + media.media_files.pdf) }}"> + {%- trans %}PDF file{% endtrans -%} + </a> + </li> + {% endif %} + </ul> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/stl.html b/mediagoblin/templates/mediagoblin/media_displays/stl.html new file mode 100644 index 00000000..a89e0b4f --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/stl.html @@ -0,0 +1,150 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + + +{% block mediagoblin_media %} + + +{% set model_download = request.app.public_store.file_url( + media.media_files['original']) %} +{% set perspective_view = request.app.public_store.file_url( + media.media_files['perspective']) %} +{% set top_view = request.app.public_store.file_url( + media.media_files['top']) %} +{% set side_view = request.app.public_store.file_url( + media.media_files['side']) %} +{% set front_view = request.app.public_store.file_url( + media.media_files['front']) %} + +<style type="text/css"> +#top_view, #side_view, #front_view, #thingy_view { + display: none; +} +.media_image { + cursor: inherit!important; +} + +</style> + +{% if media.media_data.file_type == "stl" %} + <script src="{{ request.staticdirect('/js/extlib/thingiview.js/Three.js') }}"></script> + <script src="{{ request.staticdirect('/js/extlib/thingiview.js/plane.js') }}"></script> + <script src="{{ request.staticdirect('/js/extlib/thingiview.js/thingiview.js') }}"></script> +{% endif %} + + +<script type="text/javascript"> +window.show = function (view_id) { + ids = [ + "perspective", + "top_view", + "side_view", + "front_view", + "thingy_view", + ]; + for (var i=0; i<ids.length; i+=1) { + id = ids[i]; + var view = document.getElementById(id); + view.style.display = id===view_id ? "block" : "none"; + } +}; + +window.show_things = function () { + document.getElementById("webgl_button").onclick = function () { + show('thingy_view'); + }; + window.show("thingy_view"); + thingiurlbase = "{{ request.staticdirect('/js/extlib/thingiview.js') }}"; + thingiview = new Thingiview("thingy_view"); + thingiview.setObjectColor('#821543'); + thingiview.initScene(); + thingiview.loadSTL("{{ model_download }}"); + thingiview.setRotation(false); +}; +</script> + +<img + id="perspective" + class="media_image" + src="{{ perspective_view }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> +<img + id="top_view" + class="media_image" + src="{{ top_view }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> +<img + id="side_view" + class="media_image" + src="{{ side_view }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> +<img + id="front_view" + class="media_image" + src="{{ front_view }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> +<div id="thingy_view" style="width:640px;height:640px;"></div> + + +<div style="padding: 4px;"> + <a class="button_action" onclick="show('perspective');" + title="{%- trans %}Toggle Rotate{% endtrans -%}"> + {%- trans %}Perspective{% endtrans -%} + </a> + <a class="button_action" onclick="show('front_view');" + title="{%- trans %}Front{% endtrans -%}"> + {%- trans %}Front{% endtrans -%} + </a> + <a class="button_action" onclick="show('top_view');" + title="{%- trans %}Top{% endtrans -%}"> + {%- trans %}Top{% endtrans -%} + </a> + <a class="button_action" onclick="show('side_view');" + title="{%- trans %}Side{% endtrans -%}"> + {%- trans %}Side{% endtrans -%} + </a> +{% if media.media_data.file_type == "stl" %} + <a id="webgl_button" class="button_action" + onclick="show_things();" + title="{%- trans %}WebGL{% endtrans -%}"> + {%- trans %}WebGL{% endtrans -%} + </a> +{% endif %} + + <a class="button_action" href="{{ model_download }}" + title="{%- trans %}Download{% endtrans -%}" + style="float:right;"> + {%- trans %}Download model{% endtrans -%} + </a> +</div> + + +{% endblock %} + +{% block mediagoblin_sidebar %} +<h3>{% trans %}File Format{% endtrans %}</h3> +<p>{{ media.media_data.file_type }}</p> +<h3>{% trans %}Object Height{% endtrans %}</h3> +<p>~{{ media.media_data.height|int }} mm</p> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/media_displays/video.html b/mediagoblin/templates/mediagoblin/media_displays/video.html new file mode 100644 index 00000000..b0854c9f --- /dev/null +++ b/mediagoblin/templates/mediagoblin/media_displays/video.html @@ -0,0 +1,74 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% extends 'mediagoblin/user_pages/media.html' %} + +{% block mediagoblin_head -%} + {{ super() }} + <script type="text/javascript" src="{{ + request.staticdirect('/extlib/video-js/video.min.js') }}"></script> + <link href="{{ request.staticdirect('/css/vjs-mg-skin.css') }}" + rel="stylesheet"> +{%- endblock %} + +{% block mediagoblin_media %} + {% set display_type, display_path = media.get_display_media() %} + + <video controls + {% if global_config['media_type:mediagoblin.media_types.video']['auto_play'] %}autoplay{% endif %} + preload="auto" class="video-js vjs-mg-skin" + data-setup='{"height": {{ media.media_data.height }}, + "width": {{ media.media_data.width }} }'> + <source src="{{ request.app.public_store.file_url(display_path) }}" + {% if media.media_data %} + type="{{ media.media_data.source_type() }}" + {% else %} + type="{{ media.media_manager['default_webm_type'] }}" + {% endif %} /> + <div class="no_html5"> + {%- trans -%}Sorry, this video will not work because + your web browser does not support HTML5 + video.{%- endtrans -%}<br/> + {%- trans -%}You can get a modern web browser that + can play this video at <a href="http://getfirefox.com"> + http://getfirefox.com</a>!{%- endtrans -%} + </div> + </video> +{% endblock %} + +{% block mediagoblin_sidebar %} + <h3>{% trans %}Download{% endtrans %}</h3> + <ul> + {% if 'original' in media.media_files %} + <li> + <a href="{{ request.app.public_store.file_url( + media.media_files.original) }}"> + {%- trans %}Original file{% endtrans -%} + </a> + </li> + {% endif %} + {% if 'webm_640' in media.media_files %} + <li> + <a href="{{ request.app.public_store.file_url( + media.media_files.webm_640) }}"> + {%- trans %}WebM file (640p; VP8/Vorbis){% endtrans -%} + </a> + </li> + {% endif %} + </ul> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/root.html b/mediagoblin/templates/mediagoblin/root.html new file mode 100644 index 00000000..15d53af1 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/root.html @@ -0,0 +1,38 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% from "mediagoblin/utils/object_gallery.html" import object_gallery %} + +{% set feed_url = request.urlgen('mediagoblin.listings.atom_feed') %} + +{% block mediagoblin_head -%} + {% set feed_url = request.urlgen('mediagoblin.listings.atom_feed') -%} + <link rel="alternate" type="application/atom+xml" href="{{ feed_url }}"> +{%- endblock mediagoblin_head %} + +{% block mediagoblin_content %} + {% include "mediagoblin/bits/frontpage_welcome.html" %} + + <h2>{% trans %}Most recent media{% endtrans %}</h2> + {{ object_gallery(request, media_entries, pagination) }} + + {#- Need to set feed_url within this block so template can use it. -#} + {%- set feed_url = feed_url -%} + {%- include "mediagoblin/utils/feed_link.html" -%} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/submit/collection.html b/mediagoblin/templates/mediagoblin/submit/collection.html new file mode 100644 index 00000000..4e2bc17d --- /dev/null +++ b/mediagoblin/templates/mediagoblin/submit/collection.html @@ -0,0 +1,34 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.submit.collection') }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box_xl"> + <h1>{% trans %}Add a collection{% endtrans %}</h1> + {{ wtforms_util.render_divs(submit_form) }} + <div class="form_submit_buttons"> + {{ csrf_token }} + <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form" /> + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/submit/start.html b/mediagoblin/templates/mediagoblin/submit/start.html new file mode 100644 index 00000000..aa390f56 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/submit/start.html @@ -0,0 +1,38 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block title -%} + {% trans %}Add your media{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.submit.start') }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box_xl"> + <h1>{% trans %}Add your media{% endtrans %}</h1> + {{ wtforms_util.render_divs(submit_form) }} + <div class="form_submit_buttons"> + {{ csrf_token }} + <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form" /> + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/test_submit.html b/mediagoblin/templates/mediagoblin/test_submit.html new file mode 100644 index 00000000..0771a0c7 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/test_submit.html @@ -0,0 +1,34 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +<html> + <body> + <form action="{{ request.urlgen('test_submit') }}" method="POST" + enctype="multipart/form-data"> + <table> + {{ wtforms_util.render_table(image_form) }} + <tr> + <td></td> + <td><input type="submit" value="submit" class="button_form" /></td> + {{ csrf_token }} + </tr> + </table> + </form> + </body> +</html> diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection.html b/mediagoblin/templates/mediagoblin/user_pages/collection.html new file mode 100644 index 00000000..5a7baadd --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/collection.html @@ -0,0 +1,72 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% from "mediagoblin/utils/collection_gallery.html" import collection_gallery %} + +{% block mediagoblin_head %} + <link rel="alternate" type="application/atom+xml" + href="{{ request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username) }}"> +{% endblock mediagoblin_head %} + +{% block title %} + {%- trans username=user.username, + collection_title=collection.title + -%} + {{ collection_title }} ({{ username }}'s collection) + {%- endtrans %} — {{ super() }} +{% endblock %} + +{% block mediagoblin_content -%} + <h1> + {%- trans username=user.username, + user_url=request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username), + collection_title=collection.title -%} + {{ collection_title }} by <a href="{{ user_url }}">{{ username }}</a> + {%- endtrans %} + </h1> + {% if request.user and (collection.creator == request.user.id or + request.user.is_admin) %} + {% set edit_url = request.urlgen('mediagoblin.edit.edit_collection', + user=collection.get_creator.username, + collection=collection.slug) %} + <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a> + {% set delete_url = request.urlgen('mediagoblin.user_pages.collection_confirm_delete', + user=collection.get_creator.username, + collection=collection.slug) %} + <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a> + {% endif %} + + <p> + {% autoescape False %} + {{ collection.description_html }} + {% endautoescape %} + </p> + + {{ collection_gallery(request, collection_items, pagination) }} + + {% set feed_url = request.urlgen('mediagoblin.user_pages.collection_atom_feed', + user=user.username, + collection=collection.slug ) %} + {% include "mediagoblin/utils/feed_link.html" %} + +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html new file mode 100644 index 00000000..694eb979 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/collection_confirm_delete.html @@ -0,0 +1,53 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.user_pages.collection_confirm_delete', + user=collection.get_creator.username, + collection=collection.slug) }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box"> + <h1> + {%- trans title=collection.title -%} + Really delete {{ title }}? + {%- endtrans %} + </h1> + + <br /> + + <p class="delete_checkbox_box"> + {{ form.confirm }} + {{ wtforms_util.render_label(form.confirm) }} + </p> + + <div class="form_submit_buttons"> + {# TODO: This isn't a button really... might do unexpected things :) #} + <a class="button_action" href=" + {{- collection.url_for_self(request.urlgen) }}"> + {%- trans %}Cancel{% endtrans -%} + </a> + <input type="submit" value="{% trans %}Delete permanently{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html new file mode 100644 index 00000000..dc31d90f --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/collection_item_confirm_remove.html @@ -0,0 +1,59 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.user_pages.collection_item_confirm_remove', + user=collection_item.in_collection.get_creator.username, + collection=collection_item.in_collection.slug, + collection_item=collection_item.id) }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box"> + <h1> + {%- trans media_title=collection_item.get_media_entry.title, + collection_title=collection_item.in_collection.title -%} + Really remove {{ media_title }} from {{ collection_title }}? + {%- endtrans %} + </h1> + + <div style="text-align: center;" > + <img src="{{ collection_item.get_media_entry.thumb_url }}" /> + </div> + + <br /> + + <p class="delete_checkbox_box"> + {{ form.confirm }} + {{ wtforms_util.render_label(form.confirm) }} + </p> + + <div class="form_submit_buttons"> + {# TODO: This isn't a button really... might do unexpected things :) #} + <a class="button_action" href=" + {{- collection_item.in_collection.url_for_self(request.urlgen) }}"> + {%- trans %}Cancel{% endtrans -%} + </a> + <input type="submit" value="{% trans %}Remove{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/collection_list.html b/mediagoblin/templates/mediagoblin/user_pages/collection_list.html new file mode 100644 index 00000000..8ac0b988 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/collection_list.html @@ -0,0 +1,56 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{%- extends "mediagoblin/base.html" %} + +{% block title %} + {%- trans username=user.username -%} + {{ username }}'s collections + {%- endtrans %} — {{ super() }} +{% endblock %} + +{% block mediagoblin_content -%} + <h1> + {%- trans username=user.username, + user_url=request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username) -%} + <a href="{{ user_url }}">{{ username }}</a>'s collections + {%- endtrans %} + </h1> + + {% if request.user %} + {% if request.user.status == 'active' %} + <p> + <a href="{{ request.urlgen('mediagoblin.submit.collection', + user=user.username) }}"> + {%- trans %}Create new collection{% endtrans -%} + </a> + </p> + {% endif %} + {% endif %} + + <ul> + {% for coll in collections %} + {%- set coll_url = coll.url_for_self(request.urlgen) %} + <li> + <a href="{{ coll_url }}">{{ coll.title }}</a> + </li> + {% endfor %} + </ul> + +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/comment_email.txt b/mediagoblin/templates/mediagoblin/user_pages/comment_email.txt new file mode 100644 index 00000000..1155ac1e --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/comment_email.txt @@ -0,0 +1,26 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} + +{% trans username=username, comment_author=comment_author, instance_name=app_config.html_title -%} + +Hi {{ username }}, +{{ comment_author }} commented on your post ({{ comment_url }}) at {{ instance_name }} +{% endtrans %} +{{ comment_content }} + +{{ app_config.html_title }} diff --git a/mediagoblin/templates/mediagoblin/user_pages/gallery.html b/mediagoblin/templates/mediagoblin/user_pages/gallery.html new file mode 100644 index 00000000..f23bb156 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/gallery.html @@ -0,0 +1,63 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% from "mediagoblin/utils/object_gallery.html" import object_gallery %} + +{% block mediagoblin_head %} + <link rel="alternate" type="application/atom+xml" + href="{{ request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username) }}"> +{% endblock mediagoblin_head %} + +{% block title %} + {%- trans username=user.username -%} + {{ username }}'s media + {%- endtrans %} — {{ super() }} +{% endblock %} + +{% block mediagoblin_content -%} + <h1> + {% if tag %} + {%- trans username=user.username, + user_url=request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username), + tag_url=request.urlgen( + 'mediagoblin.listings.tags_listing', + tag=tag) -%} + <a href="{{ user_url }}">{{ username }}</a>'s media with tag <a href="{{ tag_url }}">{{ tag }}</a> + {%- endtrans %} + {% else %} + {%- trans username=user.username, + user_url=request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username) -%} + <a href="{{ user_url }}">{{ username }}</a>'s media + {%- endtrans %} + {% endif %} + </h1> + + {{ object_gallery(request, media_entries, pagination) }} + + {% set feed_url = request.urlgen('mediagoblin.user_pages.atom_feed', + user=user.username) %} + {% include "mediagoblin/utils/feed_link.html" %} + +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html new file mode 100644 index 00000000..fb892fd7 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -0,0 +1,205 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{%- extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} +{% from "mediagoblin/utils/pagination.html" import render_pagination %} + +{% block title %}{{ media.title }} — {{ super() }}{% endblock %} + +{% block mediagoblin_head %} +<!--[if lte IE 8]><link rel="stylesheet" + href="{{ request.staticdirect('/extlib/leaflet/leaflet.ie.css') }}" /><![endif]--> + <script type="text/javascript" + src="{{ request.staticdirect('/js/comment_show.js') }}"></script> + <script type="text/javascript" + src="{{ request.staticdirect('/js/keyboard_navigation.js') }}"></script> + + {% template_hook("media_head") %} +{% endblock mediagoblin_head %} + +{% block mediagoblin_content %} + <p class="context"> + {%- trans user_url=request.urlgen( + 'mediagoblin.user_pages.user_home', + user=media.get_uploader.username), + username=media.get_uploader.username -%} + ❖ Browsing media by <a href="{{user_url}}">{{username}}</a> + {%- endtrans -%} + </p> + {% include "mediagoblin/utils/prev_next.html" %} + <div class="media_pane"> + <div class="media_image_container"> + {% block mediagoblin_media %} + {% set display_media = request.app.public_store.file_url( + media.get_display_media()[1]) %} + {# if there's a medium file size, that means the medium size + # isn't the original... so link to the original! + #} + {% if media.media_files.has_key('medium') %} + <a href="{{ request.app.public_store.file_url( + media.media_files['original']) }}"> + <img class="media_image" + src="{{ display_media }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> + </a> + {% else %} + <img class="media_image" + src="{{ display_media }}" + alt="{% trans media_title=media.title -%} + Image for {{ media_title }}{% endtrans %}" /> + {% endif %} + {% endblock %} + </div> + <h2 class="media_title"> + {{ media.title }} + </h2> + {% if request.user and + (media.uploader == request.user.id or + request.user.is_admin) %} + {% set edit_url = request.urlgen('mediagoblin.edit.edit_media', + user= media.get_uploader.username, + media_id=media.id) %} + <a class="button_action" href="{{ edit_url }}">{% trans %}Edit{% endtrans %}</a> + {% set delete_url = request.urlgen('mediagoblin.user_pages.media_confirm_delete', + user= media.get_uploader.username, + media_id=media.id) %} + <a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a> + {% endif %} + {% autoescape False %} + <p>{{ media.description_html }}</p> + {% endautoescape %} + {% if comments %} + {% if app_config['allow_comments'] %} + <a + {% if not request.user %} + href="{{ request.urlgen('mediagoblin.auth.login') }}" + {% endif %} + class="button_action" id="button_addcomment" title="Add a comment"> + {% trans %}Add a comment{% endtrans %} + </a> + {% endif %} + {% if request.user %} + <form action="{{ request.urlgen('mediagoblin.user_pages.media_post_comment', + user= media.get_uploader.username, + media_id=media.id) }}" method="POST" id="form_comment"> + {{ wtforms_util.render_divs(comment_form) }} + <div class="form_submit_buttons"> + <input type="submit" value="{% trans %}Add this comment{% endtrans %}" class="button_action" /> + {{ csrf_token }} + </div> + </form> + {% endif %} + <ul style="list-style:none"> + {% for comment in comments %} + {% set comment_author = comment.get_author %} + <li id="comment-{{ comment.id }}" + {%- if pagination.active_id == comment.id %} + class="comment_wrapper comment_active"> + <a name="comment" id="comment"></a> + {%- else %} + class="comment_wrapper"> + {%- endif %} + <div class="comment_author"> + <img src="{{ request.staticdirect('/images/icon_comment.png') }}" /> + <a href="{{ request.urlgen('mediagoblin.user_pages.user_home', + user=comment_author.username) }}" + class="comment_authorlink"> + {{- comment_author.username -}} + </a> + <a href="{{ request.urlgen('mediagoblin.user_pages.media_home.view_comment', + comment=comment.id, + user=media.get_uploader.username, + media=media.slug_or_id) }}#comment" + class="comment_whenlink"> + <span title='{{- comment.created.strftime("%I:%M%p %Y-%m-%d") -}}'> + {%- trans formatted_time=timesince(comment.created) -%} + {{ formatted_time }} ago + {%- endtrans -%} + </span></a>: + </div> + <div class="comment_content"> + {% autoescape False -%} + {{ comment.content_html }} + {%- endautoescape %} + </div> + </li> + {% endfor %} + </ul> + {{ render_pagination(request, pagination, + media.url_for_self(request.urlgen)) }} + {% endif %} + </div> + <div class="media_sidebar"> + <h3>{% trans %}Added{% endtrans %}</h3> + <p><span title="{{ media.created.strftime("%I:%M%p %Y-%m-%d") }}"> + {%- trans formatted_time=timesince(media.created) -%} + {{ formatted_time }} ago + {%- endtrans -%} + </span></p> + + {% block mediagoblin_after_added_sidebar %} + {% endblock %} + + {% if media.tags %} + {% include "mediagoblin/utils/tags.html" %} + {% endif %} + + {% include "mediagoblin/utils/collections.html" %} + + {% include "mediagoblin/utils/license.html" %} + + {% include "mediagoblin/utils/exif.html" %} + + {%- if media.attachment_files|count %} + <h3>{% trans %}Attachments{% endtrans %}</h3> + <ul> + {%- for attachment in media.attachment_files %} + <li> + <a href="{{ request.app.public_store.file_url(attachment.filepath) }}"> + {{- attachment.name -}} + </a> + </li> + {%- endfor %} + </ul> + {%- endif %} + {%- if app_config['allow_attachments'] + and request.user + and (media.uploader == request.user.id + or request.user.is_admin) %} + {%- if not media.attachment_files|count %} + <h3>{% trans %}Attachments{% endtrans %}</h3> + {%- endif %} + <p> + <a href="{{ request.urlgen('mediagoblin.edit.attachments', + user=media.get_uploader.username, + media_id=media.id) }}"> + {%- trans %}Add attachment{% endtrans -%} + </a> + </p> + {%- endif %} + + {% template_hook("media_sideinfo") %} + + {% block mediagoblin_sidebar %} + {% endblock %} + + </div> + <div class="clear"></div> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_collect.html b/mediagoblin/templates/mediagoblin/user_pages/media_collect.html new file mode 100644 index 00000000..b4c9671c --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/media_collect.html @@ -0,0 +1,73 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_head %} + <script type="text/javascript" + src="{{ request.staticdirect('/js/collection_form_show.js') }}"></script> +{% endblock %} + +{% block title -%} + {% trans media_title=media.title -%} + Add “{{ media_title }}” to a collection + {%- endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + <form action="{{ request.urlgen('mediagoblin.user_pages.media_collect', + user=media.get_uploader.username, + media_id=media.id) }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box"> + <h1> + {%- trans media_title=media.title -%} + Add “{{ media_title }}” to a collection + {%- endtrans -%} + </h1> + + <div style="text-align: center;" > + <img src="{{ media.thumb_url }}" /> + </div> + + <br /> + + {{- wtforms_util.render_label_p(form.collection) }} + <div class="form_field_input"> + {{ form.collection }} + <a class="button_action" id="button_addcollection">{% trans %}+{% endtrans %}</a> + </div> + + <div id="new_collection" class="subform"> + <h3>{% trans %}Add a new collection{% endtrans %}</h3> + + {{- wtforms_util.render_field_div(form.collection_title) }} + {{- wtforms_util.render_field_div(form.collection_description) }} + </div> + {{- wtforms_util.render_field_div(form.note) }} + + <div class="form_submit_buttons"> + {# TODO: This isn't a button really... might do unexpected things :) #} + <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a> + <input type="submit" value="{% trans %}Add{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html new file mode 100644 index 00000000..1d7dcc17 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/media_confirm_delete.html @@ -0,0 +1,54 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% import "/mediagoblin/utils/wtforms.html" as wtforms_util %} + +{% block mediagoblin_content %} + + <form action="{{ request.urlgen('mediagoblin.user_pages.media_confirm_delete', + user=media.get_uploader.username, + media_id=media.id) }}" + method="POST" enctype="multipart/form-data"> + <div class="form_box"> + <h1> + {%- trans title=media.title -%} + Really delete {{ title }}? + {%- endtrans %} + </h1> + + <div style="text-align: center;" > + <img src="{{ media.thumb_url }}" /> + </div> + + <br /> + + <p class="delete_checkbox_box"> + {{ form.confirm }} + {{ wtforms_util.render_label(form.confirm) }} + </p> + + <div class="form_submit_buttons"> + {# TODO: This isn't a button really... might do unexpected things :) #} + <a class="button_action" href="{{ media.url_for_self(request.urlgen) }}">{% trans %}Cancel{% endtrans %}</a> + <input type="submit" value="{% trans %}Delete permanently{% endtrans %}" class="button_form" /> + {{ csrf_token }} + </div> + </div> + </form> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html new file mode 100644 index 00000000..2a449d45 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/processing_panel.html @@ -0,0 +1,109 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% block title -%} + {% trans %}Media processing panel{% endtrans %} — {{ super() }} +{%- endblock %} + +{% block mediagoblin_content %} + +<h1>{% trans %}Media processing panel{% endtrans %}</h1> + +<p> + {% trans %}You can track the state of media being processed for your gallery here.{% endtrans %} +</p> + +<h2>{% trans %}Media in-processing{% endtrans %}</h2> + +{% if processing_entries.count() %} + <table class="media_panel processing"> + <tr> + <th>ID</th> + <th>Title</th> + <th>When submitted</th> + <th>Transcoding progress</th> + </tr> + {% for media_entry in processing_entries %} + <tr> + <td>{{ media_entry.id }}</td> + <td>{{ media_entry.title }}</td> + <td>{{ media_entry.created.strftime("%F %R") }}</td> + {% if media_entry.transcoding_progress %} + <td>{{ media_entry.transcoding_progress }}%</td> + {% else %} + <td>Unknown</td> + {% endif %} + </tr> + {% endfor %} + </table> +{% else %} + <p><em>{% trans %}No media in-processing{% endtrans %}</em></p> +{% endif %} + +<h2>{% trans %}These uploads failed to process:{% endtrans %}</h2> +{% if failed_entries.count() %} + + <table class="media_panel failed"> + <tr> + <th>ID</th> + <th>Title</th> + <th>When submitted</th> + <th>Reason for failure</th> + <th>Failure metadata</th> + </tr> + {% for media_entry in failed_entries %} + <tr> + <td>{{ media_entry.id }}</td> + <td>{{ media_entry.title }}</td> + <td>{{ media_entry.created.strftime("%F %R") }}</td> + {% if media_entry.get_fail_exception() %} + <td>{{ media_entry.get_fail_exception().general_message }}</td> + <td>{{ media_entry.fail_metadata }}</td> + {% else %} + <td> </td> + <td> </td> + {% endif %} + </tr> + {% endfor %} + </table> +{% else %} + <p><em>{% trans %}No failed entries!{% endtrans %}</em></p> +{% endif %} + +<h2>{% trans %}Your last 10 successful uploads{% endtrans %}</h2> +{% if processed_entries.count() %} + + <table class="media_panel processed"> + <tr> + <th>ID</th> + <th>Title</th> + <th>Submitted</th> + </tr> + {% for entry in processed_entries %} + <tr> + <td>{{ entry.id }}</td> + <td><a href="{{ entry.url_for_self(request.urlgen) }}">{{ entry.title }}</a></td> + <td>{{ entry.created.strftime("%F %R") }}</td> + </tr> + {% endfor %} + </table> +{% else %} + <p><em>{% trans %}No processed entries, yet!{% endtrans %}</em></p> +{% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/user_pages/user.html b/mediagoblin/templates/mediagoblin/user_pages/user.html new file mode 100644 index 00000000..71acd66c --- /dev/null +++ b/mediagoblin/templates/mediagoblin/user_pages/user.html @@ -0,0 +1,171 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} +{% extends "mediagoblin/base.html" %} + +{% from "mediagoblin/utils/object_gallery.html" import object_gallery %} + +{% block mediagoblin_head %} + <link rel="alternate" type="application/atom+xml" + href="{{ request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username) }}"> +{% endblock mediagoblin_head %} + +{% block title %} + {%- if user -%} + {%- trans username=user.username -%} + {{ username }}'s profile + {%- endtrans %} — {{ super() }} + {%- else -%} + {{ super() }} + {%- endif -%} +{% endblock %} + + +{% block mediagoblin_content -%} + {# If no user... #} + {% if not user %} + <p>{% trans %}Sorry, no such user found.{% endtrans %}</p> + + {# User exists, but needs verification #} + {% elif user.status == "needs_email_verification" %} + {% if user == request.user %} + {# this should only be visible when you are this user #} + <div class="form_box"> + <h1>{% trans %}Email verification needed{% endtrans %}</h1> + + <p> + {% trans -%} + Almost done! Your account still needs to be activated. + {%- endtrans %} + </p> + <p> + {% trans -%} + An email should arrive in a few moments with instructions on how to do so. + {%- endtrans %} + </p> + <p>{% trans %}In case it doesn't:{% endtrans %}</p> + + <a href="{{ request.urlgen('mediagoblin.auth.resend_verification') }}" + class="button_action_highlight">{% trans %}Resend verification email{% endtrans %}</a> + </div> + {% else %} + {# if the user is not you, but still needs to verify their email #} + <div class="form_box"> + <h1>{% trans %}Email verification needed{% endtrans %}</h1> + + <p> + {% trans -%} + Someone has registered an account with this username, but it still has to be activated. + {%- endtrans %} + </p> + + <p> + {% trans login_url=request.urlgen('mediagoblin.auth.login') -%} + If you are that person but you've lost your verification email, you can <a href="{{ login_url }}">log in</a> and resend it. + {%- endtrans %} + </p> + </div> + {% endif %} + + {# Active(?) (or at least verified at some point) user, horray! #} + {% else %} + <h1> + {%- trans username=user.username %}{{ username }}'s profile{% endtrans -%} + </h1> + + {% if not user.url and not user.bio %} + {% if request.user and (request.user.id == user.id) %} + <div class="profile_sidebar empty_space"> + <p> + {% trans %}Here's a spot to tell others about yourself.{% endtrans %} + </p> + <a href="{{ request.urlgen('mediagoblin.edit.profile', + user=user.username) }}" class="button_action"> + {%- trans %}Edit profile{% endtrans -%} + </a> + {% else %} + <div class="profile_sidebar empty_space"> + <p> + {% trans -%} + This user hasn't filled in their profile (yet). + {%- endtrans %} + </p> + {% endif %} + {% else %} + <div class="profile_sidebar"> + {% include "mediagoblin/utils/profile.html" %} + {% if request.user and + (request.user.id == user.id or request.user.is_admin) %} + <a href="{{ request.urlgen('mediagoblin.edit.profile', + user=user.username) }}"> + {%- trans %}Edit profile{% endtrans -%} + </a> + {% endif %} + {% endif %} + <p> + <a href="{{ request.urlgen('mediagoblin.user_pages.collection_list', + user=user.username) }}"> + {%- trans %}Browse collections{% endtrans -%} + </a> + </p> + </div> + + {% if media_entries.count() %} + <div class="profile_showcase"> + {{ object_gallery(request, media_entries, pagination, + pagination_base_url=user_gallery_url, col_number=3) }} + {% include "mediagoblin/utils/object_gallery.html" %} + <div class="clear"></div> + <p> + <a href="{{ user_gallery_url }}"> + {% trans username=user.username -%} + View all of {{ username }}'s media{% endtrans -%} + </a> + </p> + {% set feed_url = request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username) %} + {% include "mediagoblin/utils/feed_link.html" %} + </div> + {% else %} + {% if request.user and (request.user.id == user.id) %} + <div class="profile_showcase empty_space"> + <p> + {% trans -%} + This is where your media will appear, but you don't seem to have added anything yet. + {%- endtrans %} + </p> + <a class="button_action" + href="{{ request.urlgen('mediagoblin.submit.start') }}"> + {%- trans %}Add media{% endtrans -%} + </a> + </div> + {% else %} + <div class="profile_showcase empty_space"> + <p> + {% trans -%} + There doesn't seem to be any media here yet... + {%- endtrans %} + </p> + </div> + {% endif %} + {% endif %} + <div class="clear"></div> + {% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/collection_gallery.html b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html new file mode 100644 index 00000000..dcc59244 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/collection_gallery.html @@ -0,0 +1,90 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% from "mediagoblin/utils/pagination.html" import render_pagination %} + +{% macro media_grid(request, collection_items, col_number=5) %} + <table class="thumb_gallery"> + {% for row in collection_items|batch(col_number) %} + <tr class="thumb_row + {%- if loop.first %} thumb_row_first + {%- elif loop.last %} thumb_row_last{% endif %}"> + {% for item in row %} + {% set media_entry = item.get_media_entry %} + {% set entry_url = media_entry.url_for_self(request.urlgen) %} + <td class="media_thumbnail thumb_entry + {%- if loop.first %} thumb_entry_first + {%- elif loop.last %} thumb_entry_last{% endif %}"> + <a href="{{ entry_url }}"> + <img src="{{ media_entry.thumb_url }}" /> + </a> + + {% if item.note %} + <a href="{{ entry_url }}">{{ item.note }}</a> + {% endif %} + {% if request.user and + (item.in_collection.creator == request.user.id or + request.user.is_admin) %} + {%- set remove_url=request.urlgen( + 'mediagoblin.user_pages.collection_item_confirm_remove', + user=item.in_collection.get_creator.username, + collection=item.in_collection.slug, + collection_item=item.id) -%} + <a href="{{ remove_url }}" class="remove"> + {%- trans %}(remove){% endtrans -%} + </a> + {% endif %} + </td> + {% endfor %} + </tr> + {% endfor %} + </table> +{%- endmacro %} + +{# + Render a media gallery with pagination. + + Args: + - request: Request + - collection_items: cursor of collection items + - pagination: Paginator object + - pagination_base_url: If you want the pagination to point to a + different URL, point it here + - col_number: How many columns per row (default 5) +#} +{% macro collection_gallery(request, collection_items, pagination, + pagination_base_url=None, col_number=5) %} + {% if collection_items and collection_items.count() %} + {{ media_grid(request, collection_items, col_number=col_number) }} + <div class="clear"></div> + {% if pagination_base_url %} + {# different url, so set that and don't keep the get params #} + {{ render_pagination(request, pagination, pagination_base_url, False) }} + {% else %} + {{ render_pagination(request, pagination) }} + {% endif %} + {% else %} + <p> + <i> + {%- trans -%} + There doesn't seem to be any media here yet... + {%- endtrans -%} + </i> + </p> + {% endif %} +{% endmacro %} diff --git a/mediagoblin/templates/mediagoblin/utils/collections.html b/mediagoblin/templates/mediagoblin/utils/collections.html new file mode 100644 index 00000000..69738e26 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/collections.html @@ -0,0 +1,44 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% block collections_content -%} + {% if media.collections %} + <h3>{% trans %}Collected in{% endtrans %}</h3> + <p> + {%- for collection in media.collections %} + {%- if not loop.first %} + · + {%- endif %} + <a href="{{ collection.url_for_self(request.urlgen) }}"> + {{- collection.title }} ( + {{- collection.get_creator.username -}} + )</a> + {%- endfor %} + </p> + {%- endif %} + {%- if request.user %} + <p> + <a type="submit" href="{{ request.urlgen('mediagoblin.user_pages.media_collect', + user=media.get_uploader.username, + media_id=media.id) }}" + class="button_action"> + {% trans %}Add to a collection{% endtrans %} + </a> + </p> + {%- endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/exif.html b/mediagoblin/templates/mediagoblin/utils/exif.html new file mode 100644 index 00000000..b62208e1 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/exif.html @@ -0,0 +1,67 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% block exif_content %} +<noscript> + <style type="text/css"> + #exif_additional_info { + display: block; + } + </style> +</noscript> +<div id="exif_content"> + {% if app_config['exif_visible'] + and media.media_data + and media.media_data.exif_all is defined + and media.media_data.exif_all %} + <h3>Camera Information</h3> + <table id="exif_camera_information"> + <tbody> + {% for label, value in media.exif_display_data_short().iteritems() %} + <tr> + <td class="col1">{{ label }}</td> + <td>{{ value }}</td> + </tr> + {% endfor %} + </tbody> + </table> + <h3 id="exif_additional_info_button" class="button_action"> + Additional Information + </h3> + <div id="exif_additional_info"> + <table class="exif_info"> + {% for key, tag in media.exif_display_iter() %} + <tr> + <td class="col1">{{ key }}</td> + <td>{{ tag.printable }}</td> + </tr> + {% endfor %} + </table> + </div> + {% endif %} +<script type="text/javascript"> +$(document).ready(function(){ + +$("#exif_additional_info_button").click(function(){ + $("#exif_additional_info").slideToggle("slow"); +}); + +}); +</script> +</div> <!-- end exif_content div --> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/feed_link.html b/mediagoblin/templates/mediagoblin/utils/feed_link.html new file mode 100644 index 00000000..6a41cef5 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/feed_link.html @@ -0,0 +1,23 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +<a href="{{ feed_url }}"> + <img src="{{ request.staticdirect('/images/icon_feed.png') }}" + class="media_icon" alt="{% trans %}feed icon{% endtrans %}" /> +</a> +<a href="{{ feed_url }}">{%- trans %}Atom feed{% endtrans -%}</a> diff --git a/mediagoblin/templates/mediagoblin/utils/license.html b/mediagoblin/templates/mediagoblin/utils/license.html new file mode 100644 index 00000000..9dad7419 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/license.html @@ -0,0 +1,28 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% block license_content -%} + <h3>{% trans %}License{% endtrans %}</h3> + <p> + {% if media.license %} + <a href="{{ media.license }}">{{ media.get_license_data().abbreviation }}</a> + {% else %} + {% trans %}All rights reserved{% endtrans %} + {% endif %} + </p> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/messages.html b/mediagoblin/templates/mediagoblin/utils/messages.html new file mode 100644 index 00000000..cb45f59a --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/messages.html @@ -0,0 +1,28 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{# Display any queued messages #} +{% set messages = fetch_messages(request) %} +{% if messages %} + <ul class="mediagoblin_messages"> + {% for msg in messages %} + <li class="message_{{ msg.level }}">{{ msg.text }}</li> + {% endfor %} + </ul> +{% endif %} + diff --git a/mediagoblin/templates/mediagoblin/utils/object_gallery.html b/mediagoblin/templates/mediagoblin/utils/object_gallery.html new file mode 100644 index 00000000..d328b552 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/object_gallery.html @@ -0,0 +1,76 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% from "mediagoblin/utils/pagination.html" import render_pagination %} + +{% macro media_grid(request, media_entries, col_number=5) %} + <table class="thumb_gallery"> + {% for row in media_entries|batch(col_number) %} + <tr class="thumb_row + {%- if loop.first %} thumb_row_first + {%- elif loop.last %} thumb_row_last{% endif %}"> + {% for entry in row %} + {% set entry_url = entry.url_for_self(request.urlgen) %} + <td class="media_thumbnail thumb_entry + {%- if loop.first %} thumb_entry_first + {%- elif loop.last %} thumb_entry_last{% endif %}"> + <a href="{{ entry_url }}"> + <img src="{{ entry.thumb_url }}" /> + </a> + {% if entry.title %} + <a class="thumb_entry_title" href="{{ entry_url }}">{{ entry.title }}</a> + {% endif %} + </td> + {% endfor %} + </tr> + {% endfor %} + </table> +{%- endmacro %} + +{# + Render a media gallery with pagination. + + Args: + - request: Request + - media_entries: db cursor of media entries + - pagination: Paginator object + - pagination_base_url: If you want the pagination to point to a + different URL, point it here + - col_number: How many columns per row (default 5) +#} +{% macro object_gallery(request, media_entries, pagination, + pagination_base_url=None, col_number=5) %} + {% if media_entries and media_entries.count() %} + {{ media_grid(request, media_entries, col_number=col_number) }} + <div class="clear"></div> + {% if pagination_base_url %} + {# different url, so set that and don't keep the get params #} + {{ render_pagination(request, pagination, pagination_base_url, False) }} + {% else %} + {{ render_pagination(request, pagination) }} + {% endif %} + {% else %} + <p> + <i> + {%- trans -%} + There doesn't seem to be any media here yet... + {%- endtrans -%} + </i> + </p> + {% endif %} +{% endmacro %} diff --git a/mediagoblin/templates/mediagoblin/utils/pagination.html b/mediagoblin/templates/mediagoblin/utils/pagination.html new file mode 100644 index 00000000..2ac990ae --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/pagination.html @@ -0,0 +1,65 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% macro render_pagination(request, pagination, + base_url=None, preserve_get_params=True) %} + {# only display if {{pagination}} is defined #} + {% if pagination and pagination.pages > 1 %} + {% if not base_url %} + {% set base_url = request.full_path %} + {% endif %} + + {% if preserve_get_params %} + {% set get_params = request.GET %} + {% else %} + {% set get_params = {} %} + {% endif %} + + <div class="pagination"> + <p> + {% if pagination.has_prev %} + {% set prev_url = pagination.get_page_url_explicit( + base_url, get_params, + pagination.page - 1) %} + <a href="{{ prev_url }}">{% trans %}← Newer{% endtrans %}</a> + {% endif %} + {% if pagination.has_next %} + {% set next_url = pagination.get_page_url_explicit( + base_url, get_params, + pagination.page + 1) %} + <a href="{{ next_url }}">{% trans %}Older →{% endtrans %}</a> + {% endif %} + <br /> + {% trans %}Go to page:{% endtrans %} + {%- for page in pagination.iter_pages() %} + {% if page %} + {% if page != pagination.page %} + <a href="{{ pagination.get_page_url_explicit( + base_url, get_params, + page) }}">{{ page }}</a> + {% else %} + {{ page }} + {% endif %} + {% else %} + <span class="ellipsis">…</span> + {% endif %} + {%- endfor %} + </p> + </div> + {% endif %} +{% endmacro %} diff --git a/mediagoblin/templates/mediagoblin/utils/prev_next.html b/mediagoblin/templates/mediagoblin/utils/prev_next.html new file mode 100644 index 00000000..9e262ed9 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/prev_next.html @@ -0,0 +1,48 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{# Provide navigation links to neighboring media entries, if possible #} +{% set prev_entry_url = media.url_to_prev(request.urlgen) %} +{% set next_entry_url = media.url_to_next(request.urlgen) %} + +{% if prev_entry_url or next_entry_url %} + <div class="navigation"> + {# There are no previous entries for the very first media entry #} + {% if prev_entry_url %} + <a class="navigation_button navigation_left" href="{{ prev_entry_url }}"> + ← {% trans %}newer{% endtrans %} + </a> + {% else %} + {# This is the first entry. display greyed-out 'previous' image #} + <p class="navigation_button navigation_left"> + ← {% trans %}newer{% endtrans %} + </p> + {% endif %} + {# Likewise, this could be the very last media entry #} + {% if next_entry_url %} + <a class="navigation_button navigation_right" href="{{ next_entry_url }}"> + {% trans %}older{% endtrans %} → + </a> + {% else %} + {# This is the last entry. display greyed-out 'next' image #} + <p class="navigation_button navigation_right"> + {% trans %}older{% endtrans %} → + </p> + {% endif %} + </div> +{% endif %} diff --git a/mediagoblin/templates/mediagoblin/utils/profile.html b/mediagoblin/templates/mediagoblin/utils/profile.html new file mode 100644 index 00000000..7a3af01c --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/profile.html @@ -0,0 +1,30 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% block profile_content -%} + {% if user.bio %} + {% autoescape False %} + {{ user.bio_html }} + {% endautoescape %} + {% endif %} + {% if user.url %} + <p> + <a href="{{ user.url }}">{{ user.url }}</a> + </p> + {% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/tags.html b/mediagoblin/templates/mediagoblin/utils/tags.html new file mode 100644 index 00000000..bb4bd1a5 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/tags.html @@ -0,0 +1,46 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{% block tags_content -%} + <h3>{% trans %}Tagged with{% endtrans %}</h3> + <p> + {% for tag in media.tags %} + {% if loop.last %} + {# the 'and' should only appear if there is more than one tag #} + {% if media.tags|length > 1 %} + · + {% endif %} + <a href="{{ request.urlgen( + 'mediagoblin.user_pages.user_tag_gallery', + tag=tag['slug'], + user=media.get_uploader.username) }}">{{ tag['name'] }}</a> + {% elif loop.revindex == 2 %} + <a href="{{ request.urlgen( + 'mediagoblin.user_pages.user_tag_gallery', + tag=tag['slug'], + user=media.get_uploader.username) }}">{{ tag['name'] }}</a> + {% else %} + <a href="{{ request.urlgen( + 'mediagoblin.user_pages.user_tag_gallery', + tag=tag['slug'], + user=media.get_uploader.username) }}">{{ tag['name'] }}</a> + · + {% endif %} + {% endfor %} + </p> +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/wtforms.html b/mediagoblin/templates/mediagoblin/utils/wtforms.html new file mode 100644 index 00000000..be6976c2 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/wtforms.html @@ -0,0 +1,76 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +{# Render the label for a field #} +{% macro render_label(field) %} + {%- if field.label.text -%} + <label for="{{ field.label.field_id }}">{{ field.label.text }}</label> + {%- endif -%} +{%- endmacro %} + +{# Render the label in a <p> for a field #} +{% macro render_label_p(field) %} + {%- if field.label.text %} + <p class="form_field_label"> + {{- render_label(field) -}} + </p> + {%- endif %} +{%- endmacro %} + +{# Generically render a field #} +{% macro render_field_div(field) %} + {{- render_label_p(field) }} + <div class="form_field_input"> + {{ field }} + {%- if field.errors -%} + {% for error in field.errors %} + <p class="form_field_error">{{ error }}</p> + {% endfor %} + {%- endif %} + {%- if field.description %} + <p class="form_field_description">{{ field.description|safe }}</p> + {%- endif %} + </div> +{%- endmacro %} + +{# Auto-render a form as a series of divs #} +{% macro render_divs(form) -%} + {% for field in form %} + {{ render_field_div(field) }} + {% endfor %} +{%- endmacro %} + +{# Auto-render a form as a table #} +{% macro render_table(form) -%} + {% for field in form %} + <tr> + <th>{{ field.label.text }}</th> + <td> + {{field}} + {% if field.errors %} + <br /> + <ul class="errors"> + {% for error in field.errors %} + <li>{{error}}</li> + {% endfor %} + </ul> + {% endif %} + </td> + </tr> + {% endfor %} +{%- endmacro %} diff --git a/mediagoblin/templates/mediagoblin/webfinger/host-meta.xml b/mediagoblin/templates/mediagoblin/webfinger/host-meta.xml new file mode 100644 index 00000000..0f5fa7a3 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/webfinger/host-meta.xml @@ -0,0 +1,27 @@ +{# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +<?xml version="1.0" encoding="UTF-8"?> +<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" + xmlns:hm="http://host-meta.net/xrd/1.0"> + + <hm:Host>{{ request.host }}</hm:Host> + + <Link rel="lrdd" + template="{{ lrdd_template|replace(placeholder, '{uri}') }}"> + <Title>{{ lrdd_title }}</Title> + </Link> +</XRD> diff --git a/mediagoblin/templates/mediagoblin/webfinger/xrd.xml b/mediagoblin/templates/mediagoblin/webfinger/xrd.xml new file mode 100644 index 00000000..bb2c5905 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/webfinger/xrd.xml @@ -0,0 +1,27 @@ +{# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} +<?xml version="1.0" encoding="UTF-8"?> +<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"> + + <Subject>{{ subject }}</Subject> + <Alias>{{ alias }}</Alias> + {% for link in links %} + <Link + {%- for attr, value in link.attrs.items() %} {{ attr }}="{{ value}}" + {%- endfor %} /> + {%- endfor %} +</XRD> diff --git a/mediagoblin/tests/__init__.py b/mediagoblin/tests/__init__.py new file mode 100644 index 00000000..cf200791 --- /dev/null +++ b/mediagoblin/tests/__init__.py @@ -0,0 +1,22 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +def setup_package(): + + import warnings + from sqlalchemy.exc import SAWarning + warnings.simplefilter("error", SAWarning) diff --git a/mediagoblin/tests/appconfig_context_modified.ini b/mediagoblin/tests/appconfig_context_modified.ini new file mode 100644 index 00000000..80ca69b1 --- /dev/null +++ b/mediagoblin/tests/appconfig_context_modified.ini @@ -0,0 +1,26 @@ +[mediagoblin] +direct_remote_path = /test_static/ +email_sender_address = "notice@mediagoblin.example.org" +email_debug_mode = true + +# TODO: Switch to using an in-memory database +sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db" + +# Celery shouldn't be set up by the application as it's setup via +# mediagoblin.init.celery.from_celery +celery_setup_elsewhere = true + +[storage:publicstore] +base_dir = %(here)s/user_dev/media/public +base_url = /mgoblin_media/ + +[storage:queuestore] +base_dir = %(here)s/user_dev/media/queue + +[celery] +CELERY_ALWAYS_EAGER = true +CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db" +BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db" + +[plugins] +[[mediagoblin.tests.testplugins.modify_context]] diff --git a/mediagoblin/tests/appconfig_plugin_specs.ini b/mediagoblin/tests/appconfig_plugin_specs.ini new file mode 100644 index 00000000..5511cd97 --- /dev/null +++ b/mediagoblin/tests/appconfig_plugin_specs.ini @@ -0,0 +1,21 @@ +[mediagoblin] +direct_remote_path = /mgoblin_static/ +email_sender_address = "notice@mediagoblin.example.org" + +## Uncomment and change to your DB's appropiate setting. +## Default is a local sqlite db "mediagoblin.db". +# sql_engine = postgresql:///gmg + +# set to false to enable sending notices +email_debug_mode = true + +# Set to false to disable registrations +allow_registration = true + +[plugins] +[[mediagoblin.tests.testplugins.pluginspec]] +some_string = "not blork" +some_int = "not an int" + +# this one shouldn't have its own config +[[mediagoblin.tests.testplugins.callables1]] diff --git a/mediagoblin/tests/appconfig_static_plugin.ini b/mediagoblin/tests/appconfig_static_plugin.ini new file mode 100644 index 00000000..dc251171 --- /dev/null +++ b/mediagoblin/tests/appconfig_static_plugin.ini @@ -0,0 +1,26 @@ +[mediagoblin] +direct_remote_path = /test_static/ +email_sender_address = "notice@mediagoblin.example.org" +email_debug_mode = true + +# TODO: Switch to using an in-memory database +sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db" + +# Celery shouldn't be set up by the application as it's setup via +# mediagoblin.init.celery.from_celery +celery_setup_elsewhere = true + +[storage:publicstore] +base_dir = %(here)s/user_dev/media/public +base_url = /mgoblin_media/ + +[storage:queuestore] +base_dir = %(here)s/user_dev/media/queue + +[celery] +CELERY_ALWAYS_EAGER = true +CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db" +BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db" + +[plugins] +[[mediagoblin.tests.testplugins.staticstuff]] diff --git a/mediagoblin/tests/conftest.py b/mediagoblin/tests/conftest.py new file mode 100644 index 00000000..dbb0aa0a --- /dev/null +++ b/mediagoblin/tests/conftest.py @@ -0,0 +1,41 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import pytest + +from mediagoblin.tests import tools +from mediagoblin.tools.testing import _activate_testing + + +@pytest.fixture() +def test_app(request): + """ + py.test fixture to pass sandboxed mediagoblin applications into tests that + want them. + + You could make a local version of this method for your own tests + to override the paste and config files being used by passing them + in differently to get_app. + """ + return tools.get_app(request) + + +@pytest.fixture() +def pt_fixture_enable_testing(): + """ + py.test fixture to enable testing mode in tools. + """ + _activate_testing() diff --git a/mediagoblin/tests/fake_carrot_conf_bad.ini b/mediagoblin/tests/fake_carrot_conf_bad.ini new file mode 100644 index 00000000..9d8cf518 --- /dev/null +++ b/mediagoblin/tests/fake_carrot_conf_bad.ini @@ -0,0 +1,14 @@ +[carrotapp] +# Whether or not our carrots are going to be turned into cake. +## These should throw errors +carrotcake = slobber +num_carrots = GROSS + +# A message encouraging our users to eat their carrots. +encouragement_phrase = 586956856856 # shouldn't throw error + +# Something extra! +blah_blah = "blah!" # shouldn't throw error either + +[celery] +EAT_CELERY_WITH_CARROTS = pants # yeah that's def an error right there. diff --git a/mediagoblin/tests/fake_carrot_conf_empty.ini b/mediagoblin/tests/fake_carrot_conf_empty.ini new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mediagoblin/tests/fake_carrot_conf_empty.ini diff --git a/mediagoblin/tests/fake_carrot_conf_good.ini b/mediagoblin/tests/fake_carrot_conf_good.ini new file mode 100644 index 00000000..1377907b --- /dev/null +++ b/mediagoblin/tests/fake_carrot_conf_good.ini @@ -0,0 +1,13 @@ +[carrotapp] +# Whether or not our carrots are going to be turned into cake. +carrotcake = true +num_carrots = 88 + +# A message encouraging our users to eat their carrots. +encouragement_phrase = "I'd love it if you eat your carrots!" + +# Something extra! +blah_blah = "blah!" + +[celery] +EAT_CELERY_WITH_CARROTS = False diff --git a/mediagoblin/tests/fake_celery_conf.ini b/mediagoblin/tests/fake_celery_conf.ini new file mode 100644 index 00000000..67b0cba6 --- /dev/null +++ b/mediagoblin/tests/fake_celery_conf.ini @@ -0,0 +1,9 @@ +[mediagoblin] +# I got nothin' in this file! + +[celery] +SOME_VARIABLE = floop +MAIL_PORT = 2000 +CELERYD_ETA_SCHEDULER_PRECISION = 1.3 +CELERY_RESULT_PERSISTENT = true +CELERY_IMPORTS = foo.bar.baz, this.is.an.import diff --git a/mediagoblin/tests/fake_celery_module.py b/mediagoblin/tests/fake_celery_module.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/tests/fake_celery_module.py @@ -0,0 +1,15 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/mediagoblin/tests/fake_config_spec.ini b/mediagoblin/tests/fake_config_spec.ini new file mode 100644 index 00000000..43f2e236 --- /dev/null +++ b/mediagoblin/tests/fake_config_spec.ini @@ -0,0 +1,10 @@ +[carrotapp] +# Whether or not our carrots are going to be turned into cake. +carrotcake = boolean(default=False) +num_carrots = integer(default=1) + +# A message encouraging our users to eat their carrots. +encouragement_phrase = string() + +[celery] +EAT_CELERY_WITH_CARROTS = boolean(default=True)
\ No newline at end of file diff --git a/mediagoblin/tests/pytest.ini b/mediagoblin/tests/pytest.ini new file mode 100644 index 00000000..e561c074 --- /dev/null +++ b/mediagoblin/tests/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +usefixtures = tmpdir pt_fixture_enable_testing diff --git a/mediagoblin/tests/resources.py b/mediagoblin/tests/resources.py new file mode 100644 index 00000000..f7b3037d --- /dev/null +++ b/mediagoblin/tests/resources.py @@ -0,0 +1,41 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from pkg_resources import resource_filename + + +def resource(filename): + return resource_filename('mediagoblin.tests', 'test_submission/' + filename) + + +GOOD_JPG = resource('good.jpg') +GOOD_PNG = resource('good.png') +EVIL_FILE = resource('evil') +EVIL_JPG = resource('evil.jpg') +EVIL_PNG = resource('evil.png') +BIG_BLUE = resource('bigblue.png') +GOOD_PDF = resource('good.pdf') + + +def resource_exif(f): + return resource_filename('mediagoblin.tests', 'test_exif/' + f) + + +GOOD_JPG = resource_exif('good.jpg') +EMPTY_JPG = resource_exif('empty.jpg') +BAD_JPG = resource_exif('bad.jpg') +GPS_JPG = resource_exif('has-gps.jpg') diff --git a/mediagoblin/tests/test_api.py b/mediagoblin/tests/test_api.py new file mode 100644 index 00000000..89cf1026 --- /dev/null +++ b/mediagoblin/tests/test_api.py @@ -0,0 +1,92 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import logging +import base64 + +import pytest + +from mediagoblin import mg_globals +from mediagoblin.tools import template, pluginapi +from mediagoblin.tests.tools import fixture_add_user +from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \ + BIG_BLUE + + +_log = logging.getLogger(__name__) + + +class TestAPI(object): + def setup(self): + self.db = mg_globals.database + + self.user_password = u'4cc355_70k3N' + self.user = fixture_add_user(u'joapi', self.user_password) + + def login(self, test_app): + test_app.post( + '/auth/login/', { + 'username': self.user.username, + 'password': self.user_password}) + + def get_context(self, template_name): + return template.TEMPLATE_TEST_CONTEXT[template_name] + + def http_auth_headers(self): + return {'Authorization': 'Basic {0}'.format( + base64.b64encode(':'.join([ + self.user.username, + self.user_password])))} + + def do_post(self, data, test_app, **kwargs): + url = kwargs.pop('url', '/api/submit') + do_follow = kwargs.pop('do_follow', False) + + if not 'headers' in kwargs.keys(): + kwargs['headers'] = self.http_auth_headers() + + response = test_app.post(url, data, **kwargs) + + if do_follow: + response.follow() + + return response + + def upload_data(self, filename): + return {'upload_files': [('file', filename)]} + + def test_1_test_test_view(self, test_app): + self.login(test_app) + + response = test_app.get( + '/api/test', + headers=self.http_auth_headers()) + + assert response.body == \ + '{"username": "joapi", "email": "joapi@example.com"}' + + def test_2_test_submission(self, test_app): + self.login(test_app) + + response = self.do_post( + {'title': 'Great JPG!'}, + test_app, + **self.upload_data(GOOD_JPG)) + + assert response.status_int == 200 + + assert self.db.MediaEntry.query.filter_by(title=u'Great JPG!').first() diff --git a/mediagoblin/tests/test_auth.py b/mediagoblin/tests/test_auth.py new file mode 100644 index 00000000..755727f9 --- /dev/null +++ b/mediagoblin/tests/test_auth.py @@ -0,0 +1,396 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import urlparse +import datetime + +from mediagoblin import mg_globals +from mediagoblin.auth import lib as auth_lib +from mediagoblin.db.models import User +from mediagoblin.tests.tools import fixture_add_user +from mediagoblin.tools import template, mail + + +######################## +# Test bcrypt auth funcs +######################## + +def test_bcrypt_check_password(): + # Check known 'lollerskates' password against check function + assert auth_lib.bcrypt_check_password( + 'lollerskates', + '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO') + + assert not auth_lib.bcrypt_check_password( + 'notthepassword', + '$2a$12$PXU03zfrVCujBhVeICTwtOaHTUs5FFwsscvSSTJkqx/2RQ0Lhy/nO') + + # Same thing, but with extra fake salt. + assert not auth_lib.bcrypt_check_password( + 'notthepassword', + '$2a$12$ELVlnw3z1FMu6CEGs/L8XO8vl0BuWSlUHgh0rUrry9DUXGMUNWwl6', + '3><7R45417') + + +def test_bcrypt_gen_password_hash(): + pw = 'youwillneverguessthis' + + # Normal password hash generation, and check on that hash + hashed_pw = auth_lib.bcrypt_gen_password_hash(pw) + assert auth_lib.bcrypt_check_password( + pw, hashed_pw) + assert not auth_lib.bcrypt_check_password( + 'notthepassword', hashed_pw) + + # Same thing, extra salt. + hashed_pw = auth_lib.bcrypt_gen_password_hash(pw, '3><7R45417') + assert auth_lib.bcrypt_check_password( + pw, hashed_pw, '3><7R45417') + assert not auth_lib.bcrypt_check_password( + 'notthepassword', hashed_pw, '3><7R45417') + + +def test_register_views(test_app): + """ + Massive test function that all our registration-related views all work. + """ + # Test doing a simple GET on the page + # ----------------------------------- + + test_app.get('/auth/register/') + # Make sure it rendered with the appropriate template + assert 'mediagoblin/auth/register.html' in template.TEMPLATE_TEST_CONTEXT + + # Try to register without providing anything, should error + # -------------------------------------------------------- + + template.clear_test_template_context() + test_app.post( + '/auth/register/', {}) + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] + form = context['register_form'] + assert form.username.errors == [u'This field is required.'] + assert form.password.errors == [u'This field is required.'] + assert form.email.errors == [u'This field is required.'] + + # Try to register with fields that are known to be invalid + # -------------------------------------------------------- + + ## too short + template.clear_test_template_context() + test_app.post( + '/auth/register/', { + 'username': 'l', + 'password': 'o', + 'email': 'l'}) + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] + form = context['register_form'] + + assert form.username.errors == [u'Field must be between 3 and 30 characters long.'] + assert form.password.errors == [u'Field must be between 5 and 1024 characters long.'] + + ## bad form + template.clear_test_template_context() + test_app.post( + '/auth/register/', { + 'username': '@_@', + 'email': 'lollerskates'}) + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/register.html'] + form = context['register_form'] + + assert form.username.errors == [u'This field does not take email addresses.'] + assert form.email.errors == [u'This field requires an email address.'] + + ## At this point there should be no users in the database ;) + assert User.query.count() == 0 + + # Successful register + # ------------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/register/', { + 'username': u'happygirl', + 'password': 'iamsohappy', + 'email': 'happygrrl@example.org'}) + response.follow() + + ## Did we redirect to the proper page? Use the right template? + assert urlparse.urlsplit(response.location)[2] == '/u/happygirl/' + assert 'mediagoblin/user_pages/user.html' in template.TEMPLATE_TEST_CONTEXT + + ## Make sure user is in place + new_user = mg_globals.database.User.find_one( + {'username': u'happygirl'}) + assert new_user + assert new_user.status == u'needs_email_verification' + assert new_user.email_verified == False + + ## Make sure user is logged in + request = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/user_pages/user.html']['request'] + assert request.session['user_id'] == unicode(new_user.id) + + ## Make sure we get email confirmation, and try verifying + assert len(mail.EMAIL_TEST_INBOX) == 1 + message = mail.EMAIL_TEST_INBOX.pop() + assert message['To'] == 'happygrrl@example.org' + email_context = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/auth/verification_email.txt'] + assert email_context['verification_url'] in message.get_payload(decode=True) + + path = urlparse.urlsplit(email_context['verification_url'])[2] + get_params = urlparse.urlsplit(email_context['verification_url'])[3] + assert path == u'/auth/verify_email/' + parsed_get_params = urlparse.parse_qs(get_params) + + ### user should have these same parameters + assert parsed_get_params['userid'] == [ + unicode(new_user.id)] + assert parsed_get_params['token'] == [ + new_user.verification_key] + + ## Try verifying with bs verification key, shouldn't work + template.clear_test_template_context() + response = test_app.get( + "/auth/verify_email/?userid=%s&token=total_bs" % unicode( + new_user.id)) + response.follow() + context = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/user_pages/user.html'] + # assert context['verification_successful'] == True + # TODO: Would be good to test messages here when we can do so... + new_user = mg_globals.database.User.find_one( + {'username': u'happygirl'}) + assert new_user + assert new_user.status == u'needs_email_verification' + assert new_user.email_verified == False + + ## Verify the email activation works + template.clear_test_template_context() + response = test_app.get("%s?%s" % (path, get_params)) + response.follow() + context = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/user_pages/user.html'] + # assert context['verification_successful'] == True + # TODO: Would be good to test messages here when we can do so... + new_user = mg_globals.database.User.find_one( + {'username': u'happygirl'}) + assert new_user + assert new_user.status == u'active' + assert new_user.email_verified == True + + # Uniqueness checks + # ----------------- + ## We shouldn't be able to register with that user twice + template.clear_test_template_context() + response = test_app.post( + '/auth/register/', { + 'username': u'happygirl', + 'password': 'iamsohappy2', + 'email': 'happygrrl2@example.org'}) + + context = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/auth/register.html'] + form = context['register_form'] + assert form.username.errors == [ + u'Sorry, a user with that name already exists.'] + + ## TODO: Also check for double instances of an email address? + + ### Oops, forgot the password + # ------------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/forgot_password/', + {'username': u'happygirl'}) + response.follow() + + ## Did we redirect to the proper page? Use the right template? + assert urlparse.urlsplit(response.location)[2] == '/auth/login/' + assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT + + ## Make sure link to change password is sent by email + assert len(mail.EMAIL_TEST_INBOX) == 1 + message = mail.EMAIL_TEST_INBOX.pop() + assert message['To'] == 'happygrrl@example.org' + email_context = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/auth/fp_verification_email.txt'] + #TODO - change the name of verification_url to something forgot-password-ish + assert email_context['verification_url'] in message.get_payload(decode=True) + + path = urlparse.urlsplit(email_context['verification_url'])[2] + get_params = urlparse.urlsplit(email_context['verification_url'])[3] + assert path == u'/auth/forgot_password/verify/' + parsed_get_params = urlparse.parse_qs(get_params) + + # user should have matching parameters + new_user = mg_globals.database.User.find_one({'username': u'happygirl'}) + assert parsed_get_params['userid'] == [unicode(new_user.id)] + assert parsed_get_params['token'] == [new_user.fp_verification_key] + + ### The forgotten password token should be set to expire in ~ 10 days + # A few ticks have expired so there are only 9 full days left... + assert (new_user.fp_token_expire - datetime.datetime.now()).days == 9 + + ## Try using a bs password-changing verification key, shouldn't work + template.clear_test_template_context() + response = test_app.get( + "/auth/forgot_password/verify/?userid=%s&token=total_bs" % unicode( + new_user.id), status=404) + assert response.status.split()[0] == u'404' # status="404 NOT FOUND" + + ## Try using an expired token to change password, shouldn't work + template.clear_test_template_context() + new_user = mg_globals.database.User.find_one({'username': u'happygirl'}) + real_token_expiration = new_user.fp_token_expire + new_user.fp_token_expire = datetime.datetime.now() + new_user.save() + response = test_app.get("%s?%s" % (path, get_params), status=404) + assert response.status.split()[0] == u'404' # status="404 NOT FOUND" + new_user.fp_token_expire = real_token_expiration + new_user.save() + + ## Verify step 1 of password-change works -- can see form to change password + template.clear_test_template_context() + response = test_app.get("%s?%s" % (path, get_params)) + assert 'mediagoblin/auth/change_fp.html' in template.TEMPLATE_TEST_CONTEXT + + ## Verify step 2.1 of password-change works -- report success to user + template.clear_test_template_context() + response = test_app.post( + '/auth/forgot_password/verify/', { + 'userid': parsed_get_params['userid'], + 'password': 'iamveryveryhappy', + 'token': parsed_get_params['token']}) + response.follow() + assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT + + ## Verify step 2.2 of password-change works -- login w/ new password success + template.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'happygirl', + 'password': 'iamveryveryhappy'}) + + # User should be redirected + response.follow() + assert urlparse.urlsplit(response.location)[2] == '/' + assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT + + +def test_authentication_views(test_app): + """ + Test logging in and logging out + """ + # Make a new user + test_user = fixture_add_user(active_user=False) + + # Get login + # --------- + test_app.get('/auth/login/') + assert 'mediagoblin/auth/login.html' in template.TEMPLATE_TEST_CONTEXT + + # Failed login - blank form + # ------------------------- + template.clear_test_template_context() + response = test_app.post('/auth/login/') + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + form = context['login_form'] + assert form.username.errors == [u'This field is required.'] + assert form.password.errors == [u'This field is required.'] + + # Failed login - blank user + # ------------------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'password': u'toast'}) + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + form = context['login_form'] + assert form.username.errors == [u'This field is required.'] + + # Failed login - blank password + # ----------------------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris'}) + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + form = context['login_form'] + assert form.password.errors == [u'This field is required.'] + + # Failed login - bad user + # ----------------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'steve', + 'password': 'toast'}) + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + assert context['login_failed'] + + # Failed login - bad password + # --------------------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris', + 'password': 'jam_and_ham'}) + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/auth/login.html'] + assert context['login_failed'] + + # Successful login + # ---------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris', + 'password': 'toast'}) + + # User should be redirected + response.follow() + assert urlparse.urlsplit(response.location)[2] == '/' + assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT + + # Make sure user is in the session + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] + session = context['request'].session + assert session['user_id'] == unicode(test_user.id) + + # Successful logout + # ----------------- + template.clear_test_template_context() + response = test_app.get('/auth/logout/') + + # Should be redirected to index page + response.follow() + assert urlparse.urlsplit(response.location)[2] == '/' + assert 'mediagoblin/root.html' in template.TEMPLATE_TEST_CONTEXT + + # Make sure the user is not in the session + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] + session = context['request'].session + assert 'user_id' not in session + + # User is redirected to custom URL if POST['next'] is set + # ------------------------------------------------------- + template.clear_test_template_context() + response = test_app.post( + '/auth/login/', { + 'username': u'chris', + 'password': 'toast', + 'next' : '/u/chris/'}) + assert urlparse.urlsplit(response.location)[2] == '/u/chris/' diff --git a/mediagoblin/tests/test_celery_setup.py b/mediagoblin/tests/test_celery_setup.py new file mode 100644 index 00000000..5530c6f2 --- /dev/null +++ b/mediagoblin/tests/test_celery_setup.py @@ -0,0 +1,60 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import pkg_resources + +from mediagoblin.init import celery as celery_setup +from mediagoblin.init.config import read_mediagoblin_config + + +TEST_CELERY_CONF_NOSPECIALDB = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_celery_conf.ini') + + +def test_setup_celery_from_config(): + def _wipe_testmodule_clean(module): + vars_to_wipe = [ + var for var in dir(module) + if not var.startswith('__') and not var.endswith('__')] + for var in vars_to_wipe: + delattr(module, var) + + global_config, validation_result = read_mediagoblin_config( + TEST_CELERY_CONF_NOSPECIALDB) + app_config = global_config['mediagoblin'] + + celery_setup.setup_celery_from_config( + app_config, global_config, + 'mediagoblin.tests.fake_celery_module', set_environ=False) + + from mediagoblin.tests import fake_celery_module + assert fake_celery_module.SOME_VARIABLE == 'floop' + assert fake_celery_module.MAIL_PORT == 2000 + assert isinstance(fake_celery_module.MAIL_PORT, int) + assert fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION == 1.3 + assert isinstance(fake_celery_module.CELERYD_ETA_SCHEDULER_PRECISION, float) + assert fake_celery_module.CELERY_RESULT_PERSISTENT is True + assert fake_celery_module.CELERY_IMPORTS == [ + 'foo.bar.baz', 'this.is.an.import', 'mediagoblin.processing.task'] + assert fake_celery_module.CELERY_RESULT_BACKEND == 'database' + assert fake_celery_module.CELERY_RESULT_DBURI == ( + 'sqlite:///' + + pkg_resources.resource_filename('mediagoblin.tests', 'celery.db')) + + assert fake_celery_module.BROKER_TRANSPORT == 'sqlalchemy' + assert fake_celery_module.BROKER_HOST == ( + 'sqlite:///' + + pkg_resources.resource_filename('mediagoblin.tests', 'kombu.db')) diff --git a/mediagoblin/tests/test_collections.py b/mediagoblin/tests/test_collections.py new file mode 100644 index 00000000..87782f30 --- /dev/null +++ b/mediagoblin/tests/test_collections.py @@ -0,0 +1,32 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tests.tools import fixture_add_collection, fixture_add_user +from mediagoblin.db.models import Collection, User + + +def test_user_deletes_collection(test_app): + # Setup db. + user = fixture_add_user() + coll = fixture_add_collection(user=user) + # Reload into session: + user = User.query.get(user.id) + + cnt1 = Collection.query.count() + user.delete() + cnt2 = Collection.query.count() + + assert cnt1 == cnt2 + 1 diff --git a/mediagoblin/tests/test_config.py b/mediagoblin/tests/test_config.py new file mode 100644 index 00000000..b13adae6 --- /dev/null +++ b/mediagoblin/tests/test_config.py @@ -0,0 +1,97 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import pkg_resources + +from mediagoblin.init import config + + +CARROT_CONF_GOOD = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_carrot_conf_good.ini') +CARROT_CONF_EMPTY = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_carrot_conf_empty.ini') +CARROT_CONF_BAD = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_carrot_conf_bad.ini') +FAKE_CONFIG_SPEC = pkg_resources.resource_filename( + 'mediagoblin.tests', 'fake_config_spec.ini') + + +def test_read_mediagoblin_config(): + # An empty file + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_EMPTY, FAKE_CONFIG_SPEC) + + assert this_conf['carrotapp']['carrotcake'] == False + assert this_conf['carrotapp']['num_carrots'] == 1 + assert 'encouragement_phrase' not in this_conf['carrotapp'] + assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == True + + # A good file + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_GOOD, FAKE_CONFIG_SPEC) + + assert this_conf['carrotapp']['carrotcake'] == True + assert this_conf['carrotapp']['num_carrots'] == 88 + assert this_conf['carrotapp']['encouragement_phrase'] == \ + "I'd love it if you eat your carrots!" + assert this_conf['carrotapp']['blah_blah'] == "blah!" + assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == False + + # A bad file + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_BAD, FAKE_CONFIG_SPEC) + + # These should still open but will have errors that we'll test for + # in test_generate_validation_report() + assert this_conf['carrotapp']['carrotcake'] == 'slobber' + assert this_conf['carrotapp']['num_carrots'] == 'GROSS' + assert this_conf['carrotapp']['encouragement_phrase'] == \ + "586956856856" + assert this_conf['carrotapp']['blah_blah'] == "blah!" + assert this_conf['celery']['EAT_CELERY_WITH_CARROTS'] == "pants" + + +def test_generate_validation_report(): + # Empty + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_EMPTY, FAKE_CONFIG_SPEC) + report = config.generate_validation_report(this_conf, validation_results) + assert report is None + + # Good + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_GOOD, FAKE_CONFIG_SPEC) + report = config.generate_validation_report(this_conf, validation_results) + assert report is None + + # Bad + this_conf, validation_results = config.read_mediagoblin_config( + CARROT_CONF_BAD, FAKE_CONFIG_SPEC) + report = config.generate_validation_report(this_conf, validation_results) + + assert report.startswith("""\ +There were validation problems loading this config file: +--------------------------------------------------------""") + + expected_warnings = [ + 'carrotapp:carrotcake = the value "slobber" is of the wrong type.', + 'carrotapp:num_carrots = the value "GROSS" is of the wrong type.', + 'celery:EAT_CELERY_WITH_CARROTS = the value "pants" is of the wrong type.'] + warnings = report.splitlines()[2:] + + assert len(warnings) == 3 + for warning in expected_warnings: + assert warning in warnings diff --git a/mediagoblin/tests/test_csrf_middleware.py b/mediagoblin/tests/test_csrf_middleware.py new file mode 100644 index 00000000..a272caf6 --- /dev/null +++ b/mediagoblin/tests/test_csrf_middleware.py @@ -0,0 +1,86 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin import mg_globals + + +def test_csrf_cookie_set(test_app): + cookie_name = mg_globals.app_config['csrf_cookie_name'] + + # get login page + response = test_app.get('/auth/login/') + + # assert that the mediagoblin nonce cookie has been set + assert 'Set-Cookie' in response.headers + assert cookie_name in response.cookies_set + + # assert that we're also sending a vary header + assert response.headers.get('Vary', False) == 'Cookie' + + +# We need a fresh app for this test on webtest < 1.3.6. +# We do not understand why, but it fixes the tests. +# If we require webtest >= 1.3.6, we can switch to a non fresh app here. +# +# ... this comment might be irrelevant post-pytest-fixtures, but I'm not +# removing it yet in case we move to module-level tests :) +# -- cwebber +def test_csrf_token_must_match(test_app): + + # construct a request with no cookie or form token + assert test_app.post('/auth/login/', + extra_environ={'gmg.verify_csrf': True}, + expect_errors=True).status_int == 403 + + # construct a request with a cookie, but no form token + assert test_app.post('/auth/login/', + headers={'Cookie': str('%s=foo' % + mg_globals.app_config['csrf_cookie_name'])}, + extra_environ={'gmg.verify_csrf': True}, + expect_errors=True).status_int == 403 + + # if both the cookie and form token are provided, they must match + assert test_app.post('/auth/login/', + {'csrf_token': 'blarf'}, + headers={'Cookie': str('%s=foo' % + mg_globals.app_config['csrf_cookie_name'])}, + extra_environ={'gmg.verify_csrf': True}, + expect_errors=True).\ + status_int == 403 + + assert test_app.post('/auth/login/', + {'csrf_token': 'foo'}, + headers={'Cookie': str('%s=foo' % + mg_globals.app_config['csrf_cookie_name'])}, + extra_environ={'gmg.verify_csrf': True}).\ + status_int == 200 + +def test_csrf_exempt(test_app): + # monkey with the views to decorate a known endpoint + import mediagoblin.auth.views + from mediagoblin.meddleware.csrf import csrf_exempt + + mediagoblin.auth.views.login = csrf_exempt( + mediagoblin.auth.views.login + ) + + # construct a request with no cookie or form token + assert test_app.post('/auth/login/', + extra_environ={'gmg.verify_csrf': True}, + expect_errors=False).status_int == 200 + + # restore the CSRF protection in case other tests expect it + mediagoblin.auth.views.login.csrf_enabled = True diff --git a/mediagoblin/tests/test_edit.py b/mediagoblin/tests/test_edit.py new file mode 100644 index 00000000..08b4f8cf --- /dev/null +++ b/mediagoblin/tests/test_edit.py @@ -0,0 +1,144 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import urlparse +import pytest + +from mediagoblin import mg_globals +from mediagoblin.db.models import User +from mediagoblin.tests.tools import fixture_add_user +from mediagoblin.tools import template +from mediagoblin.auth.lib import bcrypt_check_password + +class TestUserEdit(object): + def setup(self): + # set up new user + self.user_password = u'toast' + self.user = fixture_add_user(password = self.user_password) + + def login(self, test_app): + test_app.post( + '/auth/login/', { + 'username': self.user.username, + 'password': self.user_password}) + + + def test_user_deletion(self, test_app): + """Delete user via web interface""" + self.login(test_app) + + # Make sure user exists + assert User.query.filter_by(username=u'chris').first() + + res = test_app.post('/edit/account/delete/', {'confirmed': 'y'}) + + # Make sure user has been deleted + assert User.query.filter_by(username=u'chris').first() == None + + #TODO: make sure all corresponding items comments etc have been + # deleted too. Perhaps in submission test? + + #Restore user at end of test + self.user = fixture_add_user(password = self.user_password) + self.login(test_app) + + + def test_change_password(self, test_app): + """Test changing password correctly and incorrectly""" + self.login(test_app) + + # test that the password can be changed + template.clear_test_template_context() + res = test_app.post( + '/edit/password/', { + 'old_password': 'toast', + 'new_password': '123456', + }) + res.follow() + + # Did we redirect to the correct page? + assert urlparse.urlsplit(res.location)[2] == '/edit/account/' + + # test_user has to be fetched again in order to have the current values + test_user = User.query.filter_by(username=u'chris').first() + assert bcrypt_check_password('123456', test_user.pw_hash) + # Update current user passwd + self.user_password = '123456' + + # test that the password cannot be changed if the given + # old_password is wrong + template.clear_test_template_context() + test_app.post( + '/edit/password/', { + 'old_password': 'toast', + 'new_password': '098765', + }) + + test_user = User.query.filter_by(username=u'chris').first() + assert not bcrypt_check_password('098765', test_user.pw_hash) + + + def test_change_bio_url(self, test_app): + """Test changing bio and URL""" + self.login(test_app) + + # Test if legacy profile editing URL redirects correctly + res = test_app.post( + '/edit/profile/', { + 'bio': u'I love toast!', + 'url': u'http://dustycloud.org/'}, expect_errors=True) + + # Should redirect to /u/chris/edit/ + assert res.status_int == 302 + assert res.headers['Location'].endswith("/u/chris/edit/") + + res = test_app.post( + '/u/chris/edit/', { + 'bio': u'I love toast!', + 'url': u'http://dustycloud.org/'}) + + test_user = User.query.filter_by(username=u'chris').first() + assert test_user.bio == u'I love toast!' + assert test_user.url == u'http://dustycloud.org/' + + # change a different user than the logged in (should fail with 403) + fixture_add_user(username=u"foo") + res = test_app.post( + '/u/foo/edit/', { + 'bio': u'I love toast!', + 'url': u'http://dustycloud.org/'}, expect_errors=True) + assert res.status_int == 403 + + # test changing the bio and the URL inproperly + too_long_bio = 150 * 'T' + 150 * 'o' + 150 * 'a' + 150 * 's' + 150* 't' + + test_app.post( + '/u/chris/edit/', { + # more than 500 characters + 'bio': too_long_bio, + 'url': 'this-is-no-url'}) + + # Check form errors + context = template.TEMPLATE_TEST_CONTEXT[ + 'mediagoblin/edit/edit_profile.html'] + form = context['form'] + + assert form.bio.errors == [ + u'Field must be between 0 and 500 characters long.'] + assert form.url.errors == [ + u'This address contains errors'] + +# test changing the url inproperly diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py new file mode 100644 index 00000000..c07e24ae --- /dev/null +++ b/mediagoblin/tests/test_exif.py @@ -0,0 +1,431 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +try: + from PIL import Image +except ImportError: + import Image + +from mediagoblin.tools.exif import exif_fix_image_orientation, \ + extract_exif, clean_exif, get_gps_data, get_useful +from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG + + +def assert_in(a, b): + assert a in b, "%r not in %r" % (a, b) + + +def test_exif_extraction(): + ''' + Test EXIF extraction from a good image + ''' + result = extract_exif(GOOD_JPG) + clean = clean_exif(result) + useful = get_useful(clean) + gps = get_gps_data(result) + + # Do we have the result? + assert len(result) == 56 + + # Do we have clean data? + assert len(clean) == 53 + + # GPS data? + assert gps == {} + + # Do we have the "useful" tags? + assert useful == {'EXIF CVAPattern': {'field_length': 8, + 'field_offset': 26224, + 'field_type': 7, + 'printable': u'[0, 2, 0, 2, 1, 2, 0, 1]', + 'tag': 41730, + 'values': [0, 2, 0, 2, 1, 2, 0, 1]}, + 'EXIF ColorSpace': {'field_length': 2, + 'field_offset': 476, + 'field_type': 3, + 'printable': u'sRGB', + 'tag': 40961, + 'values': [1]}, + 'EXIF ComponentsConfiguration': {'field_length': 4, + 'field_offset': 308, + 'field_type': 7, + 'printable': u'YCbCr', + 'tag': 37121, + 'values': [1, 2, 3, 0]}, + 'EXIF CompressedBitsPerPixel': {'field_length': 8, + 'field_offset': 756, + 'field_type': 5, + 'printable': u'4', + 'tag': 37122, + 'values': [[4, 1]]}, + 'EXIF Contrast': {'field_length': 2, + 'field_offset': 656, + 'field_type': 3, + 'printable': u'Soft', + 'tag': 41992, + 'values': [1]}, + 'EXIF CustomRendered': {'field_length': 2, + 'field_offset': 572, + 'field_type': 3, + 'printable': u'Normal', + 'tag': 41985, + 'values': [0]}, + 'EXIF DateTimeDigitized': {'field_length': 20, + 'field_offset': 736, + 'field_type': 2, + 'printable': u'2011:06:22 12:20:33', + 'tag': 36868, + 'values': u'2011:06:22 12:20:33'}, + 'EXIF DateTimeOriginal': {'field_length': 20, + 'field_offset': 716, + 'field_type': 2, + 'printable': u'2011:06:22 12:20:33', + 'tag': 36867, + 'values': u'2011:06:22 12:20:33'}, + 'EXIF DigitalZoomRatio': {'field_length': 8, + 'field_offset': 26232, + 'field_type': 5, + 'printable': u'1', + 'tag': 41988, + 'values': [[1, 1]]}, + 'EXIF ExifImageLength': {'field_length': 2, + 'field_offset': 500, + 'field_type': 3, + 'printable': u'2592', + 'tag': 40963, + 'values': [2592]}, + 'EXIF ExifImageWidth': {'field_length': 2, + 'field_offset': 488, + 'field_type': 3, + 'printable': u'3872', + 'tag': 40962, + 'values': [3872]}, + 'EXIF ExifVersion': {'field_length': 4, + 'field_offset': 272, + 'field_type': 7, + 'printable': u'0221', + 'tag': 36864, + 'values': [48, 50, 50, 49]}, + 'EXIF ExposureBiasValue': {'field_length': 8, + 'field_offset': 764, + 'field_type': 10, + 'printable': u'0', + 'tag': 37380, + 'values': [[0, 1]]}, + 'EXIF ExposureMode': {'field_length': 2, + 'field_offset': 584, + 'field_type': 3, + 'printable': u'Manual Exposure', + 'tag': 41986, + 'values': [1]}, + 'EXIF ExposureProgram': {'field_length': 2, + 'field_offset': 248, + 'field_type': 3, + 'printable': u'Manual', + 'tag': 34850, + 'values': [1]}, + 'EXIF ExposureTime': {'field_length': 8, + 'field_offset': 700, + 'field_type': 5, + 'printable': u'1/125', + 'tag': 33434, + 'values': [[1, 125]]}, + 'EXIF FNumber': {'field_length': 8, + 'field_offset': 708, + 'field_type': 5, + 'printable': u'10', + 'tag': 33437, + 'values': [[10, 1]]}, + 'EXIF FileSource': {'field_length': 1, + 'field_offset': 536, + 'field_type': 7, + 'printable': u'Digital Camera', + 'tag': 41728, + 'values': [3]}, + 'EXIF Flash': {'field_length': 2, + 'field_offset': 380, + 'field_type': 3, + 'printable': u'Flash did not fire', + 'tag': 37385, + 'values': [0]}, + 'EXIF FlashPixVersion': {'field_length': 4, + 'field_offset': 464, + 'field_type': 7, + 'printable': u'0100', + 'tag': 40960, + 'values': [48, 49, 48, 48]}, + 'EXIF FocalLength': {'field_length': 8, + 'field_offset': 780, + 'field_type': 5, + 'printable': u'18', + 'tag': 37386, + 'values': [[18, 1]]}, + 'EXIF FocalLengthIn35mmFilm': {'field_length': 2, + 'field_offset': 620, + 'field_type': 3, + 'printable': u'27', + 'tag': 41989, + 'values': [27]}, + 'EXIF GainControl': {'field_length': 2, + 'field_offset': 644, + 'field_type': 3, + 'printable': u'None', + 'tag': 41991, + 'values': [0]}, + 'EXIF ISOSpeedRatings': {'field_length': 2, + 'field_offset': 260, + 'field_type': 3, + 'printable': u'100', + 'tag': 34855, + 'values': [100]}, + 'EXIF InteroperabilityOffset': {'field_length': 4, + 'field_offset': 512, + 'field_type': 4, + 'printable': u'26240', + 'tag': 40965, + 'values': [26240]}, + 'EXIF LightSource': {'field_length': 2, + 'field_offset': 368, + 'field_type': 3, + 'printable': u'Unknown', + 'tag': 37384, + 'values': [0]}, + 'EXIF MaxApertureValue': {'field_length': 8, + 'field_offset': 772, + 'field_type': 5, + 'printable': u'18/5', + 'tag': 37381, + 'values': [[18, 5]]}, + 'EXIF MeteringMode': {'field_length': 2, + 'field_offset': 356, + 'field_type': 3, + 'printable': u'Pattern', + 'tag': 37383, + 'values': [5]}, + 'EXIF Saturation': {'field_length': 2, + 'field_offset': 668, + 'field_type': 3, + 'printable': u'Normal', + 'tag': 41993, + 'values': [0]}, + 'EXIF SceneCaptureType': {'field_length': 2, + 'field_offset': 632, + 'field_type': 3, + 'printable': u'Standard', + 'tag': 41990, + 'values': [0]}, + 'EXIF SceneType': {'field_length': 1, + 'field_offset': 548, + 'field_type': 7, + 'printable': u'Directly Photographed', + 'tag': 41729, + 'values': [1]}, + 'EXIF SensingMethod': {'field_length': 2, + 'field_offset': 524, + 'field_type': 3, + 'printable': u'One-chip color area', + 'tag': 41495, + 'values': [2]}, + 'EXIF Sharpness': {'field_length': 2, + 'field_offset': 680, + 'field_type': 3, + 'printable': u'Normal', + 'tag': 41994, + 'values': [0]}, + 'EXIF SubSecTime': {'field_length': 3, + 'field_offset': 428, + 'field_type': 2, + 'printable': u'10', + 'tag': 37520, + 'values': u'10'}, + 'EXIF SubSecTimeDigitized': {'field_length': 3, + 'field_offset': 452, + 'field_type': 2, + 'printable': u'10', + 'tag': 37522, + 'values': u'10'}, + 'EXIF SubSecTimeOriginal': {'field_length': 3, + 'field_offset': 440, + 'field_type': 2, + 'printable': u'10', + 'tag': 37521, + 'values': u'10'}, + 'EXIF SubjectDistanceRange': {'field_length': 2, + 'field_offset': 692, + 'field_type': 3, + 'printable': u'0', + 'tag': 41996, + 'values': [0]}, + 'EXIF WhiteBalance': {'field_length': 2, + 'field_offset': 596, + 'field_type': 3, + 'printable': u'Auto', + 'tag': 41987, + 'values': [0]}, + 'Image DateTime': {'field_length': 20, + 'field_offset': 194, + 'field_type': 2, + 'printable': u'2011:06:22 12:20:33', + 'tag': 306, + 'values': u'2011:06:22 12:20:33'}, + 'Image ExifOffset': {'field_length': 4, + 'field_offset': 126, + 'field_type': 4, + 'printable': u'214', + 'tag': 34665, + 'values': [214]}, + 'Image Make': {'field_length': 18, + 'field_offset': 134, + 'field_type': 2, + 'printable': u'NIKON CORPORATION', + 'tag': 271, + 'values': u'NIKON CORPORATION'}, + 'Image Model': {'field_length': 10, + 'field_offset': 152, + 'field_type': 2, + 'printable': u'NIKON D80', + 'tag': 272, + 'values': u'NIKON D80'}, + 'Image Orientation': {'field_length': 2, + 'field_offset': 42, + 'field_type': 3, + 'printable': u'Rotated 90 CCW', + 'tag': 274, + 'values': [6]}, + 'Image ResolutionUnit': {'field_length': 2, + 'field_offset': 78, + 'field_type': 3, + 'printable': u'Pixels/Inch', + 'tag': 296, + 'values': [2]}, + 'Image Software': {'field_length': 15, + 'field_offset': 178, + 'field_type': 2, + 'printable': u'Shotwell 0.9.3', + 'tag': 305, + 'values': u'Shotwell 0.9.3'}, + 'Image XResolution': {'field_length': 8, + 'field_offset': 162, + 'field_type': 5, + 'printable': u'300', + 'tag': 282, + 'values': [[300, 1]]}, + 'Image YCbCrPositioning': {'field_length': 2, + 'field_offset': 114, + 'field_type': 3, + 'printable': u'Co-sited', + 'tag': 531, + 'values': [2]}, + 'Image YResolution': {'field_length': 8, + 'field_offset': 170, + 'field_type': 5, + 'printable': u'300', + 'tag': 283, + 'values': [[300, 1]]}, + 'Thumbnail Compression': {'field_length': 2, + 'field_offset': 26280, + 'field_type': 3, + 'printable': u'JPEG (old-style)', + 'tag': 259, + 'values': [6]}, + 'Thumbnail ResolutionUnit': {'field_length': 2, + 'field_offset': 26316, + 'field_type': 3, + 'printable': u'Pixels/Inch', + 'tag': 296, + 'values': [2]}, + 'Thumbnail XResolution': {'field_length': 8, + 'field_offset': 26360, + 'field_type': 5, + 'printable': u'300', + 'tag': 282, + 'values': [[300, 1]]}, + 'Thumbnail YCbCrPositioning': {'field_length': 2, + 'field_offset': 26352, + 'field_type': 3, + 'printable': u'Co-sited', + 'tag': 531, + 'values': [2]}, + 'Thumbnail YResolution': {'field_length': 8, + 'field_offset': 26368, + 'field_type': 5, + 'printable': u'300', + 'tag': 283, + 'values': [[300, 1]]}} + + +def test_exif_image_orientation(): + ''' + Test image reorientation based on EXIF data + ''' + result = extract_exif(GOOD_JPG) + + image = exif_fix_image_orientation( + Image.open(GOOD_JPG), + result) + + # Are the dimensions correct? + assert image.size == (428, 640) + + # If this pixel looks right, the rest of the image probably will too. + assert_in(image.getdata()[10000], + ((41, 28, 11), (43, 27, 11)) + ) + + +def test_exif_no_exif(): + ''' + Test an image without exif + ''' + result = extract_exif(EMPTY_JPG) + clean = clean_exif(result) + useful = get_useful(clean) + gps = get_gps_data(result) + + assert result == {} + assert clean == {} + assert gps == {} + assert useful == {} + + +def test_exif_bad_image(): + ''' + Test EXIF extraction from a faithful, but bad image + ''' + result = extract_exif(BAD_JPG) + clean = clean_exif(result) + useful = get_useful(clean) + gps = get_gps_data(result) + + assert result == {} + assert clean == {} + assert gps == {} + assert useful == {} + + +def test_exif_gps_data(): + ''' + Test extractiion of GPS data + ''' + result = extract_exif(GPS_JPG) + gps = get_gps_data(result) + + assert gps == { + 'latitude': 59.336666666666666, + 'direction': 25.674046740467404, + 'altitude': 37.64365671641791, + 'longitude': 18.016166666666667} diff --git a/mediagoblin/tests/test_exif/bad.jpg b/mediagoblin/tests/test_exif/bad.jpg new file mode 100644 index 00000000..4cde23cd --- /dev/null +++ b/mediagoblin/tests/test_exif/bad.jpg @@ -0,0 +1,18 @@ +V2UncmUgbm8gc3RyYW5nZXJzIHRvIGxvdmUKWW91IGtub3cgdGhlIHJ1bGVzIGFuZCBzbyBkbyBJ +CkEgZnVsbCBjb21taXRtZW50J3Mgd2hhdCBJJ20gdGhpbmtpbicgb2YKWW91IHdvdWxkbid0IGdl +dCB0aGlzIGZyb20gYW55IG90aGVyIGd1eQpJIGp1c3Qgd2FubmEgdGVsbCB5b3UgaG93IEknbSBm +ZWVsaW4nCkdvdHRhIG1ha2UgeW91IHVuZGVyc3RhbmQKCihDaG9ydXMpCk5ldmVyIGdvbm5hIGdp +dmUgeW91IHVwCk5ldmVyIGdvbm5hIGxldCB5b3UgZG93bgpOZXZlciBnb25uYSBydW4gYXJvdW5k +IGFuZCBkZXNlcnQgeW91Ck5ldmVyIGdvbm5hIG1ha2UgeW91IGNyeQpOZXZlciBnb25uYSBzYXkg +Z29vZGJ5ZQpOZXZlciBnb25uYSB0ZWxsIGEgbGllIGFuZCBodXJ0IHlvdQoKV2UndmUga25vdyBl +YWNoIG90aGVyIGZvciBzbyBsb25nCllvdXIgaGVhcnQncyBiZWVuIGFjaGluJyBidXQgeW91J3Jl +IHRvbyBzaHkgdG8gc2F5IGl0Ckluc2lkZSB3ZSBib3RoIGtub3cgd2hhdCdzIGJlZW4gZ29pbmcg +b24KV2Uga25vdyB0aGUgZ2FtZSBhbmQgd2UncmUgZ29ubmEgcGxheSBpdApBbmQgaWYgeW91IGFz +ayBtZSBob3cgSSdtIGZlZWxpbicKRG9uJ3QgdGVsbCBtZSB5b3UncmUgdG9vIGJsaW5kIHRvIHNl +ZQoKKENob3J1cyB4MikKCihHaXZlIHlvdSB1cCwgZ2l2ZSB5b3UgdXApCk5ldmVyIGdvbm5hIGdp +dmUsIG5ldmVyIGdvbm5hIGdpdmUKKEdpdmUgeW91IHVwKQpOZXZlciBnb25uYSBnaXZlLCBuZXZl +ciBnb25uYSBnaXZlCihHaXZlIHlvdSB1cCkKCldlJ3ZlIGtub3cgZWFjaCBvdGhlciBmb3Igc28g +bG9uZwpZb3VyIGhlYXJ0J3MgYmVlbiBhY2hpbicgYnV0IHlvdSdyZSB0b28gc2h5IHRvIHNheSBp +dApJbnNpZGUgd2UgYm90aCBrbm93IHdoYXQncyBiZWVuIGdvaW5nIG9uCldlIGtub3cgdGhlIGdh +bWUgYW5kIHdlJ3JlIGdvbm5hIHBsYXkgaXQKSSBqdXN0IHdhbm5hIHRlbGwgeW91IGhvdyBJJ20g +ZmVlbGluJwpHb3R0YSBtYWtlIHlvdSB1bmRlcnN0YW5kCgooQ2hvcnVzIHgzKQo= diff --git a/mediagoblin/tests/test_exif/empty.jpg b/mediagoblin/tests/test_exif/empty.jpg Binary files differnew file mode 100644 index 00000000..37533af5 --- /dev/null +++ b/mediagoblin/tests/test_exif/empty.jpg diff --git a/mediagoblin/tests/test_exif/good.jpg b/mediagoblin/tests/test_exif/good.jpg Binary files differnew file mode 100644 index 00000000..0ee956fe --- /dev/null +++ b/mediagoblin/tests/test_exif/good.jpg diff --git a/mediagoblin/tests/test_exif/has-gps.jpg b/mediagoblin/tests/test_exif/has-gps.jpg Binary files differnew file mode 100644 index 00000000..f6f39d86 --- /dev/null +++ b/mediagoblin/tests/test_exif/has-gps.jpg diff --git a/mediagoblin/tests/test_globals.py b/mediagoblin/tests/test_globals.py new file mode 100644 index 00000000..fe3088f8 --- /dev/null +++ b/mediagoblin/tests/test_globals.py @@ -0,0 +1,42 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import pytest + +from mediagoblin import mg_globals + + +class TestGlobals(object): + def setup(self): + self.old_database = mg_globals.database + + def teardown(self): + mg_globals.database = self.old_database + + def test_setup_globals(self): + mg_globals.setup_globals( + database='my favorite database!', + public_store='my favorite public_store!', + queue_store='my favorite queue_store!') + + assert mg_globals.database == 'my favorite database!' + assert mg_globals.public_store == 'my favorite public_store!' + assert mg_globals.queue_store == 'my favorite queue_store!' + + pytest.raises( + AssertionError, + mg_globals.setup_globals, + no_such_global_foo="Dummy") diff --git a/mediagoblin/tests/test_http_callback.py b/mediagoblin/tests/test_http_callback.py new file mode 100644 index 00000000..a0511af7 --- /dev/null +++ b/mediagoblin/tests/test_http_callback.py @@ -0,0 +1,83 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json + +import pytest +from urlparse import urlparse, parse_qs + +from mediagoblin import mg_globals +from mediagoblin.tools import processing +from mediagoblin.tests.tools import fixture_add_user +from mediagoblin.tests.test_submission import GOOD_PNG +from mediagoblin.tests import test_oauth as oauth + + +class TestHTTPCallback(object): + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + + self.db = mg_globals.database + + self.user_password = u'secret' + self.user = fixture_add_user(u'call_back', self.user_password) + + self.login() + + def login(self): + self.test_app.post('/auth/login/', { + 'username': self.user.username, + 'password': self.user_password}) + + def get_access_token(self, client_id, client_secret, code): + response = self.test_app.get('/oauth/access_token', { + 'code': code, + 'client_id': client_id, + 'client_secret': client_secret}) + + response_data = json.loads(response.body) + + return response_data['access_token'] + + def test_callback(self): + ''' Test processing HTTP callback ''' + self.oauth = oauth.TestOAuth() + self.oauth.setup(self.test_app) + + redirect, client_id = self.oauth.test_4_authorize_confidential_client() + + code = parse_qs(urlparse(redirect.location).query)['code'][0] + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.identifier == unicode(client_id)).first() + + client_secret = client.secret + + access_token = self.get_access_token(client_id, client_secret, code) + + callback_url = 'https://foo.example?secrettestmediagoblinparam' + + self.test_app.post('/api/submit?client_id={0}&access_token={1}\ +&client_secret={2}'.format( + client_id, + access_token, + client_secret), { + 'title': 'Test', + 'callback_url': callback_url}, + upload_files=[('file', GOOD_PNG)]) + + assert processing.TESTS_CALLBACKS[callback_url]['state'] == u'processed' diff --git a/mediagoblin/tests/test_messages.py b/mediagoblin/tests/test_messages.py new file mode 100644 index 00000000..22f9e800 --- /dev/null +++ b/mediagoblin/tests/test_messages.py @@ -0,0 +1,50 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin import messages +from mediagoblin.tools import template + + +def test_messages(test_app): + """ + Added messages should show up in the request.session, + fetched messages should be the same as the added ones, + and fetching should clear the message list. + """ + # Aquire a request object + test_app.get('/') + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/root.html'] + request = context['request'] + + # The message queue should be empty + assert request.session.get('messages', []) == [] + + # First of all, we should clear the messages queue + messages.clear_add_message() + # Adding a message should modify the session accordingly + messages.add_message(request, 'herp_derp', 'First!') + test_msg_queue = [{'text': 'First!', 'level': 'herp_derp'}] + + # Alternative tests to the following, test divided in two steps: + # assert request.session['messages'] == test_msg_queue + # 1. Tests if add_message worked + assert messages.ADD_MESSAGE_TEST[-1] == test_msg_queue + # 2. Tests if add_message updated session information + assert messages.ADD_MESSAGE_TEST[-1] == request.session['messages'] + + # fetch_messages should return and empty the queue + assert messages.fetch_messages(request) == test_msg_queue + assert request.session.get('messages') == [] diff --git a/mediagoblin/tests/test_mgoblin_app.ini b/mediagoblin/tests/test_mgoblin_app.ini new file mode 100644 index 00000000..0466b53b --- /dev/null +++ b/mediagoblin/tests/test_mgoblin_app.ini @@ -0,0 +1,33 @@ +[mediagoblin] +direct_remote_path = /test_static/ +email_sender_address = "notice@mediagoblin.example.org" +email_debug_mode = true + +# TODO: Switch to using an in-memory database +sql_engine = "sqlite:///%(here)s/user_dev/mediagoblin.db" + +# tag parsing +tags_max_length = 50 + +# So we can start to test attachments: +allow_attachments = True + +media_types = mediagoblin.media_types.image, mediagoblin.media_types.pdf + +[storage:publicstore] +base_dir = %(here)s/user_dev/media/public +base_url = /mgoblin_media/ + +[storage:queuestore] +base_dir = %(here)s/user_dev/media/queue + +[celery] +CELERY_ALWAYS_EAGER = true +CELERY_RESULT_DBURI = "sqlite:///%(here)s/user_dev/celery.db" +BROKER_HOST = "sqlite:///%(here)s/user_dev/kombu.db" + +[plugins] +[[mediagoblin.plugins.api]] +[[mediagoblin.plugins.oauth]] +[[mediagoblin.plugins.httpapiauth]] +[[mediagoblin.plugins.piwigo]] diff --git a/mediagoblin/tests/test_misc.py b/mediagoblin/tests/test_misc.py new file mode 100644 index 00000000..755d863f --- /dev/null +++ b/mediagoblin/tests/test_misc.py @@ -0,0 +1,91 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.db.base import Session +from mediagoblin.db.models import User, MediaEntry, MediaComment +from mediagoblin.tests.tools import fixture_add_user, fixture_media_entry + + +def test_404_for_non_existent(test_app): + res = test_app.get('/does-not-exist/', expect_errors=True) + assert res.status_int == 404 + + +def test_user_deletes_other_comments(test_app): + user_a = fixture_add_user(u"chris_a") + user_b = fixture_add_user(u"chris_b") + + media_a = fixture_media_entry(uploader=user_a.id, save=False) + media_b = fixture_media_entry(uploader=user_b.id, save=False) + Session.add(media_a) + Session.add(media_b) + Session.flush() + + # Create all 4 possible comments: + for u_id in (user_a.id, user_b.id): + for m_id in (media_a.id, media_b.id): + cmt = MediaComment() + cmt.media_entry = m_id + cmt.author = u_id + cmt.content = u"Some Comment" + Session.add(cmt) + + Session.flush() + + usr_cnt1 = User.query.count() + med_cnt1 = MediaEntry.query.count() + cmt_cnt1 = MediaComment.query.count() + + User.query.get(user_a.id).delete(commit=False) + + usr_cnt2 = User.query.count() + med_cnt2 = MediaEntry.query.count() + cmt_cnt2 = MediaComment.query.count() + + # One user deleted + assert usr_cnt2 == usr_cnt1 - 1 + # One media gone + assert med_cnt2 == med_cnt1 - 1 + # Three of four comments gone. + assert cmt_cnt2 == cmt_cnt1 - 3 + + User.query.get(user_b.id).delete() + + usr_cnt2 = User.query.count() + med_cnt2 = MediaEntry.query.count() + cmt_cnt2 = MediaComment.query.count() + + # All users gone + assert usr_cnt2 == usr_cnt1 - 2 + # All media gone + assert med_cnt2 == med_cnt1 - 2 + # All comments gone + assert cmt_cnt2 == cmt_cnt1 - 4 + + +def test_media_deletes_broken_attachment(test_app): + user_a = fixture_add_user(u"chris_a") + + media = fixture_media_entry(uploader=user_a.id, save=False) + media.attachment_files.append(dict( + name=u"some name", + filepath=[u"does", u"not", u"exist"], + )) + Session.add(media) + Session.flush() + + MediaEntry.query.get(media.id).delete() + User.query.get(user_a.id).delete() diff --git a/mediagoblin/tests/test_modelmethods.py b/mediagoblin/tests/test_modelmethods.py new file mode 100644 index 00000000..427aa47c --- /dev/null +++ b/mediagoblin/tests/test_modelmethods.py @@ -0,0 +1,167 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Maybe not every model needs a test, but some models have special +# methods, and so it makes sense to test them here. + +from mediagoblin.db.base import Session +from mediagoblin.db.models import MediaEntry + +from mediagoblin.tests.tools import fixture_add_user + +import mock + + +class FakeUUID(object): + hex = 'testtest-test-test-test-testtesttest' + +UUID_MOCK = mock.Mock(return_value=FakeUUID()) + + +class TestMediaEntrySlugs(object): + def _setup(self): + self.chris_user = fixture_add_user(u'chris') + self.emily_user = fixture_add_user(u'emily') + self.existing_entry = self._insert_media_entry_fixture( + title=u"Beware, I exist!", + slug=u"beware-i-exist") + + def _insert_media_entry_fixture(self, title=None, slug=None, this_id=None, + uploader=None, save=True): + entry = MediaEntry() + entry.title = title or u"Some title" + entry.slug = slug + entry.id = this_id + entry.uploader = uploader or self.chris_user.id + entry.media_type = u'image' + + if save: + entry.save() + + return entry + + def test_unique_slug_from_title(self, test_app): + self._setup() + + entry = self._insert_media_entry_fixture(u"Totally unique slug!", save=False) + entry.generate_slug() + assert entry.slug == u'totally-unique-slug' + + def test_old_good_unique_slug(self, test_app): + self._setup() + + entry = self._insert_media_entry_fixture( + u"A title here", u"a-different-slug-there", save=False) + entry.generate_slug() + assert entry.slug == u"a-different-slug-there" + + def test_old_weird_slug(self, test_app): + self._setup() + + entry = self._insert_media_entry_fixture( + slug=u"wowee!!!!!", save=False) + entry.generate_slug() + assert entry.slug == u"wowee" + + def test_existing_slug_use_id(self, test_app): + self._setup() + + entry = self._insert_media_entry_fixture( + u"Beware, I exist!!", this_id=9000, save=False) + entry.generate_slug() + assert entry.slug == u"beware-i-exist-9000" + + def test_existing_slug_cant_use_id(self, test_app): + self._setup() + + # Getting tired of dealing with test_app and this mock.patch + # thing conflicting, getting lazy. + @mock.patch('uuid.uuid4', UUID_MOCK) + def _real_test(): + # This one grabs the nine thousand slug + self._insert_media_entry_fixture( + slug=u"beware-i-exist-9000") + + entry = self._insert_media_entry_fixture( + u"Beware, I exist!!", this_id=9000, save=False) + entry.generate_slug() + assert entry.slug == u"beware-i-exist-test" + + _real_test() + + def test_existing_slug_cant_use_id_extra_junk(self, test_app): + self._setup() + + # Getting tired of dealing with test_app and this mock.patch + # thing conflicting, getting lazy. + @mock.patch('uuid.uuid4', UUID_MOCK) + def _real_test(): + # This one grabs the nine thousand slug + self._insert_media_entry_fixture( + slug=u"beware-i-exist-9000") + + # This one grabs makes sure the annoyance doesn't stop + self._insert_media_entry_fixture( + slug=u"beware-i-exist-test") + + entry = self._insert_media_entry_fixture( + u"Beware, I exist!!", this_id=9000, save=False) + entry.generate_slug() + assert entry.slug == u"beware-i-exist-testtest" + + _real_test() + + def test_garbage_slug(self, test_app): + """ + Titles that sound totally like Q*Bert shouldn't have slugs at + all. We'll just reference them by id. + + , + / \ (@!#?@!) + |\,/| ,-, / + | |#| ( ")~ + / \|/ \ L L + |\,/|\,/| + | |#, |#| + / \|/ \|/ \ + |\,/|\,/|\,/| + | |#| |#| |#| + / \|/ \|/ \|/ \ + |\,/|\,/|\,/|\,/| + | |#| |#| |#| |#| + \|/ \|/ \|/ \|/ + """ + self._setup() + + qbert_entry = self._insert_media_entry_fixture( + u"@!#?@!", save=False) + qbert_entry.generate_slug() + assert qbert_entry.slug is None + + +def test_media_data_init(test_app): + Session.rollback() + Session.remove() + media = MediaEntry() + media.media_type = u"mediagoblin.media_types.image" + assert media.media_data is None + media.media_data_init() + assert media.media_data is not None + obj_in_session = 0 + for obj in Session(): + obj_in_session += 1 + print repr(obj) + assert obj_in_session == 0 diff --git a/mediagoblin/tests/test_oauth.py b/mediagoblin/tests/test_oauth.py new file mode 100644 index 00000000..ea3bd798 --- /dev/null +++ b/mediagoblin/tests/test_oauth.py @@ -0,0 +1,222 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +import logging + +import pytest +from urlparse import parse_qs, urlparse + +from mediagoblin import mg_globals +from mediagoblin.tools import template, pluginapi +from mediagoblin.tests.tools import fixture_add_user + + +_log = logging.getLogger(__name__) + + +class TestOAuth(object): + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + + self.db = mg_globals.database + + self.pman = pluginapi.PluginManager() + + self.user_password = u'4cc355_70k3N' + self.user = fixture_add_user(u'joauth', self.user_password) + + self.login() + + def login(self): + self.test_app.post( + '/auth/login/', { + 'username': self.user.username, + 'password': self.user_password}) + + def register_client(self, name, client_type, description=None, + redirect_uri=''): + return self.test_app.post( + '/oauth/client/register', { + 'name': name, + 'description': description, + 'type': client_type, + 'redirect_uri': redirect_uri}) + + def get_context(self, template_name): + return template.TEMPLATE_TEST_CONTEXT[template_name] + + def test_1_public_client_registration_without_redirect_uri(self): + ''' Test 'public' OAuth client registration without any redirect uri ''' + response = self.register_client( + u'OMGOMGOMG', 'public', 'OMGOMG Apache License v2') + + ctx = self.get_context('oauth/client/register.html') + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.name == u'OMGOMGOMG').first() + + assert response.status_int == 200 + + # Should display an error + assert len(ctx['form'].redirect_uri.errors) + + # Should not pass through + assert not client + + def test_2_successful_public_client_registration(self): + ''' Successfully register a public client ''' + uri = 'http://foo.example' + self.register_client( + u'OMGOMG', 'public', 'OMG!', uri) + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.name == u'OMGOMG').first() + + # redirect_uri should be set + assert client.redirect_uri == uri + + # Client should have been registered + assert client + + def test_3_successful_confidential_client_reg(self): + ''' Register a confidential OAuth client ''' + response = self.register_client( + u'GMOGMO', 'confidential', 'NO GMO!') + + assert response.status_int == 302 + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.name == u'GMOGMO').first() + + # Client should have been registered + assert client + + return client + + def test_4_authorize_confidential_client(self): + ''' Authorize a confidential client as a logged in user ''' + client = self.test_3_successful_confidential_client_reg() + + client_identifier = client.identifier + + redirect_uri = 'https://foo.example' + response = self.test_app.get('/oauth/authorize', { + 'client_id': client.identifier, + 'scope': 'all', + 'redirect_uri': redirect_uri}) + + # User-agent should NOT be redirected + assert response.status_int == 200 + + ctx = self.get_context('oauth/authorize.html') + + form = ctx['form'] + + # Short for client authorization post reponse + capr = self.test_app.post( + '/oauth/client/authorize', { + 'client_id': form.client_id.data, + 'allow': 'Allow', + 'next': form.next.data}) + + assert capr.status_int == 302 + + authorization_response = capr.follow() + + assert authorization_response.location.startswith(redirect_uri) + + return authorization_response, client_identifier + + def get_code_from_redirect_uri(self, uri): + ''' Get the value of ?code= from an URI ''' + return parse_qs(urlparse(uri).query)['code'][0] + + def test_token_endpoint_successful_confidential_request(self): + ''' Successful request against token endpoint ''' + code_redirect, client_id = self.test_4_authorize_confidential_client() + + code = self.get_code_from_redirect_uri(code_redirect.location) + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.identifier == unicode(client_id)).first() + + token_res = self.test_app.get('/oauth/access_token?client_id={0}&\ +code={1}&client_secret={2}'.format(client_id, code, client.secret)) + + assert token_res.status_int == 200 + + token_data = json.loads(token_res.body) + + assert not 'error' in token_data + assert 'access_token' in token_data + assert 'token_type' in token_data + assert 'expires_in' in token_data + assert type(token_data['expires_in']) == int + assert token_data['expires_in'] > 0 + + # There should be a refresh token provided in the token data + assert len(token_data['refresh_token']) + + return client_id, token_data + + def test_token_endpont_missing_id_confidential_request(self): + ''' Unsuccessful request against token endpoint, missing client_id ''' + code_redirect, client_id = self.test_4_authorize_confidential_client() + + code = self.get_code_from_redirect_uri(code_redirect.location) + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.identifier == unicode(client_id)).first() + + token_res = self.test_app.get('/oauth/access_token?\ +code={0}&client_secret={1}'.format(code, client.secret)) + + assert token_res.status_int == 200 + + token_data = json.loads(token_res.body) + + assert 'error' in token_data + assert not 'access_token' in token_data + assert token_data['error'] == 'invalid_request' + assert len(token_data['error_description']) + + def test_refresh_token(self): + ''' Try to get a new access token using the refresh token ''' + # Get an access token and a refresh token + client_id, token_data =\ + self.test_token_endpoint_successful_confidential_request() + + client = self.db.OAuthClient.query.filter( + self.db.OAuthClient.identifier == client_id).first() + + token_res = self.test_app.get('/oauth/access_token', + {'refresh_token': token_data['refresh_token'], + 'client_id': client_id, + 'client_secret': client.secret + }) + + assert token_res.status_int == 200 + + new_token_data = json.loads(token_res.body) + + assert not 'error' in new_token_data + assert 'access_token' in new_token_data + assert 'token_type' in new_token_data + assert 'expires_in' in new_token_data + assert type(new_token_data['expires_in']) == int + assert new_token_data['expires_in'] > 0 diff --git a/mediagoblin/tests/test_paste.ini b/mediagoblin/tests/test_paste.ini new file mode 100644 index 00000000..a9595432 --- /dev/null +++ b/mediagoblin/tests/test_paste.ini @@ -0,0 +1,40 @@ +[DEFAULT] +debug = true + +[composite:main] +use = egg:Paste#urlmap +/ = mediagoblin +/mgoblin_media/ = publicstore_serve +/test_static/ = mediagoblin_static +/theme_static/ = theme_static +/plugin_static/ = plugin_static + +[app:mediagoblin] +use = egg:mediagoblin#app +config = %(here)s/mediagoblin.ini + +[app:publicstore_serve] +use = egg:Paste#static +document_root = %(here)s/user_dev/media/public + +[app:mediagoblin_static] +use = egg:Paste#static +document_root = %(here)s/mediagoblin/static/ + +[app:theme_static] +use = egg:Paste#static +document_root = %(here)s/user_dev/theme_static/ +cache_max_age = 86400 + +[app:plugin_static] +use = egg:Paste#static +document_root = %(here)s/user_dev/plugin_static/ +cache_max_age = 86400 + +[celery] +CELERY_ALWAYS_EAGER = true + +[server:main] +use = egg:Paste#http +host = 127.0.0.1 +port = 6543 diff --git a/mediagoblin/tests/test_pdf.py b/mediagoblin/tests/test_pdf.py new file mode 100644 index 00000000..b4d1940a --- /dev/null +++ b/mediagoblin/tests/test_pdf.py @@ -0,0 +1,39 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import tempfile +import shutil +import os +import pytest + +from mediagoblin.media_types.pdf.processing import ( + pdf_info, check_prerequisites, create_pdf_thumb) +from .resources import GOOD_PDF as GOOD + + +@pytest.mark.skipif("not check_prerequisites()") +def test_pdf(): + good_dict = {'pdf_version_major': 1, 'pdf_title': '', + 'pdf_page_size_width': 612, 'pdf_author': '', + 'pdf_keywords': '', 'pdf_pages': 10, + 'pdf_producer': 'dvips + GNU Ghostscript 7.05', + 'pdf_version_minor': 3, + 'pdf_creator': 'LaTeX with hyperref package', + 'pdf_page_size_height': 792} + assert pdf_info(GOOD) == good_dict + temp_dir = tempfile.mkdtemp() + create_pdf_thumb(GOOD, os.path.join(temp_dir, 'good_256_256.png'), 256, 256) + shutil.rmtree(temp_dir) diff --git a/mediagoblin/tests/test_piwigo.py b/mediagoblin/tests/test_piwigo.py new file mode 100644 index 00000000..16ad0111 --- /dev/null +++ b/mediagoblin/tests/test_piwigo.py @@ -0,0 +1,71 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import pytest +from .tools import fixture_add_user + + +XML_PREFIX = "<?xml version='1.0' encoding='utf-8'?>\n" + + +class Test_PWG(object): + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + + fixture_add_user() + + self.username = u"chris" + self.password = "toast" + + def do_post(self, method, params): + params["method"] = method + return self.test_app.post("/api/piwigo/ws.php", params) + + def do_get(self, method, params=None): + if params is None: + params = {} + params["method"] = method + return self.test_app.get("/api/piwigo/ws.php", params) + + def test_session(self): + resp = self.do_post("pwg.session.login", + {"username": u"nouser", "password": "wrong"}) + assert resp.body == XML_PREFIX \ + + '<rsp stat="fail"><err code="999" msg="Invalid username/password"/></rsp>' + + resp = self.do_post("pwg.session.login", + {"username": self.username, "password": "wrong"}) + assert resp.body == XML_PREFIX \ + + '<rsp stat="fail"><err code="999" msg="Invalid username/password"/></rsp>' + + resp = self.do_get("pwg.session.getStatus") + assert resp.body == XML_PREFIX \ + + '<rsp stat="ok"><username>guest</username></rsp>' + + resp = self.do_post("pwg.session.login", + {"username": self.username, "password": self.password}) + assert resp.body == XML_PREFIX + '<rsp stat="ok">1</rsp>' + + resp = self.do_get("pwg.session.getStatus") + assert resp.body == XML_PREFIX \ + + '<rsp stat="ok"><username>chris</username></rsp>' + + self.do_get("pwg.session.logout") + + resp = self.do_get("pwg.session.getStatus") + assert resp.body == XML_PREFIX \ + + '<rsp stat="ok"><username>guest</username></rsp>' diff --git a/mediagoblin/tests/test_pluginapi.py b/mediagoblin/tests/test_pluginapi.py new file mode 100644 index 00000000..eae0ce15 --- /dev/null +++ b/mediagoblin/tests/test_pluginapi.py @@ -0,0 +1,466 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import json +import sys + +from configobj import ConfigObj +import pytest +import pkg_resources +from validate import VdtTypeError + +from mediagoblin import mg_globals +from mediagoblin.init.plugins import setup_plugins +from mediagoblin.init.config import read_mediagoblin_config +from mediagoblin.gmg_commands.assetlink import link_plugin_assets +from mediagoblin.tools import pluginapi +from mediagoblin.tests.tools import get_app +from mediagoblin.tools.common import CollectingPrinter + + +def with_cleanup(*modules_to_delete): + def _with_cleanup(fun): + """Wrapper that saves and restores mg_globals""" + def _with_cleanup_inner(*args, **kwargs): + old_app_config = mg_globals.app_config + old_global_config = mg_globals.global_config + # Need to delete icky modules before and after so as to make + # sure things work correctly. + for module in modules_to_delete: + try: + del sys.modules[module] + except KeyError: + pass + # The plugin cache gets populated as a side-effect of + # importing, so it's best to clear it before and after a test. + pman = pluginapi.PluginManager() + pman.clear() + try: + return fun(*args, **kwargs) + finally: + mg_globals.app_config = old_app_config + mg_globals.global_config = old_global_config + # Need to delete icky modules before and after so as to make + # sure things work correctly. + for module in modules_to_delete: + try: + del sys.modules[module] + except KeyError: + pass + pman.clear() + + _with_cleanup_inner.__name__ = fun.__name__ + return _with_cleanup_inner + return _with_cleanup + + +def build_config(sections): + """Builds a ConfigObj object with specified data + + :arg sections: list of ``(section_name, section_data, + subsection_list)`` tuples where section_data is a dict and + subsection_list is a list of ``(section_name, section_data, + subsection_list)``, ... + + For example: + + >>> build_config([ + ... ('mediagoblin', {'key1': 'val1'}, []), + ... ('section2', {}, [ + ... ('subsection1', {}, []) + ... ]) + ... ]) + """ + cfg = ConfigObj() + cfg.filename = 'foo' + def _iter_section(cfg, section_list): + for section_name, data, subsection_list in section_list: + cfg[section_name] = data + _iter_section(cfg[section_name], subsection_list) + + _iter_section(cfg, sections) + return cfg + + +@with_cleanup() +def test_no_plugins(): + """Run setup_plugins with no plugins in config""" + cfg = build_config([('mediagoblin', {}, [])]) + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + pman = pluginapi.PluginManager() + setup_plugins() + + # Make sure we didn't load anything. + assert len(pman.plugins) == 0 + + +@with_cleanup('mediagoblin.plugins.sampleplugin') +def test_one_plugin(): + """Run setup_plugins with a single working plugin""" + cfg = build_config([ + ('mediagoblin', {}, []), + ('plugins', {}, [ + ('mediagoblin.plugins.sampleplugin', {}, []) + ]) + ]) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + pman = pluginapi.PluginManager() + setup_plugins() + + # Make sure we only found one plugin + assert len(pman.plugins) == 1 + # Make sure the plugin is the one we think it is. + assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin' + # Make sure there was one hook registered + assert len(pman.hooks) == 1 + # Make sure _setup_plugin_called was called once + import mediagoblin.plugins.sampleplugin + assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1 + + +@with_cleanup('mediagoblin.plugins.sampleplugin') +def test_same_plugin_twice(): + """Run setup_plugins with a single working plugin twice""" + cfg = build_config([ + ('mediagoblin', {}, []), + ('plugins', {}, [ + ('mediagoblin.plugins.sampleplugin', {}, []), + ('mediagoblin.plugins.sampleplugin', {}, []), + ]) + ]) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + pman = pluginapi.PluginManager() + setup_plugins() + + # Make sure we only found one plugin + assert len(pman.plugins) == 1 + # Make sure the plugin is the one we think it is. + assert pman.plugins[0] == 'mediagoblin.plugins.sampleplugin' + # Make sure there was one hook registered + assert len(pman.hooks) == 1 + # Make sure _setup_plugin_called was called once + import mediagoblin.plugins.sampleplugin + assert mediagoblin.plugins.sampleplugin._setup_plugin_called == 1 + + +@with_cleanup() +def test_disabled_plugin(): + """Run setup_plugins with a single working plugin twice""" + cfg = build_config([ + ('mediagoblin', {}, []), + ('plugins', {}, [ + ('-mediagoblin.plugins.sampleplugin', {}, []), + ]) + ]) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + pman = pluginapi.PluginManager() + setup_plugins() + + # Make sure we didn't load the plugin + assert len(pman.plugins) == 0 + + +CONFIG_ALL_CALLABLES = [ + ('mediagoblin', {}, []), + ('plugins', {}, [ + ('mediagoblin.tests.testplugins.callables1', {}, []), + ('mediagoblin.tests.testplugins.callables2', {}, []), + ('mediagoblin.tests.testplugins.callables3', {}, []), + ]) + ] + + +@with_cleanup() +def test_hook_handle(): + """ + Test the hook_handle method + """ + cfg = build_config(CONFIG_ALL_CALLABLES) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + # Just one hook provided + call_log = [] + assert pluginapi.hook_handle( + "just_one", call_log) == "Called just once" + assert call_log == ["expect this one call"] + + # Nothing provided and unhandled not okay + call_log = [] + pluginapi.hook_handle( + "nothing_handling", call_log) == None + assert call_log == [] + + # Nothing provided and unhandled okay + call_log = [] + assert pluginapi.hook_handle( + "nothing_handling", call_log, unhandled_okay=True) is None + assert call_log == [] + + # Multiple provided, go with the first! + call_log = [] + assert pluginapi.hook_handle( + "multi_handle", call_log) == "the first returns" + assert call_log == ["Hi, I'm the first"] + + # Multiple provided, one has CantHandleIt + call_log = [] + assert pluginapi.hook_handle( + "multi_handle_with_canthandle", + call_log) == "the second returns" + assert call_log == ["Hi, I'm the second"] + + +@with_cleanup() +def test_hook_runall(): + """ + Test the hook_runall method + """ + cfg = build_config(CONFIG_ALL_CALLABLES) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + # Just one hook, check results + call_log = [] + assert pluginapi.hook_runall( + "just_one", call_log) == ["Called just once"] + assert call_log == ["expect this one call"] + + # None provided, check results + call_log = [] + assert pluginapi.hook_runall( + "nothing_handling", call_log) == [] + assert call_log == [] + + # Multiple provided, check results + call_log = [] + assert pluginapi.hook_runall( + "multi_handle", call_log) == [ + "the first returns", + "the second returns", + "the third returns", + ] + assert call_log == [ + "Hi, I'm the first", + "Hi, I'm the second", + "Hi, I'm the third"] + + # Multiple provided, one has CantHandleIt, check results + call_log = [] + assert pluginapi.hook_runall( + "multi_handle_with_canthandle", call_log) == [ + "the second returns", + "the third returns", + ] + assert call_log == [ + "Hi, I'm the second", + "Hi, I'm the third"] + + +@with_cleanup() +def test_hook_transform(): + """ + Test the hook_transform method + """ + cfg = build_config(CONFIG_ALL_CALLABLES) + + mg_globals.app_config = cfg['mediagoblin'] + mg_globals.global_config = cfg + + setup_plugins() + + assert pluginapi.hook_transform( + "expand_tuple", (-1, 0)) == (-1, 0, 1, 2, 3) + + +def test_plugin_config(): + """ + Make sure plugins can set up their own config + """ + config, validation_result = read_mediagoblin_config( + pkg_resources.resource_filename( + 'mediagoblin.tests', 'appconfig_plugin_specs.ini')) + + pluginspec_section = config['plugins'][ + 'mediagoblin.tests.testplugins.pluginspec'] + assert pluginspec_section['some_string'] == 'not blork' + assert pluginspec_section['dont_change_me'] == 'still the default' + + # Make sure validation works... this should be an error + assert isinstance( + validation_result[ + 'plugins'][ + 'mediagoblin.tests.testplugins.pluginspec'][ + 'some_int'], + VdtTypeError) + + # the callables thing shouldn't really have anything though. + assert len(config['plugins'][ + 'mediagoblin.tests.testplugins.callables1']) == 0 + + +@pytest.fixture() +def context_modified_app(request): + """ + Get a MediaGoblin app fixture using appconfig_context_modified.ini + """ + return get_app( + request, + mgoblin_config=pkg_resources.resource_filename( + 'mediagoblin.tests', 'appconfig_context_modified.ini')) + + +def test_modify_context(context_modified_app): + """ + Test that we can modify both the view/template specific and + global contexts for templates. + """ + # Specific thing passed into a page + result = context_modified_app.get("/modify_context/specific/") + assert result.body.strip() == """Specific page! + +specific thing: in yer specificpage +global thing: globally appended! +something: orother +doubleme: happyhappy""" + + # General test, should have global context variable only + result = context_modified_app.get("/modify_context/") + assert result.body.strip() == """General page! + +global thing: globally appended! +lol: cats +doubleme: joyjoy""" + + +@pytest.fixture() +def static_plugin_app(request): + """ + Get a MediaGoblin app fixture using appconfig_static_plugin.ini + """ + return get_app( + request, + mgoblin_config=pkg_resources.resource_filename( + 'mediagoblin.tests', 'appconfig_static_plugin.ini')) + + +def test_plugin_assetlink(static_plugin_app): + """ + Test that the assetlink command works correctly + """ + linked_assets_dir = mg_globals.app_config['plugin_linked_assets_dir'] + plugin_link_dir = os.path.join( + linked_assets_dir.rstrip(os.path.sep), + 'staticstuff') + + plugin_statics = pluginapi.hook_runall("static_setup") + assert len(plugin_statics) == 1 + plugin_static = plugin_statics[0] + + def run_assetlink(): + printer = CollectingPrinter() + + link_plugin_assets( + plugin_static, linked_assets_dir, printer) + + return printer + + # it shouldn't exist yet + assert not os.path.lexists(plugin_link_dir) + + # link dir doesn't exist, link it + result = run_assetlink().collection[0] + assert result == \ + 'Linked asset directory for plugin "staticstuff":\n %s\nto:\n %s\n' % ( + plugin_static.file_path.rstrip(os.path.sep), + plugin_link_dir) + assert os.path.lexists(plugin_link_dir) + assert os.path.islink(plugin_link_dir) + assert os.path.realpath(plugin_link_dir) == plugin_static.file_path + + # link dir exists, leave it alone + # (and it should exist still since we just ran it..) + result = run_assetlink().collection[0] + assert result == 'Skipping "staticstuff"; already set up.\n' + assert os.path.lexists(plugin_link_dir) + assert os.path.islink(plugin_link_dir) + assert os.path.realpath(plugin_link_dir) == plugin_static.file_path + + # link dir exists, is a symlink to somewhere else (re-link) + junk_file_path = os.path.join( + linked_assets_dir.rstrip(os.path.sep), + 'junk.txt') + with file(junk_file_path, 'w') as junk_file: + junk_file.write('barf') + + os.unlink(plugin_link_dir) + os.symlink(junk_file_path, plugin_link_dir) + + result = run_assetlink().combined_string + assert result == """Old link found for "staticstuff"; removing. +Linked asset directory for plugin "staticstuff": + %s +to: + %s +""" % (plugin_static.file_path.rstrip(os.path.sep), plugin_link_dir) + assert os.path.lexists(plugin_link_dir) + assert os.path.islink(plugin_link_dir) + assert os.path.realpath(plugin_link_dir) == plugin_static.file_path + + # link dir exists, but is a non-symlink + os.unlink(plugin_link_dir) + with file(plugin_link_dir, 'w') as clobber_file: + clobber_file.write('clobbered!') + + result = run_assetlink().collection[0] + assert result == 'Could not link "staticstuff": %s exists and is not a symlink\n' % ( + plugin_link_dir) + + with file(plugin_link_dir, 'r') as clobber_file: + assert clobber_file.read() == 'clobbered!' + + +def test_plugin_staticdirect(static_plugin_app): + """ + Test that the staticdirect utilities pull up the right things + """ + result = json.loads( + static_plugin_app.get('/staticstuff/').body) + + assert len(result) == 2 + + assert result['mgoblin_bunny_pic'] == '/test_static/images/bunny_pic.png' + assert result['plugin_bunny_css'] == \ + '/plugin_static/staticstuff/css/bunnify.css' + diff --git a/mediagoblin/tests/test_processing.py b/mediagoblin/tests/test_processing.py new file mode 100644 index 00000000..591add96 --- /dev/null +++ b/mediagoblin/tests/test_processing.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +from mediagoblin import processing + +class TestProcessing(object): + def run_fill(self, input, format, output=None): + builder = processing.FilenameBuilder(input) + result = builder.fill(format) + if output is None: + return result + assert output == result + + def test_easy_filename_fill(self): + self.run_fill('/home/user/foo.TXT', '{basename}bar{ext}', 'foobar.txt') + + def test_long_filename_fill(self): + self.run_fill('{0}.png'.format('A' * 300), 'image-{basename}{ext}', + 'image-{0}.png'.format('A' * 245)) diff --git a/mediagoblin/tests/test_session.py b/mediagoblin/tests/test_session.py new file mode 100644 index 00000000..78d790eb --- /dev/null +++ b/mediagoblin/tests/test_session.py @@ -0,0 +1,30 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools import session + +def test_session(): + sess = session.Session() + assert not sess + assert not sess.is_updated() + sess['user_id'] = 27 + assert sess + assert not sess.is_updated() + sess.save() + assert sess.is_updated() + sess.delete() + assert not sess + assert sess.is_updated() diff --git a/mediagoblin/tests/test_sql_migrations.py b/mediagoblin/tests/test_sql_migrations.py new file mode 100644 index 00000000..2fc4c043 --- /dev/null +++ b/mediagoblin/tests/test_sql_migrations.py @@ -0,0 +1,896 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2012, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import copy + +from sqlalchemy import ( + Table, Column, MetaData, Index, + Integer, Float, Unicode, UnicodeText, DateTime, Boolean, + ForeignKey, UniqueConstraint, PickleType, VARCHAR) +from sqlalchemy.orm import sessionmaker, relationship +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.sql import select, insert +from migrate import changeset + +from mediagoblin.db.base import GMGTableBase +from mediagoblin.db.migration_tools import MigrationManager, RegisterMigration +from mediagoblin.tools.common import CollectingPrinter + + +# This one will get filled with local migrations +FULL_MIGRATIONS = {} + + +####################################################### +# Migration set 1: Define initial models, no migrations +####################################################### + +Base1 = declarative_base(cls=GMGTableBase) + +class Creature1(Base1): + __tablename__ = "creature" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True, nullable=False, index=True) + num_legs = Column(Integer, nullable=False) + is_demon = Column(Boolean) + +class Level1(Base1): + __tablename__ = "level" + + id = Column(Unicode, primary_key=True) + name = Column(Unicode) + description = Column(Unicode) + exits = Column(PickleType) + +SET1_MODELS = [Creature1, Level1] + +SET1_MIGRATIONS = {} + +####################################################### +# Migration set 2: A few migrations and new model +####################################################### + +Base2 = declarative_base(cls=GMGTableBase) + +class Creature2(Base2): + __tablename__ = "creature" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True, nullable=False, index=True) + num_legs = Column(Integer, nullable=False) + magical_powers = relationship("CreaturePower2") + +class CreaturePower2(Base2): + __tablename__ = "creature_power" + + id = Column(Integer, primary_key=True) + creature = Column( + Integer, ForeignKey('creature.id'), nullable=False) + name = Column(Unicode) + description = Column(Unicode) + hitpower = Column(Integer, nullable=False) + +class Level2(Base2): + __tablename__ = "level" + + id = Column(Unicode, primary_key=True) + name = Column(Unicode) + description = Column(Unicode) + +class LevelExit2(Base2): + __tablename__ = "level_exit" + + id = Column(Integer, primary_key=True) + name = Column(Unicode) + from_level = Column( + Unicode, ForeignKey('level.id'), nullable=False) + to_level = Column( + Unicode, ForeignKey('level.id'), nullable=False) + +SET2_MODELS = [Creature2, CreaturePower2, Level2, LevelExit2] + + +@RegisterMigration(1, FULL_MIGRATIONS) +def creature_remove_is_demon(db_conn): + """ + Remove the is_demon field from the creature model. We don't need + it! + """ + # :( Commented out 'cuz of: + # http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=143&thanks=143&ts=1327882242 + + # metadata = MetaData(bind=db_conn.bind) + # creature_table = Table( + # 'creature', metadata, + # autoload=True, autoload_with=db_conn.bind) + # creature_table.drop_column('is_demon') + pass + + +@RegisterMigration(2, FULL_MIGRATIONS) +def creature_powers_new_table(db_conn): + """ + Add a new table for creature powers. Nothing needs to go in it + yet though as there wasn't anything that previously held this + information + """ + metadata = MetaData(bind=db_conn.bind) + + # We have to access the creature table so sqlalchemy can make the + # foreign key relationship + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.bind) + + creature_powers = Table( + 'creature_power', metadata, + Column('id', Integer, primary_key=True), + Column('creature', + Integer, ForeignKey('creature.id'), nullable=False), + Column('name', Unicode), + Column('description', Unicode), + Column('hitpower', Integer, nullable=False)) + metadata.create_all(db_conn.bind) + + +@RegisterMigration(3, FULL_MIGRATIONS) +def level_exits_new_table(db_conn): + """ + Make a new table for level exits and move the previously pickled + stuff over to here (then drop the old unneeded table) + """ + # First, create the table + # ----------------------- + metadata = MetaData(bind=db_conn.bind) + + # Minimal representation of level table. + # Not auto-introspecting here because of pickle table. I'm not + # sure sqlalchemy can auto-introspect pickle columns. + levels = Table( + 'level', metadata, + Column('id', Unicode, primary_key=True), + Column('name', Unicode), + Column('description', Unicode), + Column('exits', PickleType)) + + level_exits = Table( + 'level_exit', metadata, + Column('id', Integer, primary_key=True), + Column('name', Unicode), + Column('from_level', + Unicode, ForeignKey('level.id'), nullable=False), + Column('to_level', + Unicode, ForeignKey('level.id'), nullable=False)) + metadata.create_all(db_conn.bind) + + # And now, convert all the old exit pickles to new level exits + # ------------------------------------------------------------ + + # query over and insert + result = db_conn.execute( + select([levels], levels.c.exits!=None)) + + for level in result: + + for exit_name, to_level in level['exits'].iteritems(): + # Insert the level exit + db_conn.execute( + level_exits.insert().values( + name=exit_name, + from_level=level.id, + to_level=to_level)) + + # Finally, drop the old level exits pickle table + # ---------------------------------------------- + levels.drop_column('exits') + + +# A hack! At this point we freeze-fame and get just a partial list of +# migrations + +SET2_MIGRATIONS = copy.copy(FULL_MIGRATIONS) + +####################################################### +# Migration set 3: Final migrations +####################################################### + +Base3 = declarative_base(cls=GMGTableBase) + +class Creature3(Base3): + __tablename__ = "creature" + + id = Column(Integer, primary_key=True) + name = Column(Unicode, unique=True, nullable=False, index=True) + num_limbs= Column(Integer, nullable=False) + magical_powers = relationship("CreaturePower3") + +class CreaturePower3(Base3): + __tablename__ = "creature_power" + + id = Column(Integer, primary_key=True) + creature = Column( + Integer, ForeignKey('creature.id'), nullable=False, index=True) + name = Column(Unicode) + description = Column(Unicode) + hitpower = Column(Float, nullable=False) + +class Level3(Base3): + __tablename__ = "level" + + id = Column(Unicode, primary_key=True) + name = Column(Unicode) + description = Column(Unicode) + +class LevelExit3(Base3): + __tablename__ = "level_exit" + + id = Column(Integer, primary_key=True) + name = Column(Unicode) + from_level = Column( + Unicode, ForeignKey('level.id'), nullable=False, index=True) + to_level = Column( + Unicode, ForeignKey('level.id'), nullable=False, index=True) + + +SET3_MODELS = [Creature3, CreaturePower3, Level3, LevelExit3] +SET3_MIGRATIONS = FULL_MIGRATIONS + + +@RegisterMigration(4, FULL_MIGRATIONS) +def creature_num_legs_to_num_limbs(db_conn): + """ + Turns out we're tracking all sorts of limbs, not "legs" + specifically. Humans would be 4 here, for instance. So we + renamed the column. + """ + metadata = MetaData(bind=db_conn.bind) + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.bind) + creature_table.c.num_legs.alter(name=u"num_limbs") + + +@RegisterMigration(5, FULL_MIGRATIONS) +def level_exit_index_from_and_to_level(db_conn): + """ + Index the from and to levels of the level exit table. + """ + metadata = MetaData(bind=db_conn.bind) + level_exit = Table( + 'level_exit', metadata, + autoload=True, autoload_with=db_conn.bind) + Index('ix_level_exit_from_level', + level_exit.c.from_level).create(db_conn.bind) + Index('ix_level_exit_to_level', + level_exit.c.to_level).create(db_conn.bind) + + +@RegisterMigration(6, FULL_MIGRATIONS) +def creature_power_index_creature(db_conn): + """ + Index our foreign key relationship to the creatures + """ + metadata = MetaData(bind=db_conn.bind) + creature_power = Table( + 'creature_power', metadata, + autoload=True, autoload_with=db_conn.bind) + Index('ix_creature_power_creature', + creature_power.c.creature).create(db_conn.bind) + + +@RegisterMigration(7, FULL_MIGRATIONS) +def creature_power_hitpower_to_float(db_conn): + """ + Convert hitpower column on creature power table from integer to + float. + + Turns out we want super precise values of how much hitpower there + really is. + """ + metadata = MetaData(bind=db_conn.bind) + + # We have to access the creature table so sqlalchemy can make the + # foreign key relationship + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=db_conn.bind) + + creature_power = Table( + 'creature_power', metadata, + Column('id', Integer, primary_key=True), + Column('creature', Integer, + ForeignKey('creature.id'), nullable=False, + index=True), + Column('name', Unicode), + Column('description', Unicode), + Column('hitpower', Integer, nullable=False)) + + creature_power.c.hitpower.alter(type=Float) + + +@RegisterMigration(8, FULL_MIGRATIONS) +def creature_power_name_creature_unique(db_conn): + """ + Add a unique constraint to name and creature on creature_power. + + We don't want multiple creature powers with the same name per creature! + """ + # Note: We don't actually check to see if this constraint is set + # up because at present there's no way to do so in sqlalchemy :\ + + metadata = MetaData(bind=db_conn.bind) + + creature_power = Table( + 'creature_power', metadata, + autoload=True, autoload_with=db_conn.bind) + + cons = changeset.constraint.UniqueConstraint( + 'name', 'creature', table=creature_power) + + cons.create() + + +def _insert_migration1_objects(session): + """ + Test objects to insert for the first set of things + """ + # Insert creatures + session.add_all( + [Creature1(name=u'centipede', + num_legs=100, + is_demon=False), + Creature1(name=u'wolf', + num_legs=4, + is_demon=False), + # don't ask me what a wizardsnake is. + Creature1(name=u'wizardsnake', + num_legs=0, + is_demon=True)]) + + # Insert levels + session.add_all( + [Level1(id=u'necroplex', + name=u'The Necroplex', + description=u'A complex full of pure deathzone.', + exits={ + u'deathwell': u'evilstorm', + u'portal': u'central_park'}), + Level1(id=u'evilstorm', + name=u'Evil Storm', + description=u'A storm full of pure evil.', + exits={}), # you can't escape the evilstorm + Level1(id=u'central_park', + name=u'Central Park, NY, NY', + description=u"New York's friendly Central Park.", + exits={ + u'portal': u'necroplex'})]) + + session.commit() + + +def _insert_migration2_objects(session): + """ + Test objects to insert for the second set of things + """ + # Insert creatures + session.add_all( + [Creature2( + name=u'centipede', + num_legs=100), + Creature2( + name=u'wolf', + num_legs=4, + magical_powers = [ + CreaturePower2( + name=u"ice breath", + description=u"A blast of icy breath!", + hitpower=20), + CreaturePower2( + name=u"death stare", + description=u"A frightening stare, for sure!", + hitpower=45)]), + Creature2( + name=u'wizardsnake', + num_legs=0, + magical_powers=[ + CreaturePower2( + name=u'death_rattle', + description=u'A rattle... of DEATH!', + hitpower=1000), + CreaturePower2( + name=u'sneaky_stare', + description=u"The sneakiest stare you've ever seen!", + hitpower=300), + CreaturePower2( + name=u'slithery_smoke', + description=u"A blast of slithery, slithery smoke.", + hitpower=10), + CreaturePower2( + name=u'treacherous_tremors', + description=u"The ground shakes beneath footed animals!", + hitpower=0)])]) + + # Insert levels + session.add_all( + [Level2(id=u'necroplex', + name=u'The Necroplex', + description=u'A complex full of pure deathzone.'), + Level2(id=u'evilstorm', + name=u'Evil Storm', + description=u'A storm full of pure evil.', + exits=[]), # you can't escape the evilstorm + Level2(id=u'central_park', + name=u'Central Park, NY, NY', + description=u"New York's friendly Central Park.")]) + + # necroplex exits + session.add_all( + [LevelExit2(name=u'deathwell', + from_level=u'necroplex', + to_level=u'evilstorm'), + LevelExit2(name=u'portal', + from_level=u'necroplex', + to_level=u'central_park')]) + + # there are no evilstorm exits because there is no exit from the + # evilstorm + + # central park exits + session.add_all( + [LevelExit2(name=u'portal', + from_level=u'central_park', + to_level=u'necroplex')]) + + session.commit() + + +def _insert_migration3_objects(session): + """ + Test objects to insert for the third set of things + """ + # Insert creatures + session.add_all( + [Creature3( + name=u'centipede', + num_limbs=100), + Creature3( + name=u'wolf', + num_limbs=4, + magical_powers = [ + CreaturePower3( + name=u"ice breath", + description=u"A blast of icy breath!", + hitpower=20.0), + CreaturePower3( + name=u"death stare", + description=u"A frightening stare, for sure!", + hitpower=45.0)]), + Creature3( + name=u'wizardsnake', + num_limbs=0, + magical_powers=[ + CreaturePower3( + name=u'death_rattle', + description=u'A rattle... of DEATH!', + hitpower=1000.0), + CreaturePower3( + name=u'sneaky_stare', + description=u"The sneakiest stare you've ever seen!", + hitpower=300.0), + CreaturePower3( + name=u'slithery_smoke', + description=u"A blast of slithery, slithery smoke.", + hitpower=10.0), + CreaturePower3( + name=u'treacherous_tremors', + description=u"The ground shakes beneath footed animals!", + hitpower=0.0)])], + # annnnnd one more to test a floating point hitpower + Creature3( + name=u'deity', + numb_limbs=30, + magical_powers=[ + CreaturePower3( + name=u'smite', + description=u'Smitten by holy wrath!', + hitpower=9999.9)])) + + # Insert levels + session.add_all( + [Level3(id=u'necroplex', + name=u'The Necroplex', + description=u'A complex full of pure deathzone.'), + Level3(id=u'evilstorm', + name=u'Evil Storm', + description=u'A storm full of pure evil.', + exits=[]), # you can't escape the evilstorm + Level3(id=u'central_park', + name=u'Central Park, NY, NY', + description=u"New York's friendly Central Park.")]) + + # necroplex exits + session.add_all( + [LevelExit3(name=u'deathwell', + from_level=u'necroplex', + to_level=u'evilstorm'), + LevelExit3(name=u'portal', + from_level=u'necroplex', + to_level=u'central_park')]) + + # there are no evilstorm exits because there is no exit from the + # evilstorm + + # central park exits + session.add_all( + [LevelExit3(name=u'portal', + from_level=u'central_park', + to_level=u'necroplex')]) + + session.commit() + + +def create_test_engine(): + from sqlalchemy import create_engine + engine = create_engine('sqlite:///:memory:', echo=False) + Session = sessionmaker(bind=engine) + return engine, Session + + +def assert_col_type(column, this_class): + assert isinstance(column.type, this_class) + + +def _get_level3_exits(session, level): + return dict( + [(level_exit.name, level_exit.to_level) + for level_exit in + session.query(LevelExit3).filter_by(from_level=level.id)]) + + +def test_set1_to_set3(): + # Create / connect to database + # ---------------------------- + + engine, Session = create_test_engine() + + # Create tables by migrating on empty initial set + # ----------------------------------------------- + + printer = CollectingPrinter() + migration_manager = MigrationManager( + u'__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), + printer) + + # Check latest migration and database current migration + assert migration_manager.latest_migration == 0 + assert migration_manager.database_current_migration == None + + result = migration_manager.init_or_migrate() + + # Make sure output was "inited" + assert result == u'inited' + # Check output + assert printer.combined_string == ( + "-> Initializing main mediagoblin tables... done.\n") + # Check version in database + assert migration_manager.latest_migration == 0 + assert migration_manager.database_current_migration == 0 + + # Install the initial set + # ----------------------- + + _insert_migration1_objects(Session()) + + # Try to "re-migrate" with same manager settings... nothing should happen + migration_manager = MigrationManager( + u'__main__', SET1_MODELS, SET1_MIGRATIONS, Session(), + printer) + assert migration_manager.init_or_migrate() == None + + # Check version in database + assert migration_manager.latest_migration == 0 + assert migration_manager.database_current_migration == 0 + + # Sanity check a few things in the database... + metadata = MetaData(bind=engine) + + # Check the structure of the creature table + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=engine) + assert set(creature_table.c.keys()) == set( + ['id', 'name', 'num_legs', 'is_demon']) + assert_col_type(creature_table.c.id, Integer) + assert_col_type(creature_table.c.name, VARCHAR) + assert creature_table.c.name.nullable is False + #assert creature_table.c.name.index is True + #assert creature_table.c.name.unique is True + assert_col_type(creature_table.c.num_legs, Integer) + assert creature_table.c.num_legs.nullable is False + assert_col_type(creature_table.c.is_demon, Boolean) + + # Check the structure of the level table + level_table = Table( + 'level', metadata, + autoload=True, autoload_with=engine) + assert set(level_table.c.keys()) == set( + ['id', 'name', 'description', 'exits']) + assert_col_type(level_table.c.id, VARCHAR) + assert level_table.c.id.primary_key is True + assert_col_type(level_table.c.name, VARCHAR) + assert_col_type(level_table.c.description, VARCHAR) + # Skipping exits... Not sure if we can detect pickletype, not a + # big deal regardless. + + # Now check to see if stuff seems to be in there. + session = Session() + + creature = session.query(Creature1).filter_by( + name=u'centipede').one() + assert creature.num_legs == 100 + assert creature.is_demon == False + + creature = session.query(Creature1).filter_by( + name=u'wolf').one() + assert creature.num_legs == 4 + assert creature.is_demon == False + + creature = session.query(Creature1).filter_by( + name=u'wizardsnake').one() + assert creature.num_legs == 0 + assert creature.is_demon == True + + level = session.query(Level1).filter_by( + id=u'necroplex').one() + assert level.name == u'The Necroplex' + assert level.description == u'A complex full of pure deathzone.' + assert level.exits == { + 'deathwell': 'evilstorm', + 'portal': 'central_park'} + + level = session.query(Level1).filter_by( + id=u'evilstorm').one() + assert level.name == u'Evil Storm' + assert level.description == u'A storm full of pure evil.' + assert level.exits == {} # You still can't escape the evilstorm! + + level = session.query(Level1).filter_by( + id=u'central_park').one() + assert level.name == u'Central Park, NY, NY' + assert level.description == u"New York's friendly Central Park." + assert level.exits == { + 'portal': 'necroplex'} + + # Create new migration manager, but make sure the db migration + # isn't said to be updated yet + printer = CollectingPrinter() + migration_manager = MigrationManager( + u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), + printer) + + assert migration_manager.latest_migration == 8 + assert migration_manager.database_current_migration == 0 + + # Migrate + result = migration_manager.init_or_migrate() + + # Make sure result was "migrated" + assert result == u'migrated' + + # TODO: Check output to user + assert printer.combined_string == """\ +-> Updating main mediagoblin tables: + + Running migration 1, "creature_remove_is_demon"... done. + + Running migration 2, "creature_powers_new_table"... done. + + Running migration 3, "level_exits_new_table"... done. + + Running migration 4, "creature_num_legs_to_num_limbs"... done. + + Running migration 5, "level_exit_index_from_and_to_level"... done. + + Running migration 6, "creature_power_index_creature"... done. + + Running migration 7, "creature_power_hitpower_to_float"... done. + + Running migration 8, "creature_power_name_creature_unique"... done. +""" + + # Make sure version matches expected + migration_manager = MigrationManager( + u'__main__', SET3_MODELS, SET3_MIGRATIONS, Session(), + printer) + assert migration_manager.latest_migration == 8 + assert migration_manager.database_current_migration == 8 + + # Check all things in database match expected + + # Check the creature table + metadata = MetaData(bind=engine) + creature_table = Table( + 'creature', metadata, + autoload=True, autoload_with=engine) + # assert set(creature_table.c.keys()) == set( + # ['id', 'name', 'num_limbs']) + assert set(creature_table.c.keys()) == set( + [u'id', 'name', u'num_limbs', u'is_demon']) + assert_col_type(creature_table.c.id, Integer) + assert_col_type(creature_table.c.name, VARCHAR) + assert creature_table.c.name.nullable is False + #assert creature_table.c.name.index is True + #assert creature_table.c.name.unique is True + assert_col_type(creature_table.c.num_limbs, Integer) + assert creature_table.c.num_limbs.nullable is False + + # Check the CreaturePower table + creature_power_table = Table( + 'creature_power', metadata, + autoload=True, autoload_with=engine) + assert set(creature_power_table.c.keys()) == set( + ['id', 'creature', 'name', 'description', 'hitpower']) + assert_col_type(creature_power_table.c.id, Integer) + assert_col_type(creature_power_table.c.creature, Integer) + assert creature_power_table.c.creature.nullable is False + assert_col_type(creature_power_table.c.name, VARCHAR) + assert_col_type(creature_power_table.c.description, VARCHAR) + assert_col_type(creature_power_table.c.hitpower, Float) + assert creature_power_table.c.hitpower.nullable is False + + # Check the structure of the level table + level_table = Table( + 'level', metadata, + autoload=True, autoload_with=engine) + assert set(level_table.c.keys()) == set( + ['id', 'name', 'description']) + assert_col_type(level_table.c.id, VARCHAR) + assert level_table.c.id.primary_key is True + assert_col_type(level_table.c.name, VARCHAR) + assert_col_type(level_table.c.description, VARCHAR) + + # Check the structure of the level_exits table + level_exit_table = Table( + 'level_exit', metadata, + autoload=True, autoload_with=engine) + assert set(level_exit_table.c.keys()) == set( + ['id', 'name', 'from_level', 'to_level']) + assert_col_type(level_exit_table.c.id, Integer) + assert_col_type(level_exit_table.c.name, VARCHAR) + assert_col_type(level_exit_table.c.from_level, VARCHAR) + assert level_exit_table.c.from_level.nullable is False + #assert level_exit_table.c.from_level.index is True + assert_col_type(level_exit_table.c.to_level, VARCHAR) + assert level_exit_table.c.to_level.nullable is False + #assert level_exit_table.c.to_level.index is True + + # Now check to see if stuff seems to be in there. + session = Session() + creature = session.query(Creature3).filter_by( + name=u'centipede').one() + assert creature.num_limbs == 100.0 + assert creature.magical_powers == [] + + creature = session.query(Creature3).filter_by( + name=u'wolf').one() + assert creature.num_limbs == 4.0 + assert creature.magical_powers == [] + + creature = session.query(Creature3).filter_by( + name=u'wizardsnake').one() + assert creature.num_limbs == 0.0 + assert creature.magical_powers == [] + + level = session.query(Level3).filter_by( + id=u'necroplex').one() + assert level.name == u'The Necroplex' + assert level.description == u'A complex full of pure deathzone.' + level_exits = _get_level3_exits(session, level) + assert level_exits == { + u'deathwell': u'evilstorm', + u'portal': u'central_park'} + + level = session.query(Level3).filter_by( + id=u'evilstorm').one() + assert level.name == u'Evil Storm' + assert level.description == u'A storm full of pure evil.' + level_exits = _get_level3_exits(session, level) + assert level_exits == {} # You still can't escape the evilstorm! + + level = session.query(Level3).filter_by( + id=u'central_park').one() + assert level.name == u'Central Park, NY, NY' + assert level.description == u"New York's friendly Central Park." + level_exits = _get_level3_exits(session, level) + assert level_exits == { + 'portal': 'necroplex'} + + +#def test_set2_to_set3(): + # Create / connect to database + # Create tables by migrating on empty initial set + + # Install the initial set + # Check version in database + # Sanity check a few things in the database + + # Migrate + # Make sure version matches expected + # Check all things in database match expected + # pass + + +#def test_set1_to_set2_to_set3(): + # Create / connect to database + # Create tables by migrating on empty initial set + + # Install the initial set + # Check version in database + # Sanity check a few things in the database + + # Migrate + # Make sure version matches expected + # Check all things in database match expected + + # Migrate again + # Make sure version matches expected again + # Check all things in database match expected again + + ##### Set2 + # creature_table = Table( + # 'creature', metadata, + # autoload=True, autoload_with=db_conn.bind) + # assert set(creature_table.c.keys()) == set( + # ['id', 'name', 'num_legs']) + # assert_col_type(creature_table.c.id, Integer) + # assert_col_type(creature_table.c.name, VARCHAR) + # assert creature_table.c.name.nullable is False + # assert creature_table.c.name.index is True + # assert creature_table.c.name.unique is True + # assert_col_type(creature_table.c.num_legs, Integer) + # assert creature_table.c.num_legs.nullable is False + + # # Check the CreaturePower table + # creature_power_table = Table( + # 'creature_power', metadata, + # autoload=True, autoload_with=db_conn.bind) + # assert set(creature_power_table.c.keys()) == set( + # ['id', 'creature', 'name', 'description', 'hitpower']) + # assert_col_type(creature_power_table.c.id, Integer) + # assert_col_type(creature_power_table.c.creature, Integer) + # assert creature_power_table.c.creature.nullable is False + # assert_col_type(creature_power_table.c.name, VARCHAR) + # assert_col_type(creature_power_table.c.description, VARCHAR) + # assert_col_type(creature_power_table.c.hitpower, Integer) + # assert creature_power_table.c.hitpower.nullable is False + + # # Check the structure of the level table + # level_table = Table( + # 'level', metadata, + # autoload=True, autoload_with=db_conn.bind) + # assert set(level_table.c.keys()) == set( + # ['id', 'name', 'description']) + # assert_col_type(level_table.c.id, VARCHAR) + # assert level_table.c.id.primary_key is True + # assert_col_type(level_table.c.name, VARCHAR) + # assert_col_type(level_table.c.description, VARCHAR) + + # # Check the structure of the level_exits table + # level_exit_table = Table( + # 'level_exit', metadata, + # autoload=True, autoload_with=db_conn.bind) + # assert set(level_exit_table.c.keys()) == set( + # ['id', 'name', 'from_level', 'to_level']) + # assert_col_type(level_exit_table.c.id, Integer) + # assert_col_type(level_exit_table.c.name, VARCHAR) + # assert_col_type(level_exit_table.c.from_level, VARCHAR) + # assert level_exit_table.c.from_level.nullable is False + # assert_col_type(level_exit_table.c.to_level, VARCHAR) + + # pass diff --git a/mediagoblin/tests/test_staticdirect.py b/mediagoblin/tests/test_staticdirect.py new file mode 100644 index 00000000..3a9e2fd9 --- /dev/null +++ b/mediagoblin/tests/test_staticdirect.py @@ -0,0 +1,9 @@ +from mediagoblin.tools import staticdirect + +def test_staticdirect(): + sdirect = staticdirect.StaticDirect( + {None: "/static/", + "theme": "http://example.org/themestatic"}) + assert sdirect("css/monkeys.css") == "/static/css/monkeys.css" + assert sdirect("images/lollerskate.png", "theme") == \ + "http://example.org/themestatic/images/lollerskate.png" diff --git a/mediagoblin/tests/test_storage.py b/mediagoblin/tests/test_storage.py new file mode 100644 index 00000000..f6f1d18f --- /dev/null +++ b/mediagoblin/tests/test_storage.py @@ -0,0 +1,321 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import os +import tempfile + +import pytest +from werkzeug.utils import secure_filename + +from mediagoblin import storage + + +################ +# Test utilities +################ + +def test_clean_listy_filepath(): + expected = [u'dir1', u'dir2', u'linooks.jpg'] + assert storage.clean_listy_filepath( + ['dir1', 'dir2', 'linooks.jpg']) == expected + + expected = [u'dir1', u'foo_.._nasty', u'linooks.jpg'] + assert storage.clean_listy_filepath( + ['/dir1/', 'foo/../nasty', 'linooks.jpg']) == expected + + expected = [u'etc', u'passwd'] + assert storage.clean_listy_filepath( + ['../../../etc/', 'passwd']) == expected + + with pytest.raises(storage.InvalidFilepath): + storage.clean_listy_filepath(['../../', 'linooks.jpg']) + + +class FakeStorageSystem(): + def __init__(self, foobie, blech, **kwargs): + self.foobie = foobie + self.blech = blech + +class FakeRemoteStorage(storage.filestorage.BasicFileStorage): + # Theoretically despite this, all the methods should work but it + # should force copying to the workbench + local_storage = False + + def copy_local_to_storage(self, *args, **kwargs): + return storage.StorageInterface.copy_local_to_storage( + self, *args, **kwargs) + + +def test_storage_system_from_config(): + this_storage = storage.storage_system_from_config( + {'base_url': 'http://example.org/moodia/', + 'base_dir': '/tmp/', + 'garbage_arg': 'garbage_arg', + 'garbage_arg': 'trash'}) + assert this_storage.base_url == 'http://example.org/moodia/' + assert this_storage.base_dir == '/tmp/' + assert this_storage.__class__ is storage.filestorage.BasicFileStorage + + this_storage = storage.storage_system_from_config( + {'foobie': 'eiboof', + 'blech': 'hcelb', + 'garbage_arg': 'garbage_arg', + 'storage_class': + 'mediagoblin.tests.test_storage:FakeStorageSystem'}) + assert this_storage.foobie == 'eiboof' + assert this_storage.blech == 'hcelb' + assert unicode(this_storage.__class__) == \ + u'mediagoblin.tests.test_storage.FakeStorageSystem' + + +########################## +# Basic file storage tests +########################## + +def get_tmp_filestorage(mount_url=None, fake_remote=False): + tmpdir = tempfile.mkdtemp(prefix="test_gmg_storage") + if fake_remote: + this_storage = FakeRemoteStorage(tmpdir, mount_url) + else: + this_storage = storage.filestorage.BasicFileStorage(tmpdir, mount_url) + return tmpdir, this_storage + + +def cleanup_storage(this_storage, tmpdir, *paths): + for p in paths: + while p: + assert this_storage.delete_dir(p) == True + p.pop(-1) + os.rmdir(tmpdir) + + +def test_basic_storage__resolve_filepath(): + tmpdir, this_storage = get_tmp_filestorage() + + result = this_storage._resolve_filepath(['dir1', 'dir2', 'filename.jpg']) + assert result == os.path.join( + tmpdir, 'dir1/dir2/filename.jpg') + + result = this_storage._resolve_filepath(['../../etc/', 'passwd']) + assert result == os.path.join( + tmpdir, 'etc/passwd') + + pytest.raises( + storage.InvalidFilepath, + this_storage._resolve_filepath, + ['../../', 'etc', 'passwd']) + + cleanup_storage(this_storage, tmpdir) + + +def test_basic_storage_file_exists(): + tmpdir, this_storage = get_tmp_filestorage() + + os.makedirs(os.path.join(tmpdir, 'dir1', 'dir2')) + filename = os.path.join(tmpdir, 'dir1', 'dir2', 'filename.txt') + with open(filename, 'w') as ourfile: + ourfile.write("I'm having a lovely day!") + + assert this_storage.file_exists(['dir1', 'dir2', 'filename.txt']) + assert not this_storage.file_exists(['dir1', 'dir2', 'thisfile.lol']) + assert not this_storage.file_exists(['dnedir1', 'dnedir2', 'somefile.lol']) + + this_storage.delete_file(['dir1', 'dir2', 'filename.txt']) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + + +def test_basic_storage_get_unique_filepath(): + tmpdir, this_storage = get_tmp_filestorage() + + # write something that exists + os.makedirs(os.path.join(tmpdir, 'dir1', 'dir2')) + filename = os.path.join(tmpdir, 'dir1', 'dir2', 'filename.txt') + with open(filename, 'w') as ourfile: + ourfile.write("I'm having a lovely day!") + + # now we want something new, with the same name! + new_filepath = this_storage.get_unique_filepath( + ['dir1', 'dir2', 'filename.txt']) + assert new_filepath[:-1] == [u'dir1', u'dir2'] + + new_filename = new_filepath[-1] + assert new_filename.endswith('filename.txt') + assert len(new_filename) > len('filename.txt') + assert new_filename == secure_filename(new_filename) + + os.remove(filename) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + + +def test_basic_storage_get_file(): + tmpdir, this_storage = get_tmp_filestorage() + + # Write a brand new file + filepath = ['dir1', 'dir2', 'ourfile.txt'] + + with this_storage.get_file(filepath, 'w') as our_file: + our_file.write('First file') + with this_storage.get_file(filepath, 'r') as our_file: + assert our_file.read() == 'First file' + assert os.path.exists(os.path.join(tmpdir, 'dir1/dir2/ourfile.txt')) + with file(os.path.join(tmpdir, 'dir1/dir2/ourfile.txt'), 'r') as our_file: + assert our_file.read() == 'First file' + + # Write to the same path but try to get a unique file. + new_filepath = this_storage.get_unique_filepath(filepath) + assert not os.path.exists(os.path.join(tmpdir, *new_filepath)) + + with this_storage.get_file(new_filepath, 'w') as our_file: + our_file.write('Second file') + with this_storage.get_file(new_filepath, 'r') as our_file: + assert our_file.read() == 'Second file' + assert os.path.exists(os.path.join(tmpdir, *new_filepath)) + with file(os.path.join(tmpdir, *new_filepath), 'r') as our_file: + assert our_file.read() == 'Second file' + + # Read from an existing file + manually_written_file = os.makedirs( + os.path.join(tmpdir, 'testydir')) + with file(os.path.join(tmpdir, 'testydir/testyfile.txt'), 'w') as testyfile: + testyfile.write('testy file! so testy.') + + with this_storage.get_file(['testydir', 'testyfile.txt']) as testyfile: + assert testyfile.read() == 'testy file! so testy.' + + this_storage.delete_file(filepath) + this_storage.delete_file(new_filepath) + this_storage.delete_file(['testydir', 'testyfile.txt']) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2'], ['testydir']) + + +def test_basic_storage_delete_file(): + tmpdir, this_storage = get_tmp_filestorage() + + assert not os.path.exists( + os.path.join(tmpdir, 'dir1/dir2/ourfile.txt')) + + filepath = ['dir1', 'dir2', 'ourfile.txt'] + with this_storage.get_file(filepath, 'w') as our_file: + our_file.write('Testing this file') + + assert os.path.exists( + os.path.join(tmpdir, 'dir1/dir2/ourfile.txt')) + + assert this_storage.delete_dir(['dir1', 'dir2']) == False + this_storage.delete_file(filepath) + assert this_storage.delete_dir(['dir1', 'dir2']) == True + + assert not os.path.exists( + os.path.join(tmpdir, 'dir1/dir2/ourfile.txt')) + + cleanup_storage(this_storage, tmpdir, ['dir1']) + + +def test_basic_storage_url_for_file(): + # Not supplying a base_url should actually just bork. + tmpdir, this_storage = get_tmp_filestorage() + pytest.raises( + storage.NoWebServing, + this_storage.file_url, + ['dir1', 'dir2', 'filename.txt']) + cleanup_storage(this_storage, tmpdir) + + # base_url without domain + tmpdir, this_storage = get_tmp_filestorage('/media/') + result = this_storage.file_url( + ['dir1', 'dir2', 'filename.txt']) + expected = '/media/dir1/dir2/filename.txt' + assert result == expected + cleanup_storage(this_storage, tmpdir) + + # base_url with domain + tmpdir, this_storage = get_tmp_filestorage( + 'http://media.example.org/ourmedia/') + result = this_storage.file_url( + ['dir1', 'dir2', 'filename.txt']) + expected = 'http://media.example.org/ourmedia/dir1/dir2/filename.txt' + assert result == expected + cleanup_storage(this_storage, tmpdir) + + +def test_basic_storage_get_local_path(): + tmpdir, this_storage = get_tmp_filestorage() + + result = this_storage.get_local_path( + ['dir1', 'dir2', 'filename.txt']) + + expected = os.path.join( + tmpdir, 'dir1/dir2/filename.txt') + + assert result == expected + + cleanup_storage(this_storage, tmpdir) + + +def test_basic_storage_is_local(): + tmpdir, this_storage = get_tmp_filestorage() + assert this_storage.local_storage is True + cleanup_storage(this_storage, tmpdir) + + +def test_basic_storage_copy_locally(): + tmpdir, this_storage = get_tmp_filestorage() + + dest_tmpdir = tempfile.mkdtemp() + + filepath = ['dir1', 'dir2', 'ourfile.txt'] + with this_storage.get_file(filepath, 'w') as our_file: + our_file.write('Testing this file') + + new_file_dest = os.path.join(dest_tmpdir, 'file2.txt') + + this_storage.copy_locally(filepath, new_file_dest) + this_storage.delete_file(filepath) + + assert file(new_file_dest).read() == 'Testing this file' + + os.remove(new_file_dest) + os.rmdir(dest_tmpdir) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + + +def _test_copy_local_to_storage_works(tmpdir, this_storage): + local_filename = tempfile.mktemp() + with file(local_filename, 'w') as tmpfile: + tmpfile.write('haha') + + this_storage.copy_local_to_storage( + local_filename, ['dir1', 'dir2', 'copiedto.txt']) + + os.remove(local_filename) + + assert file( + os.path.join(tmpdir, 'dir1/dir2/copiedto.txt'), + 'r').read() == 'haha' + + this_storage.delete_file(['dir1', 'dir2', 'copiedto.txt']) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + + +def test_basic_storage_copy_local_to_storage(): + tmpdir, this_storage = get_tmp_filestorage() + _test_copy_local_to_storage_works(tmpdir, this_storage) + + +def test_general_storage_copy_local_to_storage(): + tmpdir, this_storage = get_tmp_filestorage(fake_remote=True) + _test_copy_local_to_storage_works(tmpdir, this_storage) diff --git a/mediagoblin/tests/test_submission.py b/mediagoblin/tests/test_submission.py new file mode 100644 index 00000000..162b2d19 --- /dev/null +++ b/mediagoblin/tests/test_submission.py @@ -0,0 +1,294 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys +reload(sys) +sys.setdefaultencoding('utf-8') + +import urlparse +import os +import pytest + +from mediagoblin.tests.tools import fixture_add_user +from mediagoblin import mg_globals +from mediagoblin.db.models import MediaEntry +from mediagoblin.tools import template +from mediagoblin.media_types.image import MEDIA_MANAGER as img_MEDIA_MANAGER +from mediagoblin.media_types.pdf.processing import check_prerequisites as pdf_check_prerequisites + +from .resources import GOOD_JPG, GOOD_PNG, EVIL_FILE, EVIL_JPG, EVIL_PNG, \ + BIG_BLUE, GOOD_PDF, GPS_JPG + +GOOD_TAG_STRING = u'yin,yang' +BAD_TAG_STRING = unicode('rage,' + 'f' * 26 + 'u' * 26) + +FORM_CONTEXT = ['mediagoblin/submit/start.html', 'submit_form'] +REQUEST_CONTEXT = ['mediagoblin/user_pages/user.html', 'request'] + + +class TestSubmission: + @pytest.fixture(autouse=True) + def setup(self, test_app): + self.test_app = test_app + + # TODO: Possibly abstract into a decorator like: + # @as_authenticated_user('chris') + test_user = fixture_add_user() + + self.test_user = test_user + + self.login() + + def login(self): + self.test_app.post( + '/auth/login/', { + 'username': u'chris', + 'password': 'toast'}) + + def logout(self): + self.test_app.get('/auth/logout/') + + def do_post(self, data, *context_keys, **kwargs): + url = kwargs.pop('url', '/submit/') + do_follow = kwargs.pop('do_follow', False) + template.clear_test_template_context() + response = self.test_app.post(url, data, **kwargs) + if do_follow: + response.follow() + context_data = template.TEMPLATE_TEST_CONTEXT + for key in context_keys: + context_data = context_data[key] + return response, context_data + + def upload_data(self, filename): + return {'upload_files': [('file', filename)]} + + def check_comments(self, request, media_id, count): + comments = request.db.MediaComment.find({'media_entry': media_id}) + assert count == len(list(comments)) + + def test_missing_fields(self): + # Test blank form + # --------------- + response, form = self.do_post({}, *FORM_CONTEXT) + assert form.file.errors == [u'You must provide a file.'] + + # Test blank file + # --------------- + response, form = self.do_post({'title': u'test title'}, *FORM_CONTEXT) + assert form.file.errors == [u'You must provide a file.'] + + def check_url(self, response, path): + assert urlparse.urlsplit(response.location)[2] == path + + def check_normal_upload(self, title, filename): + response, context = self.do_post({'title': title}, do_follow=True, + **self.upload_data(filename)) + self.check_url(response, '/u/{0}/'.format(self.test_user.username)) + assert 'mediagoblin/user_pages/user.html' in context + # Make sure the media view is at least reachable, logged in... + url = '/u/{0}/m/{1}/'.format(self.test_user.username, + title.lower().replace(' ', '-')) + self.test_app.get(url) + # ... and logged out too. + self.logout() + self.test_app.get(url) + + def test_normal_jpg(self): + self.check_normal_upload(u'Normal upload 1', GOOD_JPG) + + def test_normal_png(self): + self.check_normal_upload(u'Normal upload 2', GOOD_PNG) + + @pytest.mark.skipif("not pdf_check_prerequisites()") + def test_normal_pdf(self): + response, context = self.do_post({'title': u'Normal upload 3 (pdf)'}, + do_follow=True, + **self.upload_data(GOOD_PDF)) + self.check_url(response, '/u/{0}/'.format(self.test_user.username)) + assert 'mediagoblin/user_pages/user.html' in context + + def check_media(self, request, find_data, count=None): + media = MediaEntry.find(find_data) + if count is not None: + assert media.count() == count + if count == 0: + return + return media[0] + + def test_tags(self): + # Good tag string + # -------- + response, request = self.do_post({'title': u'Balanced Goblin 2', + 'tags': GOOD_TAG_STRING}, + *REQUEST_CONTEXT, do_follow=True, + **self.upload_data(GOOD_JPG)) + media = self.check_media(request, {'title': u'Balanced Goblin 2'}, 1) + assert media.tags[0]['name'] == u'yin' + assert media.tags[0]['slug'] == u'yin' + + assert media.tags[1]['name'] == u'yang' + assert media.tags[1]['slug'] == u'yang' + + # Test tags that are too long + # --------------- + response, form = self.do_post({'title': u'Balanced Goblin 2', + 'tags': BAD_TAG_STRING}, + *FORM_CONTEXT, + **self.upload_data(GOOD_JPG)) + assert form.tags.errors == [ + u'Tags must be shorter than 50 characters. ' \ + 'Tags that are too long: ' \ + 'ffffffffffffffffffffffffffuuuuuuuuuuuuuuuuuuuuuuuuuu'] + + def test_delete(self): + response, request = self.do_post({'title': u'Balanced Goblin'}, + *REQUEST_CONTEXT, do_follow=True, + **self.upload_data(GOOD_JPG)) + media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) + media_id = media.id + + # render and post to the edit page. + edit_url = request.urlgen( + 'mediagoblin.edit.edit_media', + user=self.test_user.username, media_id=media_id) + self.test_app.get(edit_url) + self.test_app.post(edit_url, + {'title': u'Balanced Goblin', + 'slug': u"Balanced=Goblin", + 'tags': u''}) + media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) + assert media.slug == u"balanced-goblin" + + # Add a comment, so we can test for its deletion later. + self.check_comments(request, media_id, 0) + comment_url = request.urlgen( + 'mediagoblin.user_pages.media_post_comment', + user=self.test_user.username, media_id=media_id) + response = self.do_post({'comment_content': 'i love this test'}, + url=comment_url, do_follow=True)[0] + self.check_comments(request, media_id, 1) + + # Do not confirm deletion + # --------------------------------------------------- + delete_url = request.urlgen( + 'mediagoblin.user_pages.media_confirm_delete', + user=self.test_user.username, media_id=media_id) + # Empty data means don't confirm + response = self.do_post({}, do_follow=True, url=delete_url)[0] + media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) + media_id = media.id + + # Confirm deletion + # --------------------------------------------------- + response, request = self.do_post({'confirm': 'y'}, *REQUEST_CONTEXT, + do_follow=True, url=delete_url) + self.check_media(request, {'id': media_id}, 0) + self.check_comments(request, media_id, 0) + + def test_evil_file(self): + # Test non-suppoerted file with non-supported extension + # ----------------------------------------------------- + response, form = self.do_post({'title': u'Malicious Upload 1'}, + *FORM_CONTEXT, + **self.upload_data(EVIL_FILE)) + assert len(form.file.errors) == 1 + assert 'Sorry, I don\'t support that file type :(' == \ + str(form.file.errors[0]) + + + def test_get_media_manager(self): + """Test if the get_media_manger function returns sensible things + """ + response, request = self.do_post({'title': u'Balanced Goblin'}, + *REQUEST_CONTEXT, do_follow=True, + **self.upload_data(GOOD_JPG)) + media = self.check_media(request, {'title': u'Balanced Goblin'}, 1) + + assert media.media_type == u'mediagoblin.media_types.image' + assert isinstance(media.media_manager, img_MEDIA_MANAGER) + assert media.media_manager.entry == media + + + def test_sniffing(self): + ''' + Test sniffing mechanism to assert that regular uploads work as intended + ''' + template.clear_test_template_context() + response = self.test_app.post( + '/submit/', { + 'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE' + }, upload_files=[( + 'file', GOOD_JPG)]) + + response.follow() + + context = template.TEMPLATE_TEST_CONTEXT['mediagoblin/user_pages/user.html'] + + request = context['request'] + + media = request.db.MediaEntry.find_one({ + u'title': u'UNIQUE_TITLE_PLS_DONT_CREATE_OTHER_MEDIA_WITH_THIS_TITLE'}) + + assert media.media_type == 'mediagoblin.media_types.image' + + def check_false_image(self, title, filename): + # NOTE: The following 2 tests will ultimately fail, but they + # *will* pass the initial form submission step. Instead, + # they'll be caught as failures during the processing step. + response, context = self.do_post({'title': title}, do_follow=True, + **self.upload_data(filename)) + self.check_url(response, '/u/{0}/'.format(self.test_user.username)) + entry = mg_globals.database.MediaEntry.find_one({'title': title}) + assert entry.state == 'failed' + assert entry.fail_error == u'mediagoblin.processing:BadMediaFail' + + def test_evil_jpg(self): + # Test non-supported file with .jpg extension + # ------------------------------------------- + self.check_false_image(u'Malicious Upload 2', EVIL_JPG) + + def test_evil_png(self): + # Test non-supported file with .png extension + # ------------------------------------------- + self.check_false_image(u'Malicious Upload 3', EVIL_PNG) + + def test_media_data(self): + self.check_normal_upload(u"With GPS data", GPS_JPG) + media = self.check_media(None, {"title": u"With GPS data"}, 1) + assert media.media_data.gps_latitude == 59.336666666666666 + + def test_processing(self): + public_store_dir = mg_globals.global_config[ + 'storage:publicstore']['base_dir'] + + data = {'title': u'Big Blue'} + response, request = self.do_post(data, *REQUEST_CONTEXT, do_follow=True, + **self.upload_data(BIG_BLUE)) + media = self.check_media(request, data, 1) + last_size = 1024 ** 3 # Needs to be larger than bigblue.png + for key, basename in (('original', 'bigblue.png'), + ('medium', 'bigblue.medium.png'), + ('thumb', 'bigblue.thumbnail.png')): + # Does the processed image have a good filename? + filename = os.path.join( + public_store_dir, + *media.media_files[key]) + assert filename.endswith('_' + basename) + # Is it smaller than the last processed image we looked at? + size = os.stat(filename).st_size + assert last_size > size + last_size = size diff --git a/mediagoblin/tests/test_submission/bigblue.png b/mediagoblin/tests/test_submission/bigblue.png Binary files differnew file mode 100644 index 00000000..2b2c2a44 --- /dev/null +++ b/mediagoblin/tests/test_submission/bigblue.png diff --git a/mediagoblin/tests/test_submission/evil b/mediagoblin/tests/test_submission/evil new file mode 100755 index 00000000..2c850e29 --- /dev/null +++ b/mediagoblin/tests/test_submission/evil @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "In yer base, doin spooky things"
\ No newline at end of file diff --git a/mediagoblin/tests/test_submission/evil.jpg b/mediagoblin/tests/test_submission/evil.jpg new file mode 100755 index 00000000..2c850e29 --- /dev/null +++ b/mediagoblin/tests/test_submission/evil.jpg @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "In yer base, doin spooky things"
\ No newline at end of file diff --git a/mediagoblin/tests/test_submission/evil.png b/mediagoblin/tests/test_submission/evil.png new file mode 100755 index 00000000..2c850e29 --- /dev/null +++ b/mediagoblin/tests/test_submission/evil.png @@ -0,0 +1,3 @@ +#!/bin/sh + +echo "In yer base, doin spooky things"
\ No newline at end of file diff --git a/mediagoblin/tests/test_submission/good.jpg b/mediagoblin/tests/test_submission/good.jpg Binary files differnew file mode 100644 index 00000000..936458e9 --- /dev/null +++ b/mediagoblin/tests/test_submission/good.jpg 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_submission/good.png b/mediagoblin/tests/test_submission/good.png Binary files differnew file mode 100644 index 00000000..c1eadf9c --- /dev/null +++ b/mediagoblin/tests/test_submission/good.png diff --git a/mediagoblin/tests/test_tags.py b/mediagoblin/tests/test_tags.py new file mode 100644 index 00000000..e25cc283 --- /dev/null +++ b/mediagoblin/tests/test_tags.py @@ -0,0 +1,39 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools import text + +def test_list_of_dicts_conversion(test_app): + """ + When the user adds tags to a media entry, the string from the form is + converted into a list of tags, where each tag is stored in the database + as a dict. Each tag dict should contain the tag's name and slug. Another + function performs the reverse operation when populating a form to edit tags. + """ + # Leading, trailing, and internal whitespace should be removed and slugified + assert text.convert_to_tag_list_of_dicts('sleep , 6 AM, chainsaw! ') == [ + {'name': u'sleep', 'slug': u'sleep'}, + {'name': u'6 AM', 'slug': u'6-am'}, + {'name': u'chainsaw!', 'slug': u'chainsaw'}] + + # If the user enters two identical tags, record only one of them + assert text.convert_to_tag_list_of_dicts('echo,echo') == [{'name': u'echo', + 'slug': u'echo'}] + + # Make sure converting the list of dicts to a string works + assert text.media_tags_as_string([{'name': u'yin', 'slug': u'yin'}, + {'name': u'yang', 'slug': u'yang'}]) == \ + u'yin, yang' diff --git a/mediagoblin/tests/test_timesince.py b/mediagoblin/tests/test_timesince.py new file mode 100644 index 00000000..6579eb09 --- /dev/null +++ b/mediagoblin/tests/test_timesince.py @@ -0,0 +1,57 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from datetime import datetime, timedelta + +from mediagoblin.tools.timesince import is_aware, timesince + + +def test_timesince(): + test_time = datetime.now() + + # it should ignore second and microseconds + assert timesince(test_time, test_time + timedelta(microseconds=1)) == "0 minutes" + assert timesince(test_time, test_time + timedelta(seconds=1)) == "0 minutes" + + # test minutes, hours, days, weeks, months and years (singular and plural) + assert timesince(test_time, test_time + timedelta(minutes=1)) == "1 minute" + assert timesince(test_time, test_time + timedelta(minutes=2)) == "2 minutes" + + assert timesince(test_time, test_time + timedelta(hours=1)) == "1 hour" + assert timesince(test_time, test_time + timedelta(hours=2)) == "2 hours" + + assert timesince(test_time, test_time + timedelta(days=1)) == "1 day" + assert timesince(test_time, test_time + timedelta(days=2)) == "2 days" + + assert timesince(test_time, test_time + timedelta(days=7)) == "1 week" + assert timesince(test_time, test_time + timedelta(days=14)) == "2 weeks" + + assert timesince(test_time, test_time + timedelta(days=30)) == "1 month" + assert timesince(test_time, test_time + timedelta(days=60)) == "2 months" + + assert timesince(test_time, test_time + timedelta(days=365)) == "1 year" + assert timesince(test_time, test_time + timedelta(days=730)) == "2 years" + + # okay now we want to test combinations + # e.g. 1 hour, 5 days + assert timesince(test_time, test_time + timedelta(days=5, hours=1)) == "5 days, 1 hour" + + assert timesince(test_time, test_time + timedelta(days=15)) == "2 weeks, 1 day" + + assert timesince(test_time, test_time + timedelta(days=97)) == "3 months, 1 week" + + assert timesince(test_time, test_time + timedelta(days=2250)) == "6 years, 2 months" + diff --git a/mediagoblin/tests/test_util.py b/mediagoblin/tests/test_util.py new file mode 100644 index 00000000..bc14f528 --- /dev/null +++ b/mediagoblin/tests/test_util.py @@ -0,0 +1,145 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import email + +from mediagoblin.tools import common, url, translate, mail, text, testing + +testing._activate_testing() + + +def _import_component_testing_method(silly_string): + # Just for the sake of testing that our component importer works. + return u"'%s' is the silliest string I've ever seen" % silly_string + + +def test_import_component(): + imported_func = common.import_component( + 'mediagoblin.tests.test_util:_import_component_testing_method') + result = imported_func('hooobaladoobala') + expected = u"'hooobaladoobala' is the silliest string I've ever seen" + assert result == expected + + +def test_send_email(): + mail._clear_test_inboxes() + + # send the email + mail.send_email( + "sender@mediagoblin.example.org", + ["amanda@example.org", "akila@example.org"], + "Testing is so much fun!", + """HAYYY GUYS! + +I hope you like unit tests JUST AS MUCH AS I DO!""") + + # check the main inbox + assert len(mail.EMAIL_TEST_INBOX) == 1 + message = mail.EMAIL_TEST_INBOX.pop() + assert message['From'] == "sender@mediagoblin.example.org" + assert message['To'] == "amanda@example.org, akila@example.org" + assert message['Subject'] == "Testing is so much fun!" + assert message.get_payload(decode=True) == """HAYYY GUYS! + +I hope you like unit tests JUST AS MUCH AS I DO!""" + + # Check everything that the FakeMhost.sendmail() method got is correct + assert len(mail.EMAIL_TEST_MBOX_INBOX) == 1 + mbox_dict = mail.EMAIL_TEST_MBOX_INBOX.pop() + assert mbox_dict['from'] == "sender@mediagoblin.example.org" + assert mbox_dict['to'] == ["amanda@example.org", "akila@example.org"] + mbox_message = email.message_from_string(mbox_dict['message']) + assert mbox_message['From'] == "sender@mediagoblin.example.org" + assert mbox_message['To'] == "amanda@example.org, akila@example.org" + assert mbox_message['Subject'] == "Testing is so much fun!" + assert mbox_message.get_payload(decode=True) == """HAYYY GUYS! + +I hope you like unit tests JUST AS MUCH AS I DO!""" + +def test_slugify(): + assert url.slugify(u'a walk in the park') == u'a-walk-in-the-park' + assert url.slugify(u'A Walk in the Park') == u'a-walk-in-the-park' + assert url.slugify(u'a walk in the park') == u'a-walk-in-the-park' + assert url.slugify(u'a walk in-the-park') == u'a-walk-in-the-park' + assert url.slugify(u'a w@lk in the park?') == u'a-w-lk-in-the-park' + assert url.slugify(u'a walk in the par\u0107') == u'a-walk-in-the-parc' + assert url.slugify(u'\u00E0\u0042\u00E7\u010F\u00EB\u0066') == u'abcdef' + +def test_locale_to_lower_upper(): + """ + Test cc.i18n.util.locale_to_lower_upper() + """ + assert translate.locale_to_lower_upper('en') == 'en' + assert translate.locale_to_lower_upper('en_US') == 'en_US' + assert translate.locale_to_lower_upper('en-us') == 'en_US' + + # crazy renditions. Useful? + assert translate.locale_to_lower_upper('en-US') == 'en_US' + assert translate.locale_to_lower_upper('en_us') == 'en_US' + + +def test_locale_to_lower_lower(): + """ + Test cc.i18n.util.locale_to_lower_lower() + """ + assert translate.locale_to_lower_lower('en') == 'en' + assert translate.locale_to_lower_lower('en_US') == 'en-us' + assert translate.locale_to_lower_lower('en-us') == 'en-us' + + # crazy renditions. Useful? + assert translate.locale_to_lower_lower('en-US') == 'en-us' + assert translate.locale_to_lower_lower('en_us') == 'en-us' + + +def test_gettext_lazy_proxy(): + from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + from mediagoblin.tools.translate import pass_to_ugettext, set_thread_locale + proxy = _(u"Password") + orig = u"Password" + + set_thread_locale("es") + p1 = unicode(proxy) + p1_should = pass_to_ugettext(orig) + assert p1_should != orig, "Test useless, string not translated" + assert p1 == p1_should + + set_thread_locale("sv") + p2 = unicode(proxy) + p2_should = pass_to_ugettext(orig) + assert p2_should != orig, "Test broken, string not translated" + assert p2 == p2_should + + assert p1_should != p2_should, "Test broken, same translated string" + assert p1 != p2 + + +def test_html_cleaner(): + # Remove images + result = text.clean_html( + '<p>Hi everybody! ' + '<img src="http://example.org/huge-purple-barney.png" /></p>\n' + '<p>:)</p>') + assert result == ( + '<div>' + '<p>Hi everybody! </p>\n' + '<p>:)</p>' + '</div>') + + # Remove evil javascript + result = text.clean_html( + '<p><a href="javascript:nasty_surprise">innocent link!</a></p>') + assert result == ( + '<p><a href="">innocent link!</a></p>') diff --git a/mediagoblin/tests/test_workbench.py b/mediagoblin/tests/test_workbench.py new file mode 100644 index 00000000..6695618b --- /dev/null +++ b/mediagoblin/tests/test_workbench.py @@ -0,0 +1,122 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import tempfile + + +from mediagoblin.tools import workbench +from mediagoblin.mg_globals import setup_globals +from mediagoblin.decorators import get_workbench +from mediagoblin.tests.test_storage import get_tmp_filestorage, cleanup_storage + + +class TestWorkbench(object): + def setup(self): + self.workbench_base = tempfile.mkdtemp(prefix='gmg_workbench_testing') + self.workbench_manager = workbench.WorkbenchManager( + self.workbench_base) + + def teardown(self): + # If the workbench is empty, this should work. + os.rmdir(self.workbench_base) + + def test_create_workbench(self): + workbench = self.workbench_manager.create() + assert os.path.isdir(workbench.dir) + assert workbench.dir.startswith(self.workbench_manager.base_workbench_dir) + workbench.destroy() + + def test_joinpath(self): + this_workbench = self.workbench_manager.create() + tmpname = this_workbench.joinpath('temp.txt') + assert tmpname == os.path.join(this_workbench.dir, 'temp.txt') + this_workbench.destroy() + + def test_destroy_workbench(self): + # kill a workbench + this_workbench = self.workbench_manager.create() + tmpfile_name = this_workbench.joinpath('temp.txt') + tmpfile = file(tmpfile_name, 'w') + with tmpfile: + tmpfile.write('lollerskates') + + assert os.path.exists(tmpfile_name) + + wb_dir = this_workbench.dir + this_workbench.destroy() + assert not os.path.exists(tmpfile_name) + assert not os.path.exists(wb_dir) + + def test_localized_file(self): + tmpdir, this_storage = get_tmp_filestorage() + this_workbench = self.workbench_manager.create() + + # Write a brand new file + filepath = ['dir1', 'dir2', 'ourfile.txt'] + + with this_storage.get_file(filepath, 'w') as our_file: + our_file.write('Our file') + + # with a local file storage + filename = this_workbench.localized_file(this_storage, filepath) + assert filename == os.path.join( + tmpdir, 'dir1/dir2/ourfile.txt') + this_storage.delete_file(filepath) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + + # with a fake remote file storage + tmpdir, this_storage = get_tmp_filestorage(fake_remote=True) + + # ... write a brand new file, again ;) + with this_storage.get_file(filepath, 'w') as our_file: + our_file.write('Our file') + + filename = this_workbench.localized_file(this_storage, filepath) + assert filename == os.path.join( + this_workbench.dir, 'ourfile.txt') + + # fake remote file storage, filename_if_copying set + filename = this_workbench.localized_file( + this_storage, filepath, 'thisfile') + assert filename == os.path.join( + this_workbench.dir, 'thisfile.txt') + + # fake remote file storage, filename_if_copying set, + # keep_extension_if_copying set to false + filename = this_workbench.localized_file( + this_storage, filepath, 'thisfile.text', False) + assert filename == os.path.join( + this_workbench.dir, 'thisfile.text') + + this_storage.delete_file(filepath) + cleanup_storage(this_storage, tmpdir, ['dir1', 'dir2']) + this_workbench.destroy() + + def test_workbench_decorator(self): + """Test @get_workbench decorator and automatic cleanup""" + # The decorator needs mg_globals.workbench_manager + setup_globals(workbench_manager=self.workbench_manager) + + @get_workbench + def create_it(workbench=None): + # workbench dir exists? + assert os.path.isdir(workbench.dir) + return workbench.dir + + benchdir = create_it() + # workbench dir has been cleaned up automatically? + assert not os.path.isdir(benchdir) diff --git a/mediagoblin/tests/testplugins/__init__.py b/mediagoblin/tests/testplugins/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/tests/testplugins/__init__.py @@ -0,0 +1,15 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/mediagoblin/tests/testplugins/callables1/__init__.py b/mediagoblin/tests/testplugins/callables1/__init__.py new file mode 100644 index 00000000..fe801a01 --- /dev/null +++ b/mediagoblin/tests/testplugins/callables1/__init__.py @@ -0,0 +1,43 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +def setup_plugin(): + pass + + +def just_one(call_log): + call_log.append("expect this one call") + return "Called just once" + + +def multi_handle(call_log): + call_log.append("Hi, I'm the first") + return "the first returns" + +def multi_handle_with_canthandle(call_log): + return None + + +def expand_tuple(this_tuple): + return this_tuple + (1,) + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + 'expand_tuple': expand_tuple, + } diff --git a/mediagoblin/tests/testplugins/callables2/__init__.py b/mediagoblin/tests/testplugins/callables2/__init__.py new file mode 100644 index 00000000..9d5cf950 --- /dev/null +++ b/mediagoblin/tests/testplugins/callables2/__init__.py @@ -0,0 +1,41 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +def setup_plugin(): + pass + + +def just_one(call_log): + assert "SHOULD NOT HAPPEN" + +def multi_handle(call_log): + call_log.append("Hi, I'm the second") + return "the second returns" + +def multi_handle_with_canthandle(call_log): + call_log.append("Hi, I'm the second") + return "the second returns" + +def expand_tuple(this_tuple): + return this_tuple + (2,) + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + 'expand_tuple': expand_tuple, + } diff --git a/mediagoblin/tests/testplugins/callables3/__init__.py b/mediagoblin/tests/testplugins/callables3/__init__.py new file mode 100644 index 00000000..04efc8fc --- /dev/null +++ b/mediagoblin/tests/testplugins/callables3/__init__.py @@ -0,0 +1,41 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +def setup_plugin(): + pass + + +def just_one(call_log): + assert "SHOULD NOT HAPPEN" + +def multi_handle(call_log): + call_log.append("Hi, I'm the third") + return "the third returns" + +def multi_handle_with_canthandle(call_log): + call_log.append("Hi, I'm the third") + return "the third returns" + +def expand_tuple(this_tuple): + return this_tuple + (3,) + +hooks = { + 'setup': setup_plugin, + 'just_one': just_one, + 'multi_handle': multi_handle, + 'multi_handle_with_canthandle': multi_handle_with_canthandle, + 'expand_tuple': expand_tuple, + } diff --git a/mediagoblin/tests/testplugins/modify_context/__init__.py b/mediagoblin/tests/testplugins/modify_context/__init__.py new file mode 100644 index 00000000..164e66c1 --- /dev/null +++ b/mediagoblin/tests/testplugins/modify_context/__init__.py @@ -0,0 +1,55 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools import pluginapi +import pkg_resources + + +def append_to_specific_context(context): + context['specific_page_append'] = 'in yer specificpage' + return context + +def append_to_global_context(context): + context['global_append'] = 'globally appended!' + return context + +def double_doubleme(context): + if 'doubleme' in context: + context['doubleme'] = context['doubleme'] * 2 + return context + + +def setup_plugin(): + routes = [ + ('modify_context.specific_page', + '/modify_context/specific/', + 'mediagoblin.tests.testplugins.modify_context.views:specific'), + ('modify_context.general_page', + '/modify_context/', + 'mediagoblin.tests.testplugins.modify_context.views:general')] + + pluginapi.register_routes(routes) + pluginapi.register_template_path( + pkg_resources.resource_filename( + 'mediagoblin.tests.testplugins.modify_context', 'templates')) + + +hooks = { + 'setup': setup_plugin, + ('modify_context.specific_page', + 'contextplugin/specific.html'): append_to_specific_context, + 'template_global_context': append_to_global_context, + 'template_context_prerender': double_doubleme} diff --git a/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html new file mode 100644 index 00000000..9cf96d3e --- /dev/null +++ b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/general.html @@ -0,0 +1,5 @@ +General page! + +global thing: {{ global_append }} +lol: {{ lol }} +doubleme: {{ doubleme }} diff --git a/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html new file mode 100644 index 00000000..5b1b4c4a --- /dev/null +++ b/mediagoblin/tests/testplugins/modify_context/templates/contextplugin/specific.html @@ -0,0 +1,6 @@ +Specific page! + +specific thing: {{ specific_page_append }} +global thing: {{ global_append }} +something: {{ something }} +doubleme: {{ doubleme }} diff --git a/mediagoblin/tests/testplugins/modify_context/views.py b/mediagoblin/tests/testplugins/modify_context/views.py new file mode 100644 index 00000000..701ec6f9 --- /dev/null +++ b/mediagoblin/tests/testplugins/modify_context/views.py @@ -0,0 +1,33 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.response import render_to_response + + +def specific(request): + return render_to_response( + request, + 'contextplugin/specific.html', + {"something": "orother", + "doubleme": "happy"}) + + +def general(request): + return render_to_response( + request, + 'contextplugin/general.html', + {"lol": "cats", + "doubleme": "joy"}) diff --git a/mediagoblin/tests/testplugins/pluginspec/__init__.py b/mediagoblin/tests/testplugins/pluginspec/__init__.py new file mode 100644 index 00000000..76ca2b1f --- /dev/null +++ b/mediagoblin/tests/testplugins/pluginspec/__init__.py @@ -0,0 +1,22 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +def setup_plugin(): + pass + +hooks = { + 'setup': setup_plugin, +} diff --git a/mediagoblin/tests/testplugins/pluginspec/config_spec.ini b/mediagoblin/tests/testplugins/pluginspec/config_spec.ini new file mode 100644 index 00000000..5c9c3bd7 --- /dev/null +++ b/mediagoblin/tests/testplugins/pluginspec/config_spec.ini @@ -0,0 +1,4 @@ +[plugin_spec] +some_string = string(default="blork") +some_int = integer(default=50) +dont_change_me = string(default="still the default")
\ No newline at end of file diff --git a/mediagoblin/tests/testplugins/staticstuff/__init__.py b/mediagoblin/tests/testplugins/staticstuff/__init__.py new file mode 100644 index 00000000..a2591646 --- /dev/null +++ b/mediagoblin/tests/testplugins/staticstuff/__init__.py @@ -0,0 +1,36 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.staticdirect import PluginStatic +from mediagoblin.tools import pluginapi +from pkg_resources import resource_filename + +def setup_plugin(): + routes = [ + ('staticstuff.static_demo', + '/staticstuff/', + 'mediagoblin.tests.testplugins.staticstuff.views:static_demo')] + + pluginapi.register_routes(routes) + + +hooks = { + 'setup': setup_plugin, + 'static_setup': lambda: PluginStatic( + 'staticstuff', + resource_filename( + 'mediagoblin.tests.testplugins.staticstuff', + 'static'))} diff --git a/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css b/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css new file mode 100644 index 00000000..1294ab8a --- /dev/null +++ b/mediagoblin/tests/testplugins/staticstuff/static/css/bunnify.css @@ -0,0 +1,4 @@ +body { + background-color: #5edcf1; + color: #eb8add; +}
\ No newline at end of file diff --git a/mediagoblin/tests/testplugins/staticstuff/views.py b/mediagoblin/tests/testplugins/staticstuff/views.py new file mode 100644 index 00000000..34a5e8cb --- /dev/null +++ b/mediagoblin/tests/testplugins/staticstuff/views.py @@ -0,0 +1,28 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json + +from werkzeug import Response + + +def static_demo(request): + return Response(json.dumps({ + # this does not exist, but we'll pretend it does ;) + 'mgoblin_bunny_pic': request.staticdirect( + 'images/bunny_pic.png'), + 'plugin_bunny_css': request.staticdirect( + 'css/bunnify.css', 'staticstuff')})) diff --git a/mediagoblin/tests/tools.py b/mediagoblin/tests/tools.py new file mode 100644 index 00000000..2ee39e89 --- /dev/null +++ b/mediagoblin/tests/tools.py @@ -0,0 +1,233 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import sys +import os +import pkg_resources +import shutil + +from functools import wraps + +from paste.deploy import loadapp +from webtest import TestApp + +from mediagoblin import mg_globals +from mediagoblin.db.models import User, MediaEntry, Collection +from mediagoblin.tools import testing +from mediagoblin.init.config import read_mediagoblin_config +from mediagoblin.db.base import Session +from mediagoblin.meddleware import BaseMeddleware +from mediagoblin.auth.lib import bcrypt_gen_password_hash +from mediagoblin.gmg_commands.dbupdate import run_dbupdate + + +MEDIAGOBLIN_TEST_DB_NAME = u'__mediagoblin_tests__' +TEST_SERVER_CONFIG = pkg_resources.resource_filename( + 'mediagoblin.tests', 'test_paste.ini') +TEST_APP_CONFIG = pkg_resources.resource_filename( + 'mediagoblin.tests', 'test_mgoblin_app.ini') + + +USER_DEV_DIRECTORIES_TO_SETUP = ['media/public', 'media/queue'] + + +class TestingMeddleware(BaseMeddleware): + """ + Meddleware for the Unit tests + + It might make sense to perform some tests on all + requests/responses. Or prepare them in a special + manner. For example all html responses could be tested + for being valid html *after* being rendered. + + This module is getting inserted at the front of the + meddleware list, which means: requests are handed here + first, responses last. So this wraps up the "normal" + app. + + If you need to add a test, either add it directly to + the appropiate process_request or process_response, or + create a new method and call it from process_*. + """ + + def process_response(self, request, response): + # All following tests should be for html only! + if getattr(response, 'content_type', None) != "text/html": + # Get out early + return + + # If the template contains a reference to + # /mgoblin_static/ instead of using + # /request.staticdirect(), error out here. + # This could probably be implemented as a grep on + # the shipped templates easier... + if response.text.find("/mgoblin_static/") >= 0: + raise AssertionError( + "Response HTML contains reference to /mgoblin_static/ " + "instead of staticdirect. Request was for: " + + request.full_path) + + return + + +def get_app(request, paste_config=None, mgoblin_config=None): + """Create a MediaGoblin app for testing. + + Args: + - request: Not an http request, but a pytest fixture request. We + use this to make temporary directories that pytest + automatically cleans up as needed. + - paste_config: particular paste config used by this application. + - mgoblin_config: particular mediagoblin config used by this + application. + """ + paste_config = paste_config or TEST_SERVER_CONFIG + mgoblin_config = mgoblin_config or TEST_APP_CONFIG + + # This is the directory we're copying the paste/mgoblin config stuff into + run_dir = request.config._tmpdirhandler.mktemp( + 'mgoblin_app', numbered=True) + user_dev_dir = run_dir.mkdir('user_dev').strpath + + new_paste_config = run_dir.join('paste.ini').strpath + new_mgoblin_config = run_dir.join('mediagoblin.ini').strpath + shutil.copyfile(paste_config, new_paste_config) + shutil.copyfile(mgoblin_config, new_mgoblin_config) + + Session.rollback() + Session.remove() + + # install user_dev directories + for directory in USER_DEV_DIRECTORIES_TO_SETUP: + full_dir = os.path.join(user_dev_dir, directory) + os.makedirs(full_dir) + + # Get app config + global_config, validation_result = read_mediagoblin_config(new_mgoblin_config) + app_config = global_config['mediagoblin'] + + # Run database setup/migrations + run_dbupdate(app_config, global_config) + + # setup app and return + test_app = loadapp( + 'config:' + new_paste_config) + + # Insert the TestingMeddleware, which can do some + # sanity checks on every request/response. + # Doing it this way is probably not the cleanest way. + # We'll fix it, when we have plugins! + mg_globals.app.meddleware.insert(0, TestingMeddleware(mg_globals.app)) + + app = TestApp(test_app) + + return app + + +def install_fixtures_simple(db, fixtures): + """ + Very simply install fixtures in the database + """ + for collection_name, collection_fixtures in fixtures.iteritems(): + collection = db[collection_name] + for fixture in collection_fixtures: + collection.insert(fixture) + + +def assert_db_meets_expected(db, expected): + """ + Assert a database contains the things we expect it to. + + Objects are found via 'id', so you should make sure your document + has an id. + + Args: + - db: pymongo or mongokit database connection + - expected: the data we expect. Formatted like: + {'collection_name': [ + {'id': 'foo', + 'some_field': 'some_value'},]} + """ + for collection_name, collection_data in expected.iteritems(): + collection = db[collection_name] + for expected_document in collection_data: + document = collection.find_one({'id': expected_document['id']}) + assert document is not None # make sure it exists + assert document == expected_document # make sure it matches + + +def fixture_add_user(username=u'chris', password=u'toast', + active_user=True): + # Reuse existing user or create a new one + test_user = User.query.filter_by(username=username).first() + if test_user is None: + test_user = User() + test_user.username = username + test_user.email = username + u'@example.com' + if password is not None: + test_user.pw_hash = bcrypt_gen_password_hash(password) + if active_user: + test_user.email_verified = True + test_user.status = u'active' + + test_user.save() + + # Reload + test_user = User.query.filter_by(username=username).first() + + # ... and detach from session: + Session.expunge(test_user) + + return test_user + + +def fixture_media_entry(title=u"Some title", slug=None, + uploader=None, save=True, gen_slug=True): + entry = MediaEntry() + entry.title = title + entry.slug = slug + entry.uploader = uploader or fixture_add_user().id + entry.media_type = u'image' + + if gen_slug: + entry.generate_slug() + if save: + entry.save() + + return entry + + +def fixture_add_collection(name=u"My first Collection", user=None): + if user is None: + user = fixture_add_user() + coll = Collection.query.filter_by(creator=user.id, title=name).first() + if coll is not None: + return coll + coll = Collection() + coll.creator = user.id + coll.title = name + coll.generate_slug() + coll.save() + + # Reload + Session.refresh(coll) + + # ... and detach from session: + Session.expunge(coll) + + return coll + diff --git a/mediagoblin/themes/airy/AGPLv3.txt b/mediagoblin/themes/airy/AGPLv3.txt new file mode 100644 index 00000000..dba13ed2 --- /dev/null +++ b/mediagoblin/themes/airy/AGPLv3.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<http://www.gnu.org/licenses/>. diff --git a/mediagoblin/themes/airy/CC0_1.0.txt b/mediagoblin/themes/airy/CC0_1.0.txt new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/mediagoblin/themes/airy/CC0_1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/mediagoblin/themes/airy/assets/css/airy.css b/mediagoblin/themes/airy/assets/css/airy.css new file mode 100644 index 00000000..c4bea5cb --- /dev/null +++ b/mediagoblin/themes/airy/assets/css/airy.css @@ -0,0 +1,82 @@ +body { + color: #4a4a4a; + background-color: #F7F7F7; +} + +h1,h2,h3 { + color: #4a4a4a; +} + +a { + color: #37AB74; +} + +.navigation_button { + background-color: #fff; + border: 1px solid; + border-color: #e4e4e4; + border-width: 1px 1px 2px; + color: #4a4a4a; + font-weight: bold; +} + +p.navigation_button { + font-weight: normal; + color: #A2A2A2; +} + +header { + background-color: #f7f7f7; + border-bottom: 1px solid #e4e4e4; + width: 940px; + margin-left: auto; + margin-right: auto; +} + +@media screen and (max-width: 940px) { + header { + width: 100%; + } +} + +footer { + border-top: 1px solid #E4E4E4; +} + +.button_action, .button_action_highlight, .button_form { + color: #4a4a4a; + background-color: #fff; + border: 1px solid; + border-color: #E4E4E4; + border-width: 1px 1px 2px; + padding: 5px 10px; +} + +.button_action_highlight, .button_form { + color: #fff; + background-color: #37AB74; + border-color: #6CAA8E; + border-width: 1px 1px 2px; +} + +input, textarea { + color: #4a4a4a; +} + +.form_box, .form_box_xl { + background-color: #fff; + border: 1px solid #e4e4e4; + border-radius: 6px; +} + +.media_thumbnail { + background-color: #fff; +} + +.media_thumbnail a { + color: #4a4a4a; +} + +.empty_space { + background-image: url("../images/empty_dots.png"); +} diff --git a/mediagoblin/themes/airy/assets/images/empty_dots.png b/mediagoblin/themes/airy/assets/images/empty_dots.png Binary files differnew file mode 100644 index 00000000..5ee050b2 --- /dev/null +++ b/mediagoblin/themes/airy/assets/images/empty_dots.png diff --git a/mediagoblin/themes/airy/assets/images/icon_feed.png b/mediagoblin/themes/airy/assets/images/icon_feed.png Binary files differnew file mode 100644 index 00000000..18c085b4 --- /dev/null +++ b/mediagoblin/themes/airy/assets/images/icon_feed.png diff --git a/mediagoblin/themes/airy/assets/images/logo.png b/mediagoblin/themes/airy/assets/images/logo.png Binary files differnew file mode 100644 index 00000000..a6eeaca0 --- /dev/null +++ b/mediagoblin/themes/airy/assets/images/logo.png diff --git a/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html b/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html new file mode 100644 index 00000000..c8500159 --- /dev/null +++ b/mediagoblin/themes/airy/templates/mediagoblin/bits/logo.html @@ -0,0 +1,25 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} + +{% block mediagoblin_logo %} + <a class="logo" + href="{{ request.urlgen('index') }}"> + <img src="{{ request.staticdirect('/images/logo.png', 'theme') }}" + alt="{% trans %}MediaGoblin logo{% endtrans %}" /> + </a> +{% endblock mediagoblin_logo -%} diff --git a/mediagoblin/themes/airy/templates/mediagoblin/extra_head.html b/mediagoblin/themes/airy/templates/mediagoblin/extra_head.html new file mode 100644 index 00000000..03e7db90 --- /dev/null +++ b/mediagoblin/themes/airy/templates/mediagoblin/extra_head.html @@ -0,0 +1,20 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +-#} + +<link rel="stylesheet" type="text/css" + href="{{ request.staticdirect('/css/airy.css', 'theme') }}"/> diff --git a/mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html b/mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html new file mode 100644 index 00000000..cf5099a2 --- /dev/null +++ b/mediagoblin/themes/airy/templates/mediagoblin/utils/feed_link.html @@ -0,0 +1,23 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +#} + +<a href="{{ feed_url }}"> + <img src="{{ request.staticdirect('/images/icon_feed.png', 'theme') }}" + class="media_icon" alt="{% trans %}feed icon{% endtrans %}" /> +</a> +<a href="{{ feed_url }}">{%- trans %}Atom feed{% endtrans -%}</a> diff --git a/mediagoblin/themes/airy/theme.cfg b/mediagoblin/themes/airy/theme.cfg new file mode 100644 index 00000000..b02986ba --- /dev/null +++ b/mediagoblin/themes/airy/theme.cfg @@ -0,0 +1,4 @@ +[theme] +name = Airy +description = A light theme based on the default MediaGoblin theme. I have a nagging suspicion that I'm subconciously copying something else, so if you come across a website that looks exactly the same, let me know! +licensing = AGPLv3 or later templates; assets (images/css) waived under CC0 1.0 diff --git a/mediagoblin/tools/__init__.py b/mediagoblin/tools/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mediagoblin/tools/__init__.py diff --git a/mediagoblin/tools/common.py b/mediagoblin/tools/common.py new file mode 100644 index 00000000..34586611 --- /dev/null +++ b/mediagoblin/tools/common.py @@ -0,0 +1,73 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import sys + + +global TESTS_ENABLED +TESTS_ENABLED = False + + +def import_component(import_string): + """ + Import a module component defined by STRING. Probably a method, + class, or global variable. + + Args: + - import_string: a string that defines what to import. Written + in the format of "module1.module2:component" + """ + module_name, func_name = import_string.split(':', 1) + __import__(module_name) + module = sys.modules[module_name] + func = getattr(module, func_name) + return func + + +def simple_printer(string): + """ + Prints a string, but without an auto \n at the end. + + Useful for places where we want to dependency inject for printing. + """ + sys.stdout.write(string) + sys.stdout.flush() + + +class CollectingPrinter(object): + """ + Another printer object, this one useful for capturing output for + examination during testing or otherwise. + + Use this like: + + >>> printer = CollectingPrinter() + >>> printer("herp derp\n") + >>> printer("lollerskates\n") + >>> printer.combined_string + "herp derp\nlollerskates\n" + """ + def __init__(self): + self.collection = [] + + def __call__(self, string): + self.collection.append(string) + + @property + def combined_string(self): + return u''.join(self.collection) + + diff --git a/mediagoblin/tools/crypto.py b/mediagoblin/tools/crypto.py new file mode 100644 index 00000000..1379d21b --- /dev/null +++ b/mediagoblin/tools/crypto.py @@ -0,0 +1,113 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import errno +import itsdangerous +import logging +import os.path +import random +import tempfile +from mediagoblin import mg_globals + +_log = logging.getLogger(__name__) + + +# Use the system (hardware-based) random number generator if it exists. +# -- this optimization is lifted from Django +try: + getrandbits = random.SystemRandom().getrandbits +except AttributeError: + getrandbits = random.getrandbits + + +__itsda_secret = None + + +def load_key(filename): + global __itsda_secret + key_file = open(filename) + try: + __itsda_secret = key_file.read() + finally: + key_file.close() + + +def create_key(key_dir, key_filepath): + global __itsda_secret + old_umask = os.umask(077) + key_file = None + try: + if not os.path.isdir(key_dir): + os.makedirs(key_dir) + _log.info("Created %s", key_dir) + key = str(getrandbits(192)) + key_file = tempfile.NamedTemporaryFile(dir=key_dir, suffix='.bin', + delete=False) + key_file.write(key) + key_file.flush() + os.rename(key_file.name, key_filepath) + key_file.close() + finally: + os.umask(old_umask) + if (key_file is not None) and (not key_file.closed): + key_file.close() + os.unlink(key_file.name) + __itsda_secret = key + _log.info("Saved new key for It's Dangerous") + + +def setup_crypto(): + global __itsda_secret + key_dir = mg_globals.app_config["crypto_path"] + key_filepath = os.path.join(key_dir, 'itsdangeroussecret.bin') + try: + load_key(key_filepath) + except IOError, error: + if error.errno != errno.ENOENT: + raise + create_key(key_dir, key_filepath) + + +def get_timed_signer_url(namespace): + """ + This gives a basic signing/verifying object. + + The namespace makes sure signed tokens can't be used in + a different area. Like using a forgot-password-token as + a session cookie. + + Basic usage: + + .. code-block:: python + + _signer = None + TOKEN_VALID_DAYS = 10 + def setup(): + global _signer + _signer = get_timed_signer_url("session cookie") + def create_token(obj): + return _signer.dumps(obj) + def parse_token(token): + # This might raise an exception in case + # of an invalid token, or an expired token. + return _signer.loads(token, max_age=TOKEN_VALID_DAYS*24*3600) + + For more details see + http://pythonhosted.org/itsdangerous/#itsdangerous.URLSafeTimedSerializer + """ + assert __itsda_secret is not None + return itsdangerous.URLSafeTimedSerializer(__itsda_secret, + salt=namespace) diff --git a/mediagoblin/tools/exif.py b/mediagoblin/tools/exif.py new file mode 100644 index 00000000..6b3639e8 --- /dev/null +++ b/mediagoblin/tools/exif.py @@ -0,0 +1,187 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +try: + from EXIF import process_file, Ratio +except ImportError: + from mediagoblin.tools.extlib.EXIF import process_file, Ratio + +from mediagoblin.processing import BadMediaFail +from mediagoblin.tools.translate import pass_to_ugettext as _ + +# A list of tags that should be stored for faster access +USEFUL_TAGS = [ + 'Image Make', + 'Image Model', + 'EXIF FNumber', + 'EXIF Flash', + 'EXIF FocalLength', + 'EXIF ExposureTime', + 'EXIF ApertureValue', + 'EXIF ExposureMode', + 'EXIF ISOSpeedRatings', + 'EXIF UserComment', + ] + + +def exif_image_needs_rotation(exif_tags): + """ + Returns True if EXIF orientation requires rotation + """ + return 'Image Orientation' in exif_tags \ + and exif_tags['Image Orientation'].values[0] != 1 + + +def exif_fix_image_orientation(im, exif_tags): + """ + Translate any EXIF orientation to raw orientation + + Cons: + - Well, it changes the image, which means we'll recompress + it... not a problem if scaling it down already anyway. We might + lose some quality in recompressing if it's at the same-size + though + + Pros: + - Prevents neck pain + """ + # Rotate image + if 'Image Orientation' in exif_tags: + rotation_map = { + 3: 180, + 6: 270, + 8: 90} + orientation = exif_tags['Image Orientation'].values[0] + if orientation in rotation_map: + im = im.rotate( + rotation_map[orientation]) + + return im + + +def extract_exif(filename): + """ + Returns EXIF tags found in file at ``filename`` + """ + try: + with file(filename) as image: + return process_file(image, details=False) + except IOError: + raise BadMediaFail(_('Could not read the image file.')) + + +def clean_exif(exif): + ''' + Clean the result from anything the database cannot handle + ''' + # Discard any JPEG thumbnail, for database compatibility + # and that I cannot see a case when we would use it. + # It takes up some space too. + disabled_tags = [ + 'Thumbnail JPEGInterchangeFormatLength', + 'JPEGThumbnail', + 'Thumbnail JPEGInterchangeFormat'] + + return dict((key, _ifd_tag_to_dict(value)) for (key, value) + in exif.iteritems() if key not in disabled_tags) + + +def _ifd_tag_to_dict(tag): + ''' + Takes an IFD tag object from the EXIF library and converts it to a dict + that can be stored as JSON in the database. + ''' + data = { + 'printable': tag.printable, + 'tag': tag.tag, + 'field_type': tag.field_type, + 'field_offset': tag.field_offset, + 'field_length': tag.field_length, + 'values': None} + + if isinstance(tag.printable, str): + # Force it to be decoded as UTF-8 so that it'll fit into the DB + data['printable'] = tag.printable.decode('utf8', 'replace') + + if type(tag.values) == list: + data['values'] = [_ratio_to_list(val) if isinstance(val, Ratio) else val + for val in tag.values] + else: + if isinstance(tag.values, str): + # Force UTF-8, so that it fits into the DB + data['values'] = tag.values.decode('utf8', 'replace') + else: + data['values'] = tag.values + + return data + + +def _ratio_to_list(ratio): + return [ratio.num, ratio.den] + + +def get_useful(tags): + return dict((key, tag) for (key, tag) in tags.iteritems()) + + +def get_gps_data(tags): + """ + Processes EXIF data returned by EXIF.py + """ + gps_data = {} + + if not 'Image GPSInfo' in tags: + return gps_data + + try: + dms_data = { + 'latitude': tags['GPS GPSLatitude'], + 'longitude': tags['GPS GPSLongitude']} + + for key, dat in dms_data.iteritems(): + gps_data[key] = ( + lambda v: + float(v[0].num) / float(v[0].den) \ + + (float(v[1].num) / float(v[1].den) / 60) \ + + (float(v[2].num) / float(v[2].den) / (60 * 60)) + )(dat.values) + + if tags['GPS GPSLatitudeRef'].values == 'S': + gps_data['latitude'] /= -1 + + if tags['GPS GPSLongitudeRef'].values == 'W': + gps_data['longitude'] /= -1 + + except KeyError: + pass + + try: + gps_data['direction'] = ( + lambda d: + float(d.num) / float(d.den) + )(tags['GPS GPSImgDirection'].values[0]) + except KeyError: + pass + + try: + gps_data['altitude'] = ( + lambda a: + float(a.num) / float(a.den) + )(tags['GPS GPSAltitude'].values[0]) + except KeyError: + pass + + return gps_data diff --git a/mediagoblin/tools/extlib/EXIF.py b/mediagoblin/tools/extlib/EXIF.py new file mode 120000 index 00000000..82a2fb30 --- /dev/null +++ b/mediagoblin/tools/extlib/EXIF.py @@ -0,0 +1 @@ +../../../extlib/exif/EXIF.py
\ No newline at end of file diff --git a/mediagoblin/tools/extlib/__init__.py b/mediagoblin/tools/extlib/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mediagoblin/tools/extlib/__init__.py diff --git a/mediagoblin/tools/extlib/wtf_html5.py b/mediagoblin/tools/extlib/wtf_html5.py new file mode 120000 index 00000000..5028c599 --- /dev/null +++ b/mediagoblin/tools/extlib/wtf_html5.py @@ -0,0 +1 @@ +../../../extlib/flask-wtf/html5.py
\ No newline at end of file diff --git a/mediagoblin/tools/files.py b/mediagoblin/tools/files.py new file mode 100644 index 00000000..848c86f2 --- /dev/null +++ b/mediagoblin/tools/files.py @@ -0,0 +1,43 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin import mg_globals + + +def delete_media_files(media): + """ + Delete all files associated with a MediaEntry + + Arguments: + - media: A MediaEntry document + """ + no_such_files = [] + for listpath in media.media_files.itervalues(): + try: + mg_globals.public_store.delete_file( + listpath) + except OSError: + no_such_files.append("/".join(listpath)) + + for attachment in media.attachment_files: + try: + mg_globals.public_store.delete_file( + attachment['filepath']) + except OSError: + no_such_files.append("/".join(attachment['filepath'])) + + if no_such_files: + raise OSError(", ".join(no_such_files)) diff --git a/mediagoblin/tools/licenses.py b/mediagoblin/tools/licenses.py new file mode 100644 index 00000000..a964980e --- /dev/null +++ b/mediagoblin/tools/licenses.py @@ -0,0 +1,69 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from collections import namedtuple +# Give a License attribute names: uri, name, abbreviation +License = namedtuple("License", ["abbreviation", "name", "uri"]) + +SORTED_LICENSES = [ + License("All rights reserved", "No license specified", ""), + License("CC BY 3.0", "Creative Commons Attribution Unported 3.0", + "http://creativecommons.org/licenses/by/3.0/"), + License("CC BY-SA 3.0", + "Creative Commons Attribution-ShareAlike Unported 3.0", + "http://creativecommons.org/licenses/by-sa/3.0/"), + License("CC BY-ND 3.0", + "Creative Commons Attribution-NoDerivs 3.0 Unported", + "http://creativecommons.org/licenses/by-nd/3.0/"), + License("CC BY-NC 3.0", + "Creative Commons Attribution-NonCommercial Unported 3.0", + "http://creativecommons.org/licenses/by-nc/3.0/"), + License("CC BY-NC-SA 3.0", + "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported", + "http://creativecommons.org/licenses/by-nc-sa/3.0/"), + License("CC BY-NC-ND 3.0", + "Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported", + "http://creativecommons.org/licenses/by-nc-nd/3.0/"), + License("CC0 1.0", + "Creative Commons CC0 1.0 Universal", + "http://creativecommons.org/publicdomain/zero/1.0/"), + License("Public Domain","Public Domain", + "http://creativecommons.org/publicdomain/mark/1.0/"), + ] + +# dict {uri: License,...} to enable fast license lookup by uri. Ideally, +# we'd want to use an OrderedDict (python 2.7+) here to avoid having the +# same data in two structures +SUPPORTED_LICENSES = dict(((l.uri, l) for l in SORTED_LICENSES)) + + +def get_license_by_url(url): + """Look up a license by its url and return the License object""" + try: + return SUPPORTED_LICENSES[url] + except KeyError: + # in case of an unknown License, just display the url given + # rather than exploding in the user's face. + return License(url, url, url) + + +def licenses_as_choices(): + """List of (uri, abbreviation) tuples for HTML choice field population + + The data seems to be consumed/deleted during usage, so hand over a + throwaway list, rather than just a generator. + """ + return [(lic.uri, lic.abbreviation) for lic in SORTED_LICENSES] diff --git a/mediagoblin/tools/mail.py b/mediagoblin/tools/mail.py new file mode 100644 index 00000000..6886c859 --- /dev/null +++ b/mediagoblin/tools/mail.py @@ -0,0 +1,150 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import smtplib +from email.MIMEText import MIMEText +from mediagoblin import mg_globals, messages +from mediagoblin.tools import common + +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### Special email test stuff begins HERE +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# We have two "test inboxes" here: +# +# EMAIL_TEST_INBOX: +# ---------------- +# If you're writing test views, you'll probably want to check this. +# It contains a list of MIMEText messages. +# +# EMAIL_TEST_MBOX_INBOX: +# ---------------------- +# This collects the messages from the FakeMhost inbox. It's reslly +# just here for testing the send_email method itself. +# +# Anyway this contains: +# - from +# - to: a list of email recipient addresses +# - message: not just the body, but the whole message, including +# headers, etc. +# +# ***IMPORTANT!*** +# ---------------- +# Before running tests that call functions which send email, you should +# always call _clear_test_inboxes() to "wipe" the inboxes clean. + +EMAIL_TEST_INBOX = [] +EMAIL_TEST_MBOX_INBOX = [] + + +class FakeMhost(object): + """ + Just a fake mail host so we can capture and test messages + from send_email + """ + def login(self, *args, **kwargs): + pass + + def sendmail(self, from_addr, to_addrs, message): + EMAIL_TEST_MBOX_INBOX.append( + {'from': from_addr, + 'to': to_addrs, + 'message': message}) + + +def _clear_test_inboxes(): + global EMAIL_TEST_INBOX + global EMAIL_TEST_MBOX_INBOX + EMAIL_TEST_INBOX = [] + EMAIL_TEST_MBOX_INBOX = [] + + +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +### </Special email test stuff> +### ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def send_email(from_addr, to_addrs, subject, message_body): + """ + Simple email sending wrapper, use this so we can capture messages + for unit testing purposes. + + Args: + - from_addr: address you're sending the email from + - to_addrs: list of recipient email addresses + - subject: subject of the email + - message_body: email body text + """ + if common.TESTS_ENABLED or mg_globals.app_config['email_debug_mode']: + mhost = FakeMhost() + elif not mg_globals.app_config['email_debug_mode']: + mhost = smtplib.SMTP( + mg_globals.app_config['email_smtp_host'], + mg_globals.app_config['email_smtp_port']) + + # SMTP.__init__ Issues SMTP.connect implicitly if host + if not mg_globals.app_config['email_smtp_host']: # e.g. host = '' + mhost.connect() # We SMTP.connect explicitly + + if ((not common.TESTS_ENABLED) + and (mg_globals.app_config['email_smtp_user'] + or mg_globals.app_config['email_smtp_pass'])): + mhost.login( + mg_globals.app_config['email_smtp_user'], + mg_globals.app_config['email_smtp_pass']) + + message = MIMEText(message_body.encode('utf-8'), 'plain', 'utf-8') + message['Subject'] = subject + message['From'] = from_addr + message['To'] = ', '.join(to_addrs) + + if common.TESTS_ENABLED: + EMAIL_TEST_INBOX.append(message) + + elif mg_globals.app_config['email_debug_mode']: + print u"===== Email =====" + print u"From address: %s" % message['From'] + print u"To addresses: %s" % message['To'] + print u"Subject: %s" % message['Subject'] + print u"-- Body: --" + print message.get_payload(decode=True) + + return mhost.sendmail(from_addr, to_addrs, message.as_string()) + + +def normalize_email(email): + """return case sensitive part, lower case domain name + + :returns: None in case of broken email addresses""" + try: + em_user, em_dom = email.split('@', 1) + except ValueError: + # email contained no '@' + return None + email = "@".join((em_user, em_dom.lower())) + return email + + +def email_debug_message(request): + """ + If the server is running in email debug mode (which is + the current default), give a debug message to the user + so that they have an idea where to find their email. + """ + if mg_globals.app_config['email_debug_mode']: + # DEBUG message, no need to translate + messages.add_message(request, messages.DEBUG, + u"This instance is running in email debug mode. " + u"The email will be on the console of the server process.") diff --git a/mediagoblin/tools/pagination.py b/mediagoblin/tools/pagination.py new file mode 100644 index 00000000..d0f08c94 --- /dev/null +++ b/mediagoblin/tools/pagination.py @@ -0,0 +1,113 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import urllib +import copy +from math import ceil, floor +from itertools import izip, count + + +PAGINATION_DEFAULT_PER_PAGE = 30 + + +class Pagination(object): + """ + Pagination class for database queries. + + Initialization through __init__(self, cursor, page=1, per_page=2), + get actual data slice through __call__(). + """ + + def __init__(self, page, cursor, per_page=PAGINATION_DEFAULT_PER_PAGE, + jump_to_id=False): + """ + Initializes Pagination + + Args: + - page: requested page + - per_page: number of objects per page + - cursor: db cursor + - jump_to_id: object id, sets the page to the page containing the + object with id == jump_to_id. + """ + self.page = page + self.per_page = per_page + self.cursor = cursor + self.total_count = self.cursor.count() + self.active_id = None + + if jump_to_id: + cursor = copy.copy(self.cursor) + + for (doc, increment) in izip(cursor, count(0)): + if doc.id == jump_to_id: + self.page = 1 + int(floor(increment / self.per_page)) + + self.active_id = jump_to_id + break + + def __call__(self): + """ + Returns slice of objects for the requested page + """ + # TODO, return None for out of index so templates can + # distinguish between empty galleries and out-of-bound pages??? + return self.cursor.slice( + (self.page - 1) * self.per_page, + self.page * self.per_page) + + @property + def pages(self): + return int(ceil(self.total_count / float(self.per_page))) + + @property + def has_prev(self): + return self.page > 1 + + @property + def has_next(self): + return self.page < self.pages + + def iter_pages(self, left_edge=2, left_current=2, + right_current=5, right_edge=2): + last = 0 + for num in xrange(1, self.pages + 1): + if num <= left_edge or \ + (num > self.page - left_current - 1 and \ + num < self.page + right_current) or \ + num > self.pages - right_edge: + if last + 1 != num: + yield None + yield num + last = num + + def get_page_url_explicit(self, base_url, get_params, page_no): + """ + Get a page url by adding a page= parameter to the base url + """ + new_get_params = dict(get_params) or {} + new_get_params['page'] = page_no + return "%s?%s" % ( + base_url, urllib.urlencode(new_get_params)) + + def get_page_url(self, request, page_no): + """ + Get a new page url based of the request, and the new page number. + + This is a nice wrapper around get_page_url_explicit() + """ + return self.get_page_url_explicit( + request.full_path, request.GET, page_no) diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py new file mode 100644 index 00000000..3f98aa8a --- /dev/null +++ b/mediagoblin/tools/pluginapi.py @@ -0,0 +1,367 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +This module implements the plugin api bits. + +Two things about things in this module: + +1. they should be excessively well documented because we should pull + from this file for the docs + +2. they should be well tested + + +How do plugins work? +==================== + +Plugins are structured like any Python project. You create a Python package. +In that package, you define a high-level ``__init__.py`` module that has a +``hooks`` dict that maps hooks to callables that implement those hooks. + +Additionally, you want a LICENSE file that specifies the license and a +``setup.py`` that specifies the metadata for packaging your plugin. A rough +file structure could look like this:: + + myplugin/ + |- setup.py # plugin project packaging metadata + |- README # holds plugin project information + |- LICENSE # holds license information + |- myplugin/ # plugin package directory + |- __init__.py # has hooks dict and code + + +Lifecycle +========= + +1. All the modules listed as subsections of the ``plugins`` section in + the config file are imported. MediaGoblin registers any hooks in + the ``hooks`` dict of those modules. + +2. After all plugin modules are imported, the ``setup`` hook is called + allowing plugins to do any set up they need to do. + +""" + +import logging + +from functools import wraps + +from mediagoblin import mg_globals + + +_log = logging.getLogger(__name__) + + +class PluginManager(object): + """Manager for plugin things + + .. Note:: + + This is a Borg class--there is one and only one of this class. + """ + __state = { + # list of plugin classes + "plugins": [], + + # map of hook names -> list of callables for that hook + "hooks": {}, + + # list of registered template paths + "template_paths": set(), + + # list of template hooks + "template_hooks": {}, + + # list of registered routes + "routes": [], + } + + def clear(self): + """This is only useful for testing.""" + # Why lists don't have a clear is not clear. + del self.plugins[:] + del self.routes[:] + self.hooks.clear() + self.template_paths.clear() + + def __init__(self): + self.__dict__ = self.__state + + def register_plugin(self, plugin): + """Registers a plugin class""" + self.plugins.append(plugin) + + def register_hooks(self, hook_mapping): + """Takes a hook_mapping and registers all the hooks""" + for hook, callables in hook_mapping.items(): + if isinstance(callables, (list, tuple)): + self.hooks.setdefault(hook, []).extend(list(callables)) + else: + # In this case, it's actually a single callable---not a + # list of callables. + self.hooks.setdefault(hook, []).append(callables) + + def get_hook_callables(self, hook_name): + return self.hooks.get(hook_name, []) + + def register_template_path(self, path): + """Registers a template path""" + self.template_paths.add(path) + + def get_template_paths(self): + """Returns a tuple of registered template paths""" + return tuple(self.template_paths) + + def register_route(self, route): + """Registers a single route""" + _log.debug('registering route: {0}'.format(route)) + self.routes.append(route) + + def get_routes(self): + return tuple(self.routes) + + def register_template_hooks(self, template_hooks): + for hook, templates in template_hooks.items(): + if isinstance(templates, (list, tuple)): + self.template_hooks.setdefault(hook, []).extend(list(templates)) + else: + # In this case, it's actually a single callable---not a + # list of callables. + self.template_hooks.setdefault(hook, []).append(templates) + + def get_template_hooks(self, hook_name): + return self.template_hooks.get(hook_name, []) + + +def register_routes(routes): + """Registers one or more routes + + If your plugin handles requests, then you need to call this with + the routes your plugin handles. + + A "route" is a `routes.Route` object. See `the routes.Route + documentation + <http://routes.readthedocs.org/en/latest/modules/route.html>`_ for + more details. + + Example passing in a single route: + + >>> register_routes(('about-view', '/about', + ... 'mediagoblin.views:about_view_handler')) + + Example passing in a list of routes: + + >>> register_routes([ + ... ('contact-view', '/contact', 'mediagoblin.views:contact_handler'), + ... ('about-view', '/about', 'mediagoblin.views:about_handler') + ... ]) + + + .. Note:: + + Be careful when designing your route urls. If they clash with + core urls, then it could result in DISASTER! + """ + if isinstance(routes, list): + for route in routes: + PluginManager().register_route(route) + else: + PluginManager().register_route(routes) + + +def register_template_path(path): + """Registers a path for template loading + + If your plugin has templates, then you need to call this with + the absolute path of the root of templates directory. + + Example: + + >>> my_plugin_dir = os.path.dirname(__file__) + >>> template_dir = os.path.join(my_plugin_dir, 'templates') + >>> register_template_path(template_dir) + + .. Note:: + + You can only do this in `setup_plugins()`. Doing this after + that will have no effect on template loading. + + """ + PluginManager().register_template_path(path) + + +def get_config(key): + """Retrieves the configuration for a specified plugin by key + + Example: + + >>> get_config('mediagoblin.plugins.sampleplugin') + {'foo': 'bar'} + >>> get_config('myplugin') + {} + >>> get_config('flatpages') + {'directory': '/srv/mediagoblin/pages', 'nesting': 1}} + + """ + + global_config = mg_globals.global_config + plugin_section = global_config.get('plugins', {}) + return plugin_section.get(key, {}) + + +def register_template_hooks(template_hooks): + """ + Register a dict of template hooks. + + Takes template_hooks as an argument, which is a dictionary of + template hook names/keys to the templates they should provide. + (The value can either be a single template path or an iterable + of paths.) + + Example: + + .. code-block:: python + + {"media_sidebar": "/plugin/sidemess/mess_up_the_side.html", + "media_descriptionbox": ["/plugin/sidemess/even_more_mess.html", + "/plugin/sidemess/so_much_mess.html"]} + """ + PluginManager().register_template_hooks(template_hooks) + + +def get_hook_templates(hook_name): + """ + Get a list of hook templates for this hook_name. + + Note: for the most part, you access this via a template tag, not + this method directly, like so: + + .. code-block:: html+jinja + + {% template_hook "media_sidebar" %} + + ... which will include all templates for you, partly using this + method. + + However, this method is exposed to templates, and if you wish, you + can iterate over templates in a template hook manually like so: + + .. code-block:: html+jinja + + {% for template_path in get_hook_templates("media_sidebar") %} + <div class="extra_structure"> + {% include template_path %} + </div> + {% endfor %} + + Returns: + A list of strings representing template paths. + """ + return PluginManager().get_template_hooks(hook_name) + + +############################# +## Hooks: The Next Generation +############################# + + +def hook_handle(hook_name, *args, **kwargs): + """ + Run through hooks attempting to find one that handle this hook. + + All callables called with the same arguments until one handles + things and returns a non-None value. + + (If you are writing a handler and you don't have a particularly + useful value to return even though you've handled this, returning + True is a good solution.) + + Note that there is a special keyword argument: + if "default_handler" is passed in as a keyword argument, this will + be used if no handler is found. + + Some examples of using this: + - You need an interface implemented, but only one fit for it + - You need to *do* something, but only one thing needs to do it. + """ + default_handler = kwargs.pop('default_handler', None) + + callables = PluginManager().get_hook_callables(hook_name) + + result = None + + for callable in callables: + result = callable(*args, **kwargs) + + if result is not None: + break + + if result is None and default_handler is not None: + result = default_handler(*args, **kwargs) + + return result + + +def hook_runall(hook_name, *args, **kwargs): + """ + Run through all callable hooks and pass in arguments. + + All non-None results are accrued in a list and returned from this. + (Other "false-like" values like False and friends are still + accrued, however.) + + Some examples of using this: + - You have an interface call where actually multiple things can + and should implement it + - You need to get a list of things from various plugins that + handle them and do something with them + - You need to *do* something, and actually multiple plugins need + to do it separately + """ + callables = PluginManager().get_hook_callables(hook_name) + + results = [] + + for callable in callables: + result = callable(*args, **kwargs) + + if result is not None: + results.append(result) + + return results + + +def hook_transform(hook_name, arg): + """ + Run through a bunch of hook callables and transform some input. + + Note that unlike the other hook tools, this one only takes ONE + argument. This argument is passed to each function, which in turn + returns something that becomes the input of the next callable. + + Some examples of using this: + - You have an object, say a form, but you want plugins to each be + able to modify it. + """ + result = arg + + callables = PluginManager().get_hook_callables(hook_name) + + for callable in callables: + result = callable(result) + + return result diff --git a/mediagoblin/tools/processing.py b/mediagoblin/tools/processing.py new file mode 100644 index 00000000..2abe6452 --- /dev/null +++ b/mediagoblin/tools/processing.py @@ -0,0 +1,87 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import json +import traceback + +from urllib2 import urlopen, Request, HTTPError +from urllib import urlencode + +_log = logging.getLogger(__name__) + +TESTS_CALLBACKS = {} + + +def create_post_request(url, data, **kw): + ''' + Issue a HTTP POST request. + + Args: + url: The URL to which the POST request should be issued + data: The data to be send in the body of the request + **kw: + data_parser: The parser function that is used to parse the `data` + argument + ''' + data_parser = kw.get('data_parser', urlencode) + headers = kw.get('headers', {}) + + return Request(url, data_parser(data), headers=headers) + + +def json_processing_callback(entry): + ''' + Send an HTTP post to the registered callback url, if any. + ''' + if not entry.processing_metadata: + _log.debug('No processing callback for {0}'.format(entry)) + return + + url = entry.processing_metadata[0].callback_url + + _log.debug('Sending processing callback for {0} ({1})'.format( + entry, + url)) + + headers = { + 'Content-Type': 'application/json'} + + data = { + 'id': entry.id, + 'state': entry.state} + + # Trigger testing mode, no callback will be sent + if url.endswith('secrettestmediagoblinparam'): + TESTS_CALLBACKS.update({url: data}) + return True + + request = create_post_request( + url, + data, + headers=headers, + data_parser=json.dumps) + + try: + urlopen(request) + _log.debug('Processing callback for {0} sent'.format(entry)) + + return True + except HTTPError: + _log.error('Failed to send callback: {0}'.format( + traceback.format_exc())) + + return False diff --git a/mediagoblin/tools/request.py b/mediagoblin/tools/request.py new file mode 100644 index 00000000..ee342eae --- /dev/null +++ b/mediagoblin/tools/request.py @@ -0,0 +1,38 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +from mediagoblin.db.models import User + +_log = logging.getLogger(__name__) + + +def setup_user_in_request(request): + """ + Examine a request and tack on a request.user parameter if that's + appropriate. + """ + if 'user_id' not in request.session: + request.user = None + return + + request.user = User.query.get(request.session['user_id']) + + if not request.user: + # Something's wrong... this user doesn't exist? Invalidate + # this session. + _log.warn("Killing session for user id %r", request.session['user_id']) + request.session.delete() diff --git a/mediagoblin/tools/response.py b/mediagoblin/tools/response.py new file mode 100644 index 00000000..aaf31d0b --- /dev/null +++ b/mediagoblin/tools/response.py @@ -0,0 +1,108 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import werkzeug.utils +from werkzeug.wrappers import Response as wz_Response +from mediagoblin.tools.template import render_template +from mediagoblin.tools.translate import (lazy_pass_to_ugettext as _, + pass_to_ugettext) + +class Response(wz_Response): + """Set default response mimetype to HTML, otherwise we get text/plain""" + default_mimetype = u'text/html' + + +def render_to_response(request, template, context, status=200): + """Much like Django's shortcut.render()""" + return Response( + render_template(request, template, context), + status=status) + + +def render_error(request, status=500, title=_('Oops!'), + err_msg=_('An error occured')): + """Render any error page with a given error code, title and text body + + Title and description are passed through as-is to allow html. Make + sure no user input is contained therein for security reasons. The + description will be wrapped in <p></p> tags. + """ + return Response(render_template(request, 'mediagoblin/error.html', + {'err_code': status, 'title': title, 'err_msg': err_msg}), + status=status) + + +def render_403(request): + """Render a standard 403 page""" + _ = pass_to_ugettext + title = _('Operation not allowed') + err_msg = _("Sorry Dave, I can't let you do that!</p><p>You have tried " + " to perform a function that you are not allowed to. Have you " + "been trying to delete all user accounts again?") + return render_error(request, 403, title, err_msg) + +def render_404(request): + """Render a standard 404 page.""" + _ = pass_to_ugettext + err_msg = _("There doesn't seem to be a page at this address. Sorry!</p>" + "<p>If you're sure the address is correct, maybe the page " + "you're looking for has been moved or deleted.") + return render_error(request, 404, err_msg=err_msg) + + +def render_http_exception(request, exc, description): + """Return Response() given a werkzeug.HTTPException + + :param exc: werkzeug.HTTPException or subclass thereof + :description: message describing the error.""" + # If we were passed the HTTPException stock description on + # exceptions where we have localized ones, use those: + stock_desc = (description == exc.__class__.description) + + if stock_desc and exc.code == 403: + return render_403(request) + elif stock_desc and exc.code == 404: + return render_404(request) + + return render_error(request, title=exc.args[0], + err_msg=description, + status=exc.code) + + +def redirect(request, *args, **kwargs): + """Redirects to an URL, using urlgen params or location string + + :param querystring: querystring to be appended to the URL + :param location: If the location keyword is given, redirect to the URL + """ + querystring = kwargs.pop('querystring', None) + + # Redirect to URL if given by "location=..." + if 'location' in kwargs: + location = kwargs.pop('location') + else: + location = request.urlgen(*args, **kwargs) + + if querystring: + location += querystring + return werkzeug.utils.redirect(location) + + +def redirect_obj(request, obj): + """Redirect to the page for the given object. + + Requires obj to have a .url_for_self method.""" + return redirect(request, location=obj.url_for_self(request.urlgen)) diff --git a/mediagoblin/tools/routing.py b/mediagoblin/tools/routing.py new file mode 100644 index 00000000..a15795fe --- /dev/null +++ b/mediagoblin/tools/routing.py @@ -0,0 +1,67 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging + +import six +from werkzeug.routing import Map, Rule +from mediagoblin.tools.common import import_component + + +_log = logging.getLogger(__name__) + +url_map = Map() + + +class MGRoute(Rule): + def __init__(self, endpoint, url, controller): + Rule.__init__(self, url, endpoint=endpoint) + self.gmg_controller = controller + + def empty(self): + new_rule = Rule.empty(self) + new_rule.gmg_controller = self.gmg_controller + return new_rule + + +def endpoint_to_controller(rule): + endpoint = rule.endpoint + view_func = rule.gmg_controller + + _log.debug('endpoint: {0} view_func: {1}'.format(endpoint, view_func)) + + # import the endpoint, or if it's already a callable, call that + if isinstance(view_func, six.string_types): + view_func = import_component(view_func) + rule.gmg_controller = view_func + + return view_func + + +def add_route(endpoint, url, controller): + """ + Add a route to the url mapping + """ + url_map.add(MGRoute(endpoint, url, controller)) + + +def mount(mountpoint, routes): + """ + Mount a bunch of routes to this mountpoint + """ + for endpoint, url, controller in routes: + url = "%s/%s" % (mountpoint.rstrip('/'), url.lstrip('/')) + add_route(endpoint, url, controller) diff --git a/mediagoblin/tools/session.py b/mediagoblin/tools/session.py new file mode 100644 index 00000000..fdc32523 --- /dev/null +++ b/mediagoblin/tools/session.py @@ -0,0 +1,68 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2013 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import itsdangerous +import logging + +import crypto + +_log = logging.getLogger(__name__) + +class Session(dict): + def __init__(self, *args, **kwargs): + self.send_new_cookie = False + dict.__init__(self, *args, **kwargs) + + def save(self): + self.send_new_cookie = True + + def is_updated(self): + return self.send_new_cookie + + def delete(self): + self.clear() + self.save() + + +class SessionManager(object): + def __init__(self, cookie_name='MGSession', namespace=None): + if namespace is None: + namespace = cookie_name + self.signer = crypto.get_timed_signer_url(namespace) + self.cookie_name = cookie_name + + def load_session_from_cookie(self, request): + cookie = request.cookies.get(self.cookie_name) + if not cookie: + return Session() + ### FIXME: Future cookie-blacklisting code + # m = BadCookie.query.filter_by(cookie = cookie) + # if m: + # _log.warn("Bad cookie received: %s", m.reason) + # raise BadRequest() + try: + return Session(self.signer.loads(cookie)) + except itsdangerous.BadData: + return Session() + + def save_session_to_cookie(self, session, request, response): + if not session.is_updated(): + return + elif not session: + response.delete_cookie(self.cookie_name) + else: + response.set_cookie(self.cookie_name, self.signer.dumps(session), + httponly=True) diff --git a/mediagoblin/tools/staticdirect.py b/mediagoblin/tools/staticdirect.py new file mode 100644 index 00000000..ef8b20d0 --- /dev/null +++ b/mediagoblin/tools/staticdirect.py @@ -0,0 +1,101 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +#################################### +# Staticdirect infrastructure. +# Borrowed largely from cc.engine +# by Chris Webber & Creative Commons +# +# This needs documentation! +#################################### + +import logging + +_log = logging.getLogger(__name__) + + +class StaticDirect(object): + """ + Direct to a static resource. + + This StaticDirect class can take a series of "domains" to + staticdirect to. In general, you should supply a None domain, as + that's the "default" domain. + + Things work like this: + >>> staticdirect = StaticDirect( + ... {None: "/static/", + ... "theme": "http://example.org/themestatic/"}) + >>> staticdirect("css/monkeys.css") + "/static/css/monkeys.css" + >>> staticdirect("images/lollerskate.png", "theme") + "http://example.org/themestatic/images/lollerskate.png" + """ + def __init__(self, domains): + self.domains = dict( + [(key, value.rstrip('/')) + for key, value in domains.iteritems()]) + self.cache = {} + + def __call__(self, filepath, domain=None): + if domain in self.cache and filepath in self.cache[domain]: + return self.cache[domain][filepath] + + static_direction = self.cache.setdefault( + domain, {})[filepath] = self.get(filepath, domain) + return static_direction + + def get(self, filepath, domain=None): + return '%s/%s' % ( + self.domains[domain], filepath.lstrip('/')) + + +class PluginStatic(object): + """Pass this into the ``'static_setup'`` hook to register your + plugin's static directory. + + This has two mandatory attributes that you must pass in on class + init: + - name: this name will be both used for lookup in "urlgen" for + your plugin's static resources and for the subdirectory that + it'll be "mounted" to for serving via your web browser. It + *MUST* be unique. If writing a plugin bundled with MediaGoblin + please use the pattern 'coreplugin__foo' where 'foo' is your + plugin name. All external plugins should use their modulename, + so if your plugin is 'mg_bettertags' you should also call this + name 'mg_bettertags'. + - file_path: the directory your plugin's static resources are + located in. It's recommended that you use + pkg_resources.resource_filename() for this. + + An example of using this:: + + from pkg_resources import resource_filename + from mediagoblin.tools.staticdirect import PluginStatic + + hooks = { + 'static_setup': lambda: PluginStatic( + 'mg_bettertags', + resource_filename('mg_bettertags', 'static')) + } + + """ + def __init__(self, name, file_path): + self.name = name + self.file_path = file_path + + def __call__(self): + return self diff --git a/mediagoblin/tools/template.py b/mediagoblin/tools/template.py new file mode 100644 index 00000000..3d651a6e --- /dev/null +++ b/mediagoblin/tools/template.py @@ -0,0 +1,160 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import jinja2 +from jinja2.ext import Extension +from jinja2.nodes import Include, Const + +from babel.localedata import exists +from werkzeug.urls import url_quote_plus + +from mediagoblin import mg_globals +from mediagoblin import messages +from mediagoblin import _version +from mediagoblin.tools import common +from mediagoblin.tools.translate import set_thread_locale +from mediagoblin.tools.pluginapi import get_hook_templates, hook_transform +from mediagoblin.tools.timesince import timesince +from mediagoblin.meddleware.csrf import render_csrf_form_token + + + +SETUP_JINJA_ENVS = {} + + +def get_jinja_env(template_loader, locale): + """ + Set up the Jinja environment, + + (In the future we may have another system for providing theming; + for now this is good enough.) + """ + set_thread_locale(locale) + + # If we have a jinja environment set up with this locale, just + # return that one. + if locale in SETUP_JINJA_ENVS: + return SETUP_JINJA_ENVS[locale] + + # jinja2.StrictUndefined will give exceptions on references + # to undefined/unknown variables in templates. + template_env = jinja2.Environment( + loader=template_loader, autoescape=True, + undefined=jinja2.StrictUndefined, + extensions=[ + 'jinja2.ext.i18n', 'jinja2.ext.autoescape', + TemplateHookExtension]) + + template_env.install_gettext_callables( + mg_globals.thread_scope.translations.ugettext, + mg_globals.thread_scope.translations.ungettext) + + # All templates will know how to ... + # ... fetch all waiting messages and remove them from the queue + # ... construct a grid of thumbnails or other media + # ... have access to the global and app config + template_env.globals['fetch_messages'] = messages.fetch_messages + template_env.globals['app_config'] = mg_globals.app_config + template_env.globals['global_config'] = mg_globals.global_config + template_env.globals['version'] = _version.__version__ + + template_env.filters['urlencode'] = url_quote_plus + + # add human readable fuzzy date time + template_env.globals['timesince'] = timesince + + # allow for hooking up plugin templates + template_env.globals['get_hook_templates'] = get_hook_templates + + template_env.globals = hook_transform( + 'template_global_context', template_env.globals) + + if exists(locale): + SETUP_JINJA_ENVS[locale] = template_env + + return template_env + + +# We'll store context information here when doing unit tests +TEMPLATE_TEST_CONTEXT = {} + + +def render_template(request, template_path, context): + """ + Render a template with context. + + Always inserts the request into the context, so you don't have to. + Also stores the context if we're doing unit tests. Helpful! + """ + template = request.template_env.get_template( + template_path) + context['request'] = request + rendered_csrf_token = render_csrf_form_token(request) + if rendered_csrf_token is not None: + context['csrf_token'] = render_csrf_form_token(request) + + # allow plugins to do things to the context + if request.controller_name: + context = hook_transform( + (request.controller_name, template_path), + context) + + # More evil: allow plugins to possibly do something to the context + # in every request ever with access to the request and other + # variables. Note: this is slower than using + # template_global_context + context = hook_transform( + 'template_context_prerender', context) + + rendered = template.render(context) + + if common.TESTS_ENABLED: + TEMPLATE_TEST_CONTEXT[template_path] = context + + return rendered + + +def clear_test_template_context(): + global TEMPLATE_TEST_CONTEXT + TEMPLATE_TEST_CONTEXT = {} + + +class TemplateHookExtension(Extension): + """ + Easily loop through a bunch of templates from a template hook. + + Use: + {% template_hook("comment_extras") %} + + ... will include all templates hooked into the comment_extras section. + """ + + tags = set(["template_hook"]) + + def parse(self, parser): + includes = [] + expr = parser.parse_expression() + lineno = expr.lineno + hook_name = expr.args[0].value + + for template_name in get_hook_templates(hook_name): + includes.append( + parser.parse_import_context( + Include(Const(template_name), True, False, lineno=lineno), + True)) + + return includes diff --git a/mediagoblin/tools/testing.py b/mediagoblin/tools/testing.py new file mode 100644 index 00000000..7f2bcbfb --- /dev/null +++ b/mediagoblin/tools/testing.py @@ -0,0 +1,45 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools import common +from mediagoblin.tools.template import clear_test_template_context +from mediagoblin.tools.mail import EMAIL_TEST_INBOX, EMAIL_TEST_MBOX_INBOX + +def _activate_testing(): + """ + Call this to activate testing in util.py + """ + + common.TESTS_ENABLED = True + +def clear_test_buckets(): + """ + We store some things for testing purposes that should be cleared + when we want a "clean slate" of information for our next round of + tests. Call this function to wipe all that stuff clean. + + Also wipes out some other things we might redefine during testing, + like the jinja envs. + """ + global SETUP_JINJA_ENVS + SETUP_JINJA_ENVS = {} + + global EMAIL_TEST_INBOX + global EMAIL_TEST_MBOX_INBOX + EMAIL_TEST_INBOX = [] + EMAIL_TEST_MBOX_INBOX = [] + + clear_test_template_context() diff --git a/mediagoblin/tools/text.py b/mediagoblin/tools/text.py new file mode 100644 index 00000000..96df49d2 --- /dev/null +++ b/mediagoblin/tools/text.py @@ -0,0 +1,124 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import wtforms +import markdown +from lxml.html.clean import Cleaner + +from mediagoblin import mg_globals +from mediagoblin.tools import url + + +# A super strict version of the lxml.html cleaner class +HTML_CLEANER = Cleaner( + scripts=True, + javascript=True, + comments=True, + style=True, + links=True, + page_structure=True, + processing_instructions=True, + embedded=True, + frames=True, + forms=True, + annoying_tags=True, + allow_tags=[ + 'div', 'b', 'i', 'em', 'strong', 'p', 'ul', 'ol', 'li', 'a', 'br', + 'pre', 'code'], + remove_unknown_tags=False, # can't be used with allow_tags + safe_attrs_only=True, + add_nofollow=True, # for now + host_whitelist=(), + whitelist_tags=set([])) + + +def clean_html(html): + # clean_html barfs on an empty string + if not html: + return u'' + + return HTML_CLEANER.clean_html(html) + + +def convert_to_tag_list_of_dicts(tag_string): + """ + Filter input from incoming string containing user tags, + + Strips trailing, leading, and internal whitespace, and also converts + the "tags" text into an array of tags + """ + taglist = [] + if tag_string: + + # Strip out internal, trailing, and leading whitespace + stripped_tag_string = u' '.join(tag_string.strip().split()) + + # Split the tag string into a list of tags + for tag in stripped_tag_string.split(','): + tag = tag.strip() + # Ignore empty or duplicate tags + if tag and tag not in [t['name'] for t in taglist]: + taglist.append({'name': tag, + 'slug': url.slugify(tag)}) + return taglist + + +def media_tags_as_string(media_entry_tags): + """ + Generate a string from a media item's tags, stored as a list of dicts + + This is the opposite of convert_to_tag_list_of_dicts + """ + tags_string = '' + if media_entry_tags: + tags_string = u', '.join([tag['name'] for tag in media_entry_tags]) + return tags_string + + +TOO_LONG_TAG_WARNING = \ + u'Tags must be shorter than %s characters. Tags that are too long: %s' + + +def tag_length_validator(form, field): + """ + Make sure tags do not exceed the maximum tag length. + """ + tags = convert_to_tag_list_of_dicts(field.data) + too_long_tags = [ + tag['name'] for tag in tags + if len(tag['name']) > mg_globals.app_config['tags_max_length']] + + if too_long_tags: + raise wtforms.ValidationError( + TOO_LONG_TAG_WARNING % (mg_globals.app_config['tags_max_length'], + ', '.join(too_long_tags))) + + +# Don't use the safe mode, because lxml.html.clean is better and we are using +# it anyway +UNSAFE_MARKDOWN_INSTANCE = markdown.Markdown() + + +def cleaned_markdown_conversion(text): + """ + Take a block of text, run it through MarkDown, and clean its HTML. + """ + # Markdown will do nothing with and clean_html can do nothing with + # an empty string :) + if not text: + return u'' + + return clean_html(UNSAFE_MARKDOWN_INSTANCE.convert(text)) diff --git a/mediagoblin/tools/theme.py b/mediagoblin/tools/theme.py new file mode 100644 index 00000000..97b041a6 --- /dev/null +++ b/mediagoblin/tools/theme.py @@ -0,0 +1,89 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +""" +""" + + +import pkg_resources +import os + +from configobj import ConfigObj + + +BUILTIN_THEME_DIR = pkg_resources.resource_filename('mediagoblin', 'themes') + + +def themedata_for_theme_dir(name, theme_dir): + """ + Given a theme directory, extract important theme information. + """ + # open config + config = ConfigObj(os.path.join(theme_dir, 'theme.ini')).get('theme', {}) + + templates_dir = os.path.join(theme_dir, 'templates') + if not os.path.exists(templates_dir): + templates_dir = None + + assets_dir = os.path.join(theme_dir, 'assets') + if not os.path.exists(assets_dir): + assets_dir = None + + themedata = { + 'name': config.get('name', name), + 'description': config.get('description'), + 'licensing': config.get('licensing'), + 'dir': theme_dir, + 'templates_dir': templates_dir, + 'assets_dir': assets_dir, + 'config': config} + + return themedata + + +def register_themes(app_config, builtin_dir=BUILTIN_THEME_DIR): + """ + Register all themes relevant to this application. + """ + registry = {} + + def _install_themes_in_dir(directory): + for themedir in os.listdir(directory): + abs_themedir = os.path.join(directory, themedir) + if not os.path.isdir(abs_themedir): + continue + + themedata = themedata_for_theme_dir(themedir, abs_themedir) + registry[themedir] = themedata + + # Built-in themes + if os.path.exists(builtin_dir): + _install_themes_in_dir(builtin_dir) + + # Installed themes + theme_install_dir = app_config.get('theme_install_dir') + if theme_install_dir and os.path.exists(theme_install_dir): + _install_themes_in_dir(theme_install_dir) + + current_theme_name = app_config.get('theme') + if current_theme_name \ + and registry.has_key(current_theme_name): + current_theme = registry[current_theme_name] + else: + current_theme = None + + return registry, current_theme + diff --git a/mediagoblin/tools/timesince.py b/mediagoblin/tools/timesince.py new file mode 100644 index 00000000..b761c1be --- /dev/null +++ b/mediagoblin/tools/timesince.py @@ -0,0 +1,95 @@ +# Copyright (c) Django Software Foundation and individual contributors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of Django nor the names of its contributors may be used +# to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import unicode_literals + +import datetime +import pytz + +from mediagoblin.tools.translate import pass_to_ugettext, lazy_pass_to_ungettext as _ + +"""UTC time zone as a tzinfo instance.""" +utc = pytz.utc if pytz else UTC() + +def is_aware(value): + """ + Determines if a given datetime.datetime is aware. + + The logic is described in Python's docs: + http://docs.python.org/library/datetime.html#datetime.tzinfo + """ + return value.tzinfo is not None and value.tzinfo.utcoffset(value) is not None + +def timesince(d, now=None, reversed=False): + """ + Takes two datetime objects and returns the time between d and now + as a nicely formatted string, e.g. "10 minutes". If d occurs after now, + then "0 minutes" is returned. + + Units used are years, months, weeks, days, hours, and minutes. + Seconds and microseconds are ignored. Up to two adjacent units will be + displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are + possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not. + + Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since + """ + chunks = ( + (60 * 60 * 24 * 365, lambda n: _('year', 'years', n)), + (60 * 60 * 24 * 30, lambda n: _('month', 'months', n)), + (60 * 60 * 24 * 7, lambda n : _('week', 'weeks', n)), + (60 * 60 * 24, lambda n : _('day', 'days', n)), + (60 * 60, lambda n: _('hour', 'hours', n)), + (60, lambda n: _('minute', 'minutes', n)) + ) + # Convert datetime.date to datetime.datetime for comparison. + if not isinstance(d, datetime.datetime): + d = datetime.datetime(d.year, d.month, d.day) + if now and not isinstance(now, datetime.datetime): + now = datetime.datetime(now.year, now.month, now.day) + + if not now: + now = datetime.datetime.now(utc if is_aware(d) else None) + + delta = (d - now) if reversed else (now - d) + # ignore microseconds + since = delta.days * 24 * 60 * 60 + delta.seconds + if since <= 0: + # d is in the future compared to now, stop processing. + return '0 ' + pass_to_ugettext('minutes') + for i, (seconds, name) in enumerate(chunks): + count = since // seconds + if count != 0: + break + s = pass_to_ugettext('%(number)d %(type)s') % {'number': count, 'type': name(count)} + if i + 1 < len(chunks): + # Now get the second item + seconds2, name2 = chunks[i + 1] + count2 = (since - (seconds * count)) // seconds2 + if count2 != 0: + s += pass_to_ugettext(', %(number)d %(type)s') % {'number': count2, 'type': name2(count2)} + return s diff --git a/mediagoblin/tools/translate.py b/mediagoblin/tools/translate.py new file mode 100644 index 00000000..b20e57d1 --- /dev/null +++ b/mediagoblin/tools/translate.py @@ -0,0 +1,211 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import gettext +import pkg_resources + + +from babel import localedata +from babel.support import LazyProxy + +from mediagoblin import mg_globals + +################### +# Translation tools +################### + +AVAILABLE_LOCALES = None +TRANSLATIONS_PATH = pkg_resources.resource_filename( + 'mediagoblin', 'i18n') + + +def set_available_locales(): + """Set available locales for which we have translations""" + global AVAILABLE_LOCALES + locales=['en', 'en_US'] # these are available without translations + for locale in localedata.list(): + if gettext.find('mediagoblin', TRANSLATIONS_PATH, [locale]): + locales.append(locale) + AVAILABLE_LOCALES = locales + + +class ReallyLazyProxy(LazyProxy): + """ + Like LazyProxy, except that it doesn't cache the value ;) + """ + @property + def value(self): + return self._func(*self._args, **self._kwargs) + + def __repr__(self): + return "<%s for %s(%r, %r)>" % ( + self.__class__.__name__, + self._func, + self._args, + self._kwargs) + + +def locale_to_lower_upper(locale): + """ + Take a locale, regardless of style, and format it like "en_US" + """ + if '-' in locale: + lang, country = locale.split('-', 1) + return '%s_%s' % (lang.lower(), country.upper()) + elif '_' in locale: + lang, country = locale.split('_', 1) + return '%s_%s' % (lang.lower(), country.upper()) + else: + return locale.lower() + + +def locale_to_lower_lower(locale): + """ + Take a locale, regardless of style, and format it like "en_us" + """ + if '_' in locale: + lang, country = locale.split('_', 1) + return '%s-%s' % (lang.lower(), country.lower()) + else: + return locale.lower() + + +def get_locale_from_request(request): + """ + Return most appropriate language based on prefs/request request + """ + request_args = (request.args, request.form)[request.method=='POST'] + + if 'lang' in request_args: + # User explicitely demanded a language, normalize lower_uppercase + target_lang = locale_to_lower_upper(request_args['lang']) + + elif 'target_lang' in request.session: + # TODO: Uh, ohh, this is never ever set anywhere? + target_lang = request.session['target_lang'] + else: + # Pull the most acceptable language based on browser preferences + # This returns one of AVAILABLE_LOCALES which is aready case-normalized. + # Note: in our tests request.accept_languages is None, so we need + # to explicitely fallback to en here. + target_lang = request.accept_languages.best_match(AVAILABLE_LOCALES) \ + or "en_US" + + return target_lang + +SETUP_GETTEXTS = {} + +def get_gettext_translation(locale): + """ + Return the gettext instance based on this locale + """ + # Later on when we have plugins we may want to enable the + # multi-translations system they have so we can handle plugin + # translations too + + # TODO: fallback nicely on translations from pt_PT to pt if not + # available, etc. + if locale in SETUP_GETTEXTS: + this_gettext = SETUP_GETTEXTS[locale] + else: + this_gettext = gettext.translation( + 'mediagoblin', TRANSLATIONS_PATH, [locale], fallback=True) + if localedata.exists(locale): + SETUP_GETTEXTS[locale] = this_gettext + return this_gettext + + +def set_thread_locale(locale): + """Set the current translation for this thread""" + mg_globals.thread_scope.translations = get_gettext_translation(locale) + + +def pass_to_ugettext(*args, **kwargs): + """ + Pass a translation on to the appropriate ugettext method. + + The reason we can't have a global ugettext method is because + mg_globals gets swapped out by the application per-request. + """ + return mg_globals.thread_scope.translations.ugettext( + *args, **kwargs) + +def pass_to_ungettext(*args, **kwargs): + """ + Pass a translation on to the appropriate ungettext method. + + The reason we can't have a global ugettext method is because + mg_globals gets swapped out by the application per-request. + """ + return mg_globals.thread_scope.translations.ungettext( + *args, **kwargs) + + +def lazy_pass_to_ugettext(*args, **kwargs): + """ + Lazily pass to ugettext. + + This is useful if you have to define a translation on a module + level but you need it to not translate until the time that it's + used as a string. For example, in: + def func(self, message=_('Hello boys and girls')) + + you would want to use the lazy version for _. + """ + return ReallyLazyProxy(pass_to_ugettext, *args, **kwargs) + + +def pass_to_ngettext(*args, **kwargs): + """ + Pass a translation on to the appropriate ngettext method. + + The reason we can't have a global ngettext method is because + mg_globals gets swapped out by the application per-request. + """ + return mg_globals.thread_scope.translations.ngettext( + *args, **kwargs) + + +def lazy_pass_to_ngettext(*args, **kwargs): + """ + Lazily pass to ngettext. + + This is useful if you have to define a translation on a module + level but you need it to not translate until the time that it's + used as a string. + """ + return ReallyLazyProxy(pass_to_ngettext, *args, **kwargs) + +def lazy_pass_to_ungettext(*args, **kwargs): + """ + Lazily pass to ungettext. + + This is useful if you have to define a translation on a module + level but you need it to not translate until the time that it's + used as a string. + """ + return ReallyLazyProxy(pass_to_ungettext, *args, **kwargs) + + +def fake_ugettext_passthrough(string): + """ + Fake a ugettext call for extraction's sake ;) + + In wtforms there's a separate way to define a method to translate + things... so we just need to mark up the text so that it can be + extracted, not so that it's actually run through gettext. + """ + return string diff --git a/mediagoblin/tools/url.py b/mediagoblin/tools/url.py new file mode 100644 index 00000000..d9179f9e --- /dev/null +++ b/mediagoblin/tools/url.py @@ -0,0 +1,44 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import re +# This import *is* used; see word.encode('tranlit/long') below. +from unicodedata import normalize + +try: + import translitcodec + USING_TRANSLITCODEC = True +except ImportError: + USING_TRANSLITCODEC = False + + +_punct_re = re.compile(r'[\t !"#:$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+') + + +def slugify(text, delim=u'-'): + """ + Generates an ASCII-only slug. Taken from http://flask.pocoo.org/snippets/5/ + """ + result = [] + for word in _punct_re.split(text.lower()): + if USING_TRANSLITCODEC: + word = word.encode('translit/long') + else: + word = normalize('NFKD', word).encode('ascii', 'ignore') + + if word: + result.append(word) + return unicode(delim.join(result)) diff --git a/mediagoblin/tools/workbench.py b/mediagoblin/tools/workbench.py new file mode 100644 index 00000000..0bd4096b --- /dev/null +++ b/mediagoblin/tools/workbench.py @@ -0,0 +1,164 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import shutil +import tempfile + + +# Actual workbench stuff +# ---------------------- + +class Workbench(object): + """ + Represent the directory for the workbench + + WARNING: DO NOT create Workbench objects on your own, + let the WorkbenchManager do that for you! + """ + def __init__(self, dir): + """ + WARNING: DO NOT create Workbench objects on your own, + let the WorkbenchManager do that for you! + """ + self.dir = dir + + def __unicode__(self): + return unicode(self.dir) + + def __str__(self): + return str(self.dir) + + def __repr__(self): + try: + return str(self) + except AttributeError: + return 'None' + + def joinpath(self, *args): + return os.path.join(self.dir, *args) + + def localized_file(self, storage, filepath, + filename_if_copying=None, + keep_extension_if_copying=True): + """ + Possibly localize the file from this storage system (for read-only + purposes, modifications should be written to a new file.). + + If the file is already local, just return the absolute filename of that + local file. Otherwise, copy the file locally to the workbench, and + return the absolute path of the new file. + + If it is copying locally, we might want to require a filename like + "source.jpg" to ensure that we won't conflict with other filenames in + our workbench... if that's the case, make sure filename_if_copying is + set to something like 'source.jpg'. Relatedly, if you set + keep_extension_if_copying, you don't have to set an extension on + filename_if_copying yourself, it'll be set for you (assuming such an + extension can be extacted from the filename in the filepath). + + Returns: + localized_filename + + Examples: + >>> wb_manager.localized_file( + ... '/our/workbench/subdir', local_storage, + ... ['path', 'to', 'foobar.jpg']) + u'/local/storage/path/to/foobar.jpg' + + >>> wb_manager.localized_file( + ... '/our/workbench/subdir', remote_storage, + ... ['path', 'to', 'foobar.jpg']) + '/our/workbench/subdir/foobar.jpg' + + >>> wb_manager.localized_file( + ... '/our/workbench/subdir', remote_storage, + ... ['path', 'to', 'foobar.jpg'], 'source.jpeg', False) + '/our/workbench/subdir/foobar.jpeg' + + >>> wb_manager.localized_file( + ... '/our/workbench/subdir', remote_storage, + ... ['path', 'to', 'foobar.jpg'], 'source', True) + '/our/workbench/subdir/foobar.jpg' + """ + if storage.local_storage: + return storage.get_local_path(filepath) + else: + if filename_if_copying is None: + dest_filename = filepath[-1] + else: + orig_filename, orig_ext = os.path.splitext(filepath[-1]) + if keep_extension_if_copying and orig_ext: + dest_filename = filename_if_copying + orig_ext + else: + dest_filename = filename_if_copying + + full_dest_filename = os.path.join( + self.dir, dest_filename) + + # copy it over + storage.copy_locally( + filepath, full_dest_filename) + + return full_dest_filename + + def destroy(self): + """ + Destroy this workbench! Deletes the directory and all its contents! + + WARNING: Does no checks for a sane value in self.dir! + """ + # just in case + workbench = os.path.abspath(self.dir) + shutil.rmtree(workbench) + del self.dir + + def __enter__(self): + """Make Workbench a context manager so we can use `with Workbench() as bench:`""" + return self + + def __exit__(self, *args): + """Clean up context manager, aka ourselves, deleting the workbench""" + self.destroy() + + +class WorkbenchManager(object): + """ + A system for generating and destroying workbenches. + + Workbenches are actually just subdirectories of a (local) temporary + storage space for during the processing stage. The preferred way to + create them is to use: + + with workbenchmger.create() as workbench: + do stuff... + + This will automatically clean up all temporary directories even in + case of an exceptions. Also check the + @mediagoblin.decorators.get_workbench decorator for a convenient + wrapper. + """ + + def __init__(self, base_workbench_dir): + self.base_workbench_dir = os.path.abspath(base_workbench_dir) + if not os.path.exists(self.base_workbench_dir): + os.makedirs(self.base_workbench_dir) + + def create(self): + """ + Create and return the path to a new workbench (directory). + """ + return Workbench(tempfile.mkdtemp(dir=self.base_workbench_dir)) diff --git a/mediagoblin/user_pages/__init__.py b/mediagoblin/user_pages/__init__.py new file mode 100644 index 00000000..621845ba --- /dev/null +++ b/mediagoblin/user_pages/__init__.py @@ -0,0 +1,15 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/mediagoblin/user_pages/forms.py b/mediagoblin/user_pages/forms.py new file mode 100644 index 00000000..9a193680 --- /dev/null +++ b/mediagoblin/user_pages/forms.py @@ -0,0 +1,51 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import wtforms +from wtforms.ext.sqlalchemy.fields import QuerySelectField +from mediagoblin.tools.translate import lazy_pass_to_ugettext as _ + +class MediaCommentForm(wtforms.Form): + comment_content = wtforms.TextAreaField( + _('Comment'), + [wtforms.validators.Required()], + description=_(u'You can use ' + u'<a href="http://daringfireball.net/projects/markdown/basics">' + u'Markdown</a> for formatting.')) + +class ConfirmDeleteForm(wtforms.Form): + confirm = wtforms.BooleanField( + _('I am sure I want to delete this')) + +class ConfirmCollectionItemRemoveForm(wtforms.Form): + confirm = wtforms.BooleanField( + _('I am sure I want to remove this item from the collection')) + +class MediaCollectForm(wtforms.Form): + collection = QuerySelectField( + _('Collection'), + allow_blank=True, blank_text=_('-- Select --'), get_label='title',) + note = wtforms.TextAreaField( + _('Include a note'), + [wtforms.validators.Optional()],) + collection_title = wtforms.TextField( + _('Title'), + [wtforms.validators.Length(min=0, max=500)]) + collection_description = wtforms.TextAreaField( + _('Description of this collection'), + description=_("""You can use + <a href="http://daringfireball.net/projects/markdown/basics"> + Markdown</a> for formatting.""")) diff --git a/mediagoblin/user_pages/lib.py b/mediagoblin/user_pages/lib.py new file mode 100644 index 00000000..2f47e4b1 --- /dev/null +++ b/mediagoblin/user_pages/lib.py @@ -0,0 +1,77 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.mail import send_email +from mediagoblin.tools.template import render_template +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin import mg_globals +from mediagoblin.db.base import Session +from mediagoblin.db.models import CollectionItem + + +def send_comment_email(user, comment, media, request): + """ + Sends comment email to user when a comment is made on their media. + + Args: + - user: the user object to whom the email is sent + - comment: the comment object referencing user's media + - media: the media object the comment is about + - request: the request + """ + + comment_url = request.urlgen( + 'mediagoblin.user_pages.media_home.view_comment', + comment=comment.id, + user=media.get_uploader.username, + media=media.slug_or_id, + qualified=True) + '#comment' + + comment_author = comment.get_author.username + + rendered_email = render_template( + request, 'mediagoblin/user_pages/comment_email.txt', + {'username': user.username, + 'comment_author': comment_author, + 'comment_content': comment.content, + 'comment_url': comment_url}) + + send_email( + mg_globals.app_config['email_sender_address'], + [user.email], + '{instance_title} - {comment_author} '.format( + comment_author=comment_author, + instance_title=mg_globals.app_config['html_title']) \ + + _('commented on your post'), + rendered_email) + + +def add_media_to_collection(collection, media, note=None, commit=True): + collection_item = CollectionItem() + collection_item.collection = collection.id + collection_item.media_entry = media.id + if note: + collection_item.note = note + Session.add(collection_item) + + collection.items = collection.items + 1 + Session.add(collection) + + media.collected = media.collected + 1 + Session.add(media) + + if commit: + Session.commit() diff --git a/mediagoblin/user_pages/routing.py b/mediagoblin/user_pages/routing.py new file mode 100644 index 00000000..9cb665b5 --- /dev/null +++ b/mediagoblin/user_pages/routing.py @@ -0,0 +1,91 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.routing import add_route + +add_route('mediagoblin.user_pages.user_home', + '/u/<string:user>/', 'mediagoblin.user_pages.views:user_home') + +add_route('mediagoblin.user_pages.media_home', + '/u/<string:user>/m/<string:media>/', + 'mediagoblin.user_pages.views:media_home') + +add_route('mediagoblin.user_pages.media_confirm_delete', + '/u/<string:user>/m/<int:media_id>/confirm-delete/', + 'mediagoblin.user_pages.views:media_confirm_delete') + +# Submission handling of new comments. TODO: only allow for POST methods +add_route('mediagoblin.user_pages.media_post_comment', + '/u/<string:user>/m/<int:media_id>/comment/add/', + 'mediagoblin.user_pages.views:media_post_comment') + +add_route('mediagoblin.user_pages.user_gallery', + '/u/<string:user>/gallery/', + 'mediagoblin.user_pages.views:user_gallery') + +add_route('mediagoblin.user_pages.media_home.view_comment', + '/u/<string:user>/m/<string:media>/c/<int:comment>/', + 'mediagoblin.user_pages.views:media_home') + +# User's tags gallery +add_route('mediagoblin.user_pages.user_tag_gallery', + '/u/<string:user>/tag/<string:tag>/', + 'mediagoblin.user_pages.views:user_gallery') + +add_route('mediagoblin.user_pages.atom_feed', + '/u/<string:user>/atom/', + 'mediagoblin.user_pages.views:atom_feed') + +add_route('mediagoblin.user_pages.media_collect', + '/u/<string:user>/m/<int:media_id>/collect/', + 'mediagoblin.user_pages.views:media_collect') + +add_route('mediagoblin.user_pages.collection_list', + '/u/<string:user>/collections/', + 'mediagoblin.user_pages.views:collection_list') + +add_route('mediagoblin.user_pages.user_collection', + '/u/<string:user>/collection/<string:collection>/', + 'mediagoblin.user_pages.views:user_collection') + +add_route('mediagoblin.edit.edit_collection', + '/u/<string:user>/c/<string:collection>/edit/', + 'mediagoblin.edit.views:edit_collection') + +add_route('mediagoblin.user_pages.collection_confirm_delete', + '/u/<string:user>/c/<string:collection>/confirm-delete/', + 'mediagoblin.user_pages.views:collection_confirm_delete') + +add_route('mediagoblin.user_pages.collection_item_confirm_remove', + '/u/<string:user>/collection/<string:collection>/<string:collection_item>/confirm_remove/', + 'mediagoblin.user_pages.views:collection_item_confirm_remove') + +add_route('mediagoblin.user_pages.collection_atom_feed', + '/u/<string:user>/collection/<string:collection>/atom/', + 'mediagoblin.user_pages.views:collection_atom_feed') + +add_route('mediagoblin.user_pages.processing_panel', + '/u/<string:user>/panel/', + 'mediagoblin.user_pages.views:processing_panel') + +# Stray edit routes +add_route('mediagoblin.edit.edit_media', + '/u/<string:user>/m/<int:media_id>/edit/', + 'mediagoblin.edit.views:edit_media') + +add_route('mediagoblin.edit.attachments', + '/u/<string:user>/m/<int:media_id>/attachments/', + 'mediagoblin.edit.views:edit_attachments') diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py new file mode 100644 index 00000000..738cc054 --- /dev/null +++ b/mediagoblin/user_pages/views.py @@ -0,0 +1,618 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import datetime + +from mediagoblin import messages, mg_globals +from mediagoblin.db.models import (MediaEntry, MediaTag, Collection, + CollectionItem, User) +from mediagoblin.tools.response import render_to_response, render_404, \ + redirect, redirect_obj +from mediagoblin.tools.translate import pass_to_ugettext as _ +from mediagoblin.tools.pagination import Pagination +from mediagoblin.user_pages import forms as user_forms +from mediagoblin.user_pages.lib import (send_comment_email, + add_media_to_collection) + +from mediagoblin.decorators import (uses_pagination, get_user_media_entry, + get_media_entry_by_id, + require_active_login, user_may_delete_media, user_may_alter_collection, + get_user_collection, get_user_collection_item, active_user_from_url) + +from werkzeug.contrib.atom import AtomFeed + + +_log = logging.getLogger(__name__) +_log.setLevel(logging.DEBUG) + + +@uses_pagination +def user_home(request, page): + """'Homepage' of a User()""" + # TODO: decide if we only want homepages for active users, we can + # then use the @get_active_user decorator and also simplify the + # template html. + user = User.query.filter_by(username=request.matchdict['user']).first() + if not user: + return render_404(request) + elif user.status != u'active': + return render_to_response( + request, + 'mediagoblin/user_pages/user.html', + {'user': user}) + + cursor = MediaEntry.query.\ + filter_by(uploader = user.id, + state = u'processed').order_by(MediaEntry.created.desc()) + + pagination = Pagination(page, cursor) + media_entries = pagination() + + #if no data is available, return NotFound + if media_entries == None: + return render_404(request) + + user_gallery_url = request.urlgen( + 'mediagoblin.user_pages.user_gallery', + user=user.username) + + return render_to_response( + request, + 'mediagoblin/user_pages/user.html', + {'user': user, + 'user_gallery_url': user_gallery_url, + 'media_entries': media_entries, + 'pagination': pagination}) + + +@active_user_from_url +@uses_pagination +def user_gallery(request, page, url_user=None): + """'Gallery' of a User()""" + tag = request.matchdict.get('tag', None) + cursor = MediaEntry.query.filter_by( + uploader=url_user.id, + state=u'processed').order_by(MediaEntry.created.desc()) + + # Filter potentially by tag too: + if tag: + cursor = cursor.filter( + MediaEntry.tags_helper.any( + MediaTag.slug == request.matchdict['tag'])) + + # Paginate gallery + pagination = Pagination(page, cursor) + media_entries = pagination() + + #if no data is available, return NotFound + # TODO: Should we really also return 404 for empty galleries? + if media_entries == None: + return render_404(request) + + return render_to_response( + request, + 'mediagoblin/user_pages/gallery.html', + {'user': url_user, 'tag': tag, + 'media_entries': media_entries, + 'pagination': pagination}) + +MEDIA_COMMENTS_PER_PAGE = 50 + + +@get_user_media_entry +@uses_pagination +def media_home(request, media, page, **kwargs): + """ + 'Homepage' of a MediaEntry() + """ + comment_id = request.matchdict.get('comment', None) + if comment_id: + pagination = Pagination( + page, media.get_comments( + mg_globals.app_config['comments_ascending']), + MEDIA_COMMENTS_PER_PAGE, + comment_id) + else: + pagination = Pagination( + page, media.get_comments( + mg_globals.app_config['comments_ascending']), + MEDIA_COMMENTS_PER_PAGE) + + comments = pagination() + + comment_form = user_forms.MediaCommentForm(request.form) + + media_template_name = media.media_manager['display_template'] + + return render_to_response( + request, + media_template_name, + {'media': media, + 'comments': comments, + 'pagination': pagination, + 'comment_form': comment_form, + 'app_config': mg_globals.app_config}) + + +@get_media_entry_by_id +@require_active_login +def media_post_comment(request, media): + """ + recieves POST from a MediaEntry() comment form, saves the comment. + """ + assert request.method == 'POST' + + comment = request.db.MediaComment() + comment.media_entry = media.id + comment.author = request.user.id + comment.content = unicode(request.form['comment_content']) + + # Show error message if commenting is disabled. + if not mg_globals.app_config['allow_comments']: + messages.add_message( + request, + messages.ERROR, + _("Sorry, comments are disabled.")) + elif not comment.content.strip(): + messages.add_message( + request, + messages.ERROR, + _("Oops, your comment was empty.")) + else: + comment.save() + + messages.add_message( + request, messages.SUCCESS, + _('Your comment has been posted!')) + + media_uploader = media.get_uploader + #don't send email if you comment on your own post + if (comment.author != media_uploader and + media_uploader.wants_comment_notification): + send_comment_email(media_uploader, comment, media, request) + + return redirect_obj(request, media) + + +@get_media_entry_by_id +@require_active_login +def media_collect(request, media): + """Add media to collection submission""" + + form = user_forms.MediaCollectForm(request.form) + # A user's own collections: + form.collection.query = Collection.query.filter_by( + creator = request.user.id).order_by(Collection.title) + + if request.method != 'POST' or not form.validate(): + # No POST submission, or invalid form + if not form.validate(): + messages.add_message(request, messages.ERROR, + _('Please check your entries and try again.')) + + return render_to_response( + request, + 'mediagoblin/user_pages/media_collect.html', + {'media': media, + 'form': form}) + + # If we are here, method=POST and the form is valid, submit things. + # If the user is adding a new collection, use that: + if form.collection_title.data: + # Make sure this user isn't duplicating an existing collection + existing_collection = Collection.query.filter_by( + creator=request.user.id, + title=form.collection_title.data).first() + if existing_collection: + messages.add_message(request, messages.ERROR, + _('You already have a collection called "%s"!') + % existing_collection.title) + return redirect(request, "mediagoblin.user_pages.media_home", + user=media.get_uploader.username, + media=media.slug_or_id) + + collection = Collection() + collection.title = form.collection_title.data + collection.description = form.collection_description.data + collection.creator = request.user.id + collection.generate_slug() + collection.save() + + # Otherwise, use the collection selected from the drop-down + else: + collection = form.collection.data + if collection and collection.creator != request.user.id: + collection = None + + # Make sure the user actually selected a collection + if not collection: + messages.add_message( + request, messages.ERROR, + _('You have to select or add a collection')) + return redirect(request, "mediagoblin.user_pages.media_collect", + user=media.get_uploader.username, + media_id=media.id) + + + # Check whether media already exists in collection + elif CollectionItem.query.filter_by( + media_entry=media.id, + collection=collection.id).first(): + messages.add_message(request, messages.ERROR, + _('"%s" already in collection "%s"') + % (media.title, collection.title)) + else: # Add item to collection + add_media_to_collection(collection, media, form.note.data) + + messages.add_message(request, messages.SUCCESS, + _('"%s" added to collection "%s"') + % (media.title, collection.title)) + + return redirect_obj(request, media) + + +#TODO: Why does @user_may_delete_media not implicate @require_active_login? +@get_media_entry_by_id +@require_active_login +@user_may_delete_media +def media_confirm_delete(request, media): + + form = user_forms.ConfirmDeleteForm(request.form) + + if request.method == 'POST' and form.validate(): + if form.confirm.data is True: + username = media.get_uploader.username + # Delete MediaEntry and all related files, comments etc. + media.delete() + messages.add_message( + request, messages.SUCCESS, _('You deleted the media.')) + + return redirect(request, "mediagoblin.user_pages.user_home", + user=username) + else: + messages.add_message( + request, messages.ERROR, + _("The media was not deleted because you didn't check that you were sure.")) + return redirect_obj(request, media) + + if ((request.user.is_admin and + request.user.id != media.uploader)): + messages.add_message( + request, messages.WARNING, + _("You are about to delete another user's media. " + "Proceed with caution.")) + + return render_to_response( + request, + 'mediagoblin/user_pages/media_confirm_delete.html', + {'media': media, + 'form': form}) + + +@active_user_from_url +@uses_pagination +def user_collection(request, page, url_user=None): + """A User-defined Collection""" + collection = Collection.query.filter_by( + get_creator=url_user, + slug=request.matchdict['collection']).first() + + if not collection: + return render_404(request) + + cursor = collection.get_collection_items() + + pagination = Pagination(page, cursor) + collection_items = pagination() + + # if no data is available, return NotFound + # TODO: Should an empty collection really also return 404? + if collection_items == None: + return render_404(request) + + return render_to_response( + request, + 'mediagoblin/user_pages/collection.html', + {'user': url_user, + 'collection': collection, + 'collection_items': collection_items, + 'pagination': pagination}) + + +@active_user_from_url +def collection_list(request, url_user=None): + """A User-defined Collection""" + collections = Collection.query.filter_by( + get_creator=url_user) + + return render_to_response( + request, + 'mediagoblin/user_pages/collection_list.html', + {'user': url_user, + 'collections': collections}) + + +@get_user_collection_item +@require_active_login +@user_may_alter_collection +def collection_item_confirm_remove(request, collection_item): + + form = user_forms.ConfirmCollectionItemRemoveForm(request.form) + + if request.method == 'POST' and form.validate(): + username = collection_item.in_collection.get_creator.username + collection = collection_item.in_collection + + if form.confirm.data is True: + entry = collection_item.get_media_entry + entry.collected = entry.collected - 1 + entry.save() + + collection_item.delete() + collection.items = collection.items - 1 + collection.save() + + messages.add_message( + request, messages.SUCCESS, _('You deleted the item from the collection.')) + else: + messages.add_message( + request, messages.ERROR, + _("The item was not removed because you didn't check that you were sure.")) + + return redirect_obj(request, collection) + + if ((request.user.is_admin and + request.user.id != collection_item.in_collection.creator)): + messages.add_message( + request, messages.WARNING, + _("You are about to delete an item from another user's collection. " + "Proceed with caution.")) + + return render_to_response( + request, + 'mediagoblin/user_pages/collection_item_confirm_remove.html', + {'collection_item': collection_item, + 'form': form}) + + +@get_user_collection +@require_active_login +@user_may_alter_collection +def collection_confirm_delete(request, collection): + + form = user_forms.ConfirmDeleteForm(request.form) + + if request.method == 'POST' and form.validate(): + + username = collection.get_creator.username + + if form.confirm.data is True: + collection_title = collection.title + + # Delete all the associated collection items + for item in collection.get_collection_items(): + entry = item.get_media_entry + entry.collected = entry.collected - 1 + entry.save() + item.delete() + + collection.delete() + messages.add_message(request, messages.SUCCESS, + _('You deleted the collection "%s"') % collection_title) + + return redirect(request, "mediagoblin.user_pages.user_home", + user=username) + else: + messages.add_message( + request, messages.ERROR, + _("The collection was not deleted because you didn't check that you were sure.")) + + return redirect_obj(request, collection) + + if ((request.user.is_admin and + request.user.id != collection.creator)): + messages.add_message( + request, messages.WARNING, + _("You are about to delete another user's collection. " + "Proceed with caution.")) + + return render_to_response( + request, + 'mediagoblin/user_pages/collection_confirm_delete.html', + {'collection': collection, + 'form': form}) + + +ATOM_DEFAULT_NR_OF_UPDATED_ITEMS = 15 + + +def atom_feed(request): + """ + generates the atom feed with the newest images + """ + user = User.query.filter_by( + username = request.matchdict['user'], + status = u'active').first() + if not user: + return render_404(request) + + cursor = MediaEntry.query.filter_by( + uploader = user.id, + state = u'processed').\ + order_by(MediaEntry.created.desc()).\ + limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS) + + """ + ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI) + """ + atomlinks = [{ + 'href': request.urlgen( + 'mediagoblin.user_pages.user_home', + qualified=True, user=request.matchdict['user']), + 'rel': 'alternate', + 'type': 'text/html' + }] + + if mg_globals.app_config["push_urls"]: + for push_url in mg_globals.app_config["push_urls"]: + atomlinks.append({ + 'rel': 'hub', + 'href': push_url}) + + feed = AtomFeed( + "MediaGoblin: Feed for user '%s'" % request.matchdict['user'], + feed_url=request.url, + id='tag:{host},{year}:gallery.user-{user}'.format( + host=request.host, + year=datetime.datetime.today().strftime('%Y'), + user=request.matchdict['user']), + links=atomlinks) + + for entry in cursor: + feed.add(entry.get('title'), + entry.description_html, + id=entry.url_for_self(request.urlgen, qualified=True), + content_type='html', + author={ + 'name': entry.get_uploader.username, + 'uri': request.urlgen( + 'mediagoblin.user_pages.user_home', + qualified=True, user=entry.get_uploader.username)}, + updated=entry.get('created'), + links=[{ + 'href': entry.url_for_self( + request.urlgen, + qualified=True), + 'rel': 'alternate', + 'type': 'text/html'}]) + + return feed.get_response() + + +def collection_atom_feed(request): + """ + generates the atom feed with the newest images from a collection + """ + user = User.query.filter_by( + username = request.matchdict['user'], + status = u'active').first() + if not user: + return render_404(request) + + collection = Collection.query.filter_by( + creator=user.id, + slug=request.matchdict['collection']).first() + if not collection: + return render_404(request) + + cursor = CollectionItem.query.filter_by( + collection=collection.id) \ + .order_by(CollectionItem.added.desc()) \ + .limit(ATOM_DEFAULT_NR_OF_UPDATED_ITEMS) + + """ + ATOM feed id is a tag URI (see http://en.wikipedia.org/wiki/Tag_URI) + """ + atomlinks = [{ + 'href': collection.url_for_self(request.urlgen, qualified=True), + 'rel': 'alternate', + 'type': 'text/html' + }] + + if mg_globals.app_config["push_urls"]: + for push_url in mg_globals.app_config["push_urls"]: + atomlinks.append({ + 'rel': 'hub', + 'href': push_url}) + + feed = AtomFeed( + "MediaGoblin: Feed for %s's collection %s" % + (request.matchdict['user'], collection.title), + feed_url=request.url, + id=u'tag:{host},{year}:gnu-mediagoblin.{user}.collection.{slug}'\ + .format( + host=request.host, + year=collection.created.strftime('%Y'), + user=request.matchdict['user'], + slug=collection.slug), + links=atomlinks) + + for item in cursor: + entry = item.get_media_entry + feed.add(entry.get('title'), + item.note_html, + id=entry.url_for_self(request.urlgen, qualified=True), + content_type='html', + author={ + 'name': entry.get_uploader.username, + 'uri': request.urlgen( + 'mediagoblin.user_pages.user_home', + qualified=True, user=entry.get_uploader.username)}, + updated=item.get('added'), + links=[{ + 'href': entry.url_for_self( + request.urlgen, + qualified=True), + 'rel': 'alternate', + 'type': 'text/html'}]) + + return feed.get_response() + + +@require_active_login +def processing_panel(request): + """ + Show to the user what media is still in conversion/processing... + and what failed, and why! + """ + user = User.query.filter_by(username=request.matchdict['user']).first() + # TODO: XXX: Should this be a decorator? + # + # Make sure we have permission to access this user's panel. Only + # admins and this user herself should be able to do so. + if not (user.id == request.user.id or request.user.is_admin): + # No? Simply redirect to this user's homepage. + return redirect( + request, 'mediagoblin.user_pages.user_home', + user=user.username) + + # Get media entries which are in-processing + processing_entries = MediaEntry.query.\ + filter_by(uploader = user.id, + state = u'processing').\ + order_by(MediaEntry.created.desc()) + + # Get media entries which have failed to process + failed_entries = MediaEntry.query.\ + filter_by(uploader = user.id, + state = u'failed').\ + order_by(MediaEntry.created.desc()) + + processed_entries = MediaEntry.query.\ + filter_by(uploader = user.id, + state = u'processed').\ + order_by(MediaEntry.created.desc()).\ + limit(10) + + # Render to response + return render_to_response( + request, + 'mediagoblin/user_pages/processing_panel.html', + {'user': user, + 'processing_entries': processing_entries, + 'failed_entries': failed_entries, + 'processed_entries': processed_entries}) diff --git a/mediagoblin/views.py b/mediagoblin/views.py new file mode 100644 index 00000000..6acd7e96 --- /dev/null +++ b/mediagoblin/views.py @@ -0,0 +1,46 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin import mg_globals +from mediagoblin.db.models import MediaEntry +from mediagoblin.tools.pagination import Pagination +from mediagoblin.tools.response import render_to_response +from mediagoblin.decorators import uses_pagination + + + +@uses_pagination +def root_view(request, page): + cursor = MediaEntry.query.filter_by(state=u'processed').\ + order_by(MediaEntry.created.desc()) + + pagination = Pagination(page, cursor) + media_entries = pagination() + return render_to_response( + request, 'mediagoblin/root.html', + {'media_entries': media_entries, + 'allow_registration': mg_globals.app_config["allow_registration"], + 'pagination': pagination}) + + +def simple_template_render(request): + """ + A view for absolutely simple template rendering. + Just make sure 'template' is in the matchdict! + """ + template_name = request.matchdict['template'] + return render_to_response( + request, template_name, {}) diff --git a/mediagoblin/webfinger/__init__.py b/mediagoblin/webfinger/__init__.py new file mode 100644 index 00000000..126e6ea2 --- /dev/null +++ b/mediagoblin/webfinger/__init__.py @@ -0,0 +1,25 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +''' +mediagoblin.webfinger_ provides an LRDD discovery service and +a web host meta information file + +Links: +- `LRDD Discovery Draft + <http://tools.ietf.org/html/draft-hammer-discovery-06>`_. +- `RFC 6415 - Web Host Metadata + <http://tools.ietf.org/html/rfc6415>`_. +''' diff --git a/mediagoblin/webfinger/routing.py b/mediagoblin/webfinger/routing.py new file mode 100644 index 00000000..eb10509f --- /dev/null +++ b/mediagoblin/webfinger/routing.py @@ -0,0 +1,23 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.routing import add_route + +add_route('mediagoblin.webfinger.host_meta', '/.well-known/host-meta', + 'mediagoblin.webfinger.views:host_meta') + +add_route('mediagoblin.webfinger.xrd', '/webfinger/xrd', + 'mediagoblin.webfinger.views:xrd') diff --git a/mediagoblin/webfinger/views.py b/mediagoblin/webfinger/views.py new file mode 100644 index 00000000..97fc3ef7 --- /dev/null +++ b/mediagoblin/webfinger/views.py @@ -0,0 +1,117 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +''' +For references, see docstring in mediagoblin/webfinger/__init__.py +''' + +import re + +from urlparse import urlparse + +from mediagoblin.tools.response import render_to_response, render_404 + +def host_meta(request): + ''' + Webfinger host-meta + ''' + + placeholder = 'MG_LRDD_PLACEHOLDER' + + lrdd_title = 'GNU MediaGoblin - User lookup' + + lrdd_template = request.urlgen( + 'mediagoblin.webfinger.xrd', + uri=placeholder, + qualified=True) + + return render_to_response( + request, + 'mediagoblin/webfinger/host-meta.xml', + {'request': request, + 'lrdd_template': lrdd_template, + 'lrdd_title': lrdd_title, + 'placeholder': placeholder}) + +MATCH_SCHEME_PATTERN = re.compile(r'^acct:') + +def xrd(request): + ''' + Find user data based on a webfinger URI + ''' + param_uri = request.GET.get('uri') + + if not param_uri: + return render_404(request) + + ''' + :py:module:`urlparse` does not recognize usernames in URIs of the + form ``acct:user@example.org`` or ``user@example.org``. + ''' + if not MATCH_SCHEME_PATTERN.search(param_uri): + # Assume the URI is in the form ``user@example.org`` + uri = 'acct://' + param_uri + else: + # Assumes the URI looks like ``acct:user@example.org + uri = MATCH_SCHEME_PATTERN.sub( + 'acct://', param_uri) + + parsed = urlparse(uri) + + xrd_subject = param_uri + + # TODO: Verify that the user exists + # Q: Does webfinger support error handling in this case? + # Returning 404 seems intuitive, need to check. + if parsed.username: + # The user object + # TODO: Fetch from database instead of using the MockUser + user = MockUser() + user.username = parsed.username + + xrd_links = [ + {'attrs': { + 'rel': 'http://microformats.org/profile/hcard', + 'href': request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username, + qualified=True)}}, + {'attrs': { + 'rel': 'http://schemas.google.com/g/2010#updates-from', + 'href': request.urlgen( + 'mediagoblin.user_pages.atom_feed', + user=user.username, + qualified=True)}}] + + xrd_alias = request.urlgen( + 'mediagoblin.user_pages.user_home', + user=user.username, + qualified=True) + + return render_to_response( + request, + 'mediagoblin/webfinger/xrd.xml', + {'request': request, + 'subject': xrd_subject, + 'alias': xrd_alias, + 'links': xrd_links }) + else: + return render_404(request) + +class MockUser(object): + ''' + TEMPORARY user object + ''' + username = None |