diff options
115 files changed, 4400 insertions, 3237 deletions
diff --git a/askbot/conf/badges.py b/askbot/conf/badges.py index 53292b05..ac510055 100644 --- a/askbot/conf/badges.py +++ b/askbot/conf/badges.py @@ -100,7 +100,7 @@ settings.register( IntegerValue( BADGES, 'POPULAR_QUESTION_BADGE_MIN_VIEWS', - default=150, + default=15, description=_('Popular Question: minimum views') ) ) @@ -109,7 +109,7 @@ settings.register( IntegerValue( BADGES, 'NOTABLE_QUESTION_BADGE_MIN_VIEWS', - default=250, + default=25, description=_('Notable Question: minimum views') ) ) @@ -118,7 +118,7 @@ settings.register( IntegerValue( BADGES, 'FAMOUS_QUESTION_BADGE_MIN_VIEWS', - default=500, + default=50, description=_('Famous Question: minimum views') ) ) @@ -217,7 +217,7 @@ settings.register( IntegerValue( BADGES, 'TAXONOMIST_BADGE_MIN_USE_COUNT', - default = 10, + default = 5, description = _('Taxonomist: minimum tag use count') ) ) @@ -226,7 +226,7 @@ settings.register( IntegerValue( BADGES, 'ENTHUSIAST_BADGE_MIN_DAYS', - default = 30, + default = 5, description = _('Enthusiast: minimum days') ) ) diff --git a/askbot/conf/email.py b/askbot/conf/email.py index 195f36e3..1b81fa96 100644 --- a/askbot/conf/email.py +++ b/askbot/conf/email.py @@ -274,8 +274,6 @@ settings.register( ) ) - - settings.register( livesettings.BooleanValue( EMAIL, @@ -291,6 +289,31 @@ settings.register( ) settings.register( + livesettings.StringValue( + EMAIL, + 'SELF_NOTIFY_EMAILED_POST_AUTHOR_WHEN', + description = _( + 'Emailed post: when to notify author about publishing' + ), + choices = const.SELF_NOTIFY_EMAILED_POST_AUTHOR_WHEN_CHOICES, + default = const.NEVER + ) +) + +#not implemented at this point +#settings.register( +# livesettings.IntegerValue( +# EMAIL, +# 'SELF_NOTIFY_WEB_POST_AUTHOR_WHEN', +# description = _( +# 'Web post: when to notify author about publishing' +# ), +# choices = const.SELF_NOTIFY_WEB_POST_AUTHOR_WHEN_CHOICES, +# default = const.NEVER +# ) +#) + +settings.register( livesettings.StringValue( EMAIL, 'REPLY_BY_EMAIL_HOSTNAME', @@ -301,8 +324,6 @@ settings.register( ) ) - - settings.register( livesettings.IntegerValue( EMAIL, @@ -311,4 +332,3 @@ settings.register( description=_('Email replies having fewer words than this number will be posted as comments instead of answers') ) ) - diff --git a/askbot/conf/forum_data_rules.py b/askbot/conf/forum_data_rules.py index 7d98c9e8..a26aa4b3 100644 --- a/askbot/conf/forum_data_rules.py +++ b/askbot/conf/forum_data_rules.py @@ -200,6 +200,21 @@ settings.register( ) ) +MARKED_TAG_DISPLAY_CHOICES = ( + ('always', _('Always, for all users')), + ('never', _('Never, for all users')), + ('when-user-wants', _('Let users decide')) +) +settings.register( + livesettings.StringValue( + FORUM_DATA_RULES, + 'MARKED_TAGS_ARE_PUBLIC_WHEN', + default = 'always', + choices = MARKED_TAG_DISPLAY_CHOICES, + description = _('Publicly show user tag selections') + ) +) + settings.register( livesettings.BooleanValue( FORUM_DATA_RULES, diff --git a/askbot/conf/minimum_reputation.py b/askbot/conf/minimum_reputation.py index 06f210f2..54ba0f65 100644 --- a/askbot/conf/minimum_reputation.py +++ b/askbot/conf/minimum_reputation.py @@ -18,7 +18,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_VOTE_UP', - default=15, + default=5, description=_('Upvote') ) ) @@ -27,7 +27,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_VOTE_DOWN', - default=100, + default=50, description=_('Downvote') ) ) @@ -36,7 +36,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_ANSWER_OWN_QUESTION', - default=25, + default=5, description=_('Answer own question immediately') ) ) @@ -45,7 +45,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_ACCEPT_OWN_ANSWER', - default=50, + default=20, description=_('Accept own answer') ) ) @@ -54,7 +54,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_FLAG_OFFENSIVE', - default=15, + default=5, description=_('Flag offensive') ) ) @@ -63,7 +63,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_LEAVE_COMMENTS', - default=50, + default=10, description=_('Leave comments') ) ) @@ -72,7 +72,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_DELETE_OTHERS_COMMENTS', - default=2000, + default=200, description=_('Delete comments posted by others') ) ) @@ -81,7 +81,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_DELETE_OTHERS_POSTS', - default=5000, + default=500, description=_('Delete questions and answers posted by others') ) ) @@ -90,7 +90,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_UPLOAD_FILES', - default=60, + default=10, description=_('Upload files') ) ) @@ -99,7 +99,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_CLOSE_OWN_QUESTIONS', - default=250, + default=25, description=_('Close own questions'), ) ) @@ -108,7 +108,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS', - default=500, + default=50, description=_('Retag questions posted by other people') ) ) @@ -117,7 +117,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_REOPEN_OWN_QUESTIONS', - default=500, + default=50, description=_('Reopen own questions') ) ) @@ -126,7 +126,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_EDIT_WIKI', - default=750, + default=75, description=_('Edit community wiki posts') ) ) @@ -135,7 +135,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_EDIT_OTHERS_POSTS', - default=2000, + default=200, description=_('Edit posts authored by other people') ) ) @@ -144,7 +144,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_VIEW_OFFENSIVE_FLAGS', - default=2000, + default=200, description=_('View offensive flags') ) ) @@ -153,7 +153,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS', - default=2000, + default=200, description=_('Close questions asked by others') ) ) @@ -162,7 +162,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_LOCK_POSTS', - default=4000, + default=400, description=_('Lock posts') ) ) @@ -171,7 +171,7 @@ settings.register( livesettings.IntegerValue( MIN_REP, 'MIN_REP_TO_HAVE_STRONG_URL', - default=250, + default=25, description=_('Remove rel=nofollow from own homepage'), help_text=_( 'When a search engine crawler will see a rel=nofollow ' @@ -189,4 +189,4 @@ settings.register( default=100, description=_('Post answers and comments by email') ) -)
\ No newline at end of file +) diff --git a/askbot/conf/sidebar_question.py b/askbot/conf/sidebar_question.py index 4416823d..bb71be7e 100644 --- a/askbot/conf/sidebar_question.py +++ b/askbot/conf/sidebar_question.py @@ -6,21 +6,35 @@ from askbot.deps.livesettings import ConfigurationGroup from askbot.deps.livesettings import values from django.utils.translation import ugettext as _ from askbot.conf.super_groups import CONTENT_AND_UI -SIDEBAR_QUESTION = ConfigurationGroup( +SIDEBAR_QUESTION = ConfigurationGroup(#shitty name - why sidebar? 'SIDEBAR_QUESTION', - _('Question page sidebar'), + _('Question page banners and sidebar'), super_group = CONTENT_AND_UI ) settings.register( values.LongStringValue( SIDEBAR_QUESTION, + 'QUESTION_PAGE_TOP_BANNER', + description = _('Top banner'), + default = '', + help_text = _( + 'When using this option, please ' + 'use the HTML validation service to make sure that ' + 'your input is valid and works well in all browsers.' + ) + ) +) + +settings.register( + values.LongStringValue( + SIDEBAR_QUESTION, 'SIDEBAR_QUESTION_HEADER', description = _('Custom sidebar header'), default = '', help_text = _( 'Use this area to enter content at the TOP of the sidebar' - 'in HTML format. When using this option ' + 'in HTML format. When using this option ' '(as well as the sidebar footer), please ' 'use the HTML validation service to make sure that ' 'your input is valid and works well in all browsers.' diff --git a/askbot/conf/site_modes.py b/askbot/conf/site_modes.py index efbbcca0..8ed86f12 100644 --- a/askbot/conf/site_modes.py +++ b/askbot/conf/site_modes.py @@ -9,35 +9,35 @@ from askbot.conf.super_groups import REP_AND_BADGES from askbot.deps.livesettings import ConfigurationGroup, BooleanValue from django.utils.translation import ugettext as _ -BOOTSTRAP_MODE_SETTINGS = { +LARGE_SITE_MODE_SETTINGS = { #minimum reputation settins. - 'MIN_REP_TO_VOTE_UP': 5, - 'MIN_REP_TO_VOTE_DOWN': 50, - 'MIN_REP_TO_ANSWER_OWN_QUESTION': 5, - 'MIN_REP_TO_ACCEPT_OWN_ANSWER': 20, - 'MIN_REP_TO_FLAG_OFFENSIVE': 5, - 'MIN_REP_TO_LEAVE_COMMENTS': 10, - 'MIN_REP_TO_DELETE_OTHERS_COMMENTS': 200, - 'MIN_REP_TO_DELETE_OTHERS_POSTS': 500, - 'MIN_REP_TO_UPLOAD_FILES': 10, - 'MIN_REP_TO_CLOSE_OWN_QUESTIONS': 25, - 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS': 50, - 'MIN_REP_TO_REOPEN_OWN_QUESTIONS': 50, - 'MIN_REP_TO_EDIT_WIKI': 75, - 'MIN_REP_TO_EDIT_OTHERS_POSTS': 200, - 'MIN_REP_TO_VIEW_OFFENSIVE_FLAGS': 200, - 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS': 200, - 'MIN_REP_TO_LOCK_POSTS': 400, - 'MIN_REP_TO_HAVE_STRONG_URL': 25, + 'MIN_REP_TO_VOTE_UP': 15, + 'MIN_REP_TO_VOTE_DOWN': 100, + 'MIN_REP_TO_ANSWER_OWN_QUESTION': 25, + 'MIN_REP_TO_ACCEPT_OWN_ANSWER': 50, + 'MIN_REP_TO_FLAG_OFFENSIVE': 15, + 'MIN_REP_TO_LEAVE_COMMENTS': 50, + 'MIN_REP_TO_DELETE_OTHERS_COMMENTS': 2000, + 'MIN_REP_TO_DELETE_OTHERS_POSTS': 5000, + 'MIN_REP_TO_UPLOAD_FILES': 60, + 'MIN_REP_TO_CLOSE_OWN_QUESTIONS': 250, + 'MIN_REP_TO_RETAG_OTHERS_QUESTIONS': 500, + 'MIN_REP_TO_REOPEN_OWN_QUESTIONS': 500, + 'MIN_REP_TO_EDIT_WIKI': 750, + 'MIN_REP_TO_EDIT_OTHERS_POSTS': 2000, + 'MIN_REP_TO_VIEW_OFFENSIVE_FLAGS': 2000, + 'MIN_REP_TO_CLOSE_OTHERS_QUESTIONS': 2000, + 'MIN_REP_TO_LOCK_POSTS': 4000, + 'MIN_REP_TO_HAVE_STRONG_URL': 250, #badge settings - 'NOTABLE_QUESTION_BADGE_MIN_VIEWS': 25, - 'POPULAR_QUESTION_BADGE_MIN_VIEWS': 15, - 'FAMOUS_QUESTION_BADGE_MIN_VIEWS': 50, - 'ENTHUSIAST_BADGE_MIN_DAYS': 5, - 'TAXONOMIST_BADGE_MIN_USE_COUNT': 5, + 'NOTABLE_QUESTION_BADGE_MIN_VIEWS': 250, + 'POPULAR_QUESTION_BADGE_MIN_VIEWS': 150, + 'FAMOUS_QUESTION_BADGE_MIN_VIEWS': 500, + 'ENTHUSIAST_BADGE_MIN_DAYS': 30, + 'TAXONOMIST_BADGE_MIN_USE_COUNT': 10, #moderation rule settings - 'MIN_FLAGS_TO_HIDE_POST': 2, - 'MIN_FLAGS_TO_DELETE_POST': 3, + 'MIN_FLAGS_TO_HIDE_POST': 3, + 'MIN_FLAGS_TO_DELETE_POST': 5, } def bootstrap_callback(current_value, new_value): @@ -49,11 +49,11 @@ def bootstrap_callback(current_value, new_value): return new_value if new_value == True: - for key, value in BOOTSTRAP_MODE_SETTINGS.items(): + for key, value in LARGE_SITE_MODE_SETTINGS.items(): settings.update(key, value) else: - for key in BOOTSTRAP_MODE_SETTINGS: + for key in LARGE_SITE_MODE_SETTINGS: settings.reset(key) return new_value @@ -68,14 +68,14 @@ SITE_MODES = ConfigurationGroup( settings.register( BooleanValue( SITE_MODES, - 'ACTIVATE_BOOTSTRAP_MODE', + 'ACTIVATE_LARGE_SITE_MODE', default=False, description=_( - 'Activate a "Bootstrap" mode'), + 'Activate a "Large site" mode'), help_text=_( - "Bootstrap mode lowers reputation and certain badge " + "\"Large site\" mode increases reputation and certain badge " "thresholds, to values, more suitable " - "for the smaller communities, " + "for the larger communities, " "<strong>WARNING:</strong> your current values for " "Minimum reputation, " "Badge Settings and " diff --git a/askbot/conf/vote_rules.py b/askbot/conf/vote_rules.py index 82c9b758..7bafe32f 100644 --- a/askbot/conf/vote_rules.py +++ b/askbot/conf/vote_rules.py @@ -65,7 +65,7 @@ settings.register( IntegerValue( VOTE_RULES, 'MIN_FLAGS_TO_HIDE_POST', - default=3, + default=2, description=_('Number of flags required to automatically hide posts') ) ) @@ -74,7 +74,7 @@ settings.register( IntegerValue( VOTE_RULES, 'MIN_FLAGS_TO_DELETE_POST', - default=5, + default=3, description=_('Number of flags required to automatically delete posts') ) ) diff --git a/askbot/const/__init__.py b/askbot/const/__init__.py index 7eef91d1..e693a63c 100644 --- a/askbot/const/__init__.py +++ b/askbot/const/__init__.py @@ -6,6 +6,7 @@ text in this project, all unicode text go here. """ from django.utils.translation import ugettext as _ import re + CLOSE_REASONS = ( (1, _('duplicate question')), (2, _('question is off-topic or not relevant')), @@ -54,8 +55,51 @@ POST_SORT_METHODS = ( POST_TYPES = ('answer', 'comment', 'question', 'tag_wiki', 'reject_reason') +SIMPLE_REPLY_SEPARATOR_TEMPLATE = '==== %s -=-==' + +#values for SELF_NOTIFY_WHEN... settings use bits +NEVER = 'never' +FOR_FIRST_REVISION = 'first' +FOR_ANY_REVISION = 'any' +SELF_NOTIFY_EMAILED_POST_AUTHOR_WHEN_CHOICES = ( + (NEVER, _('Never')), + (FOR_FIRST_REVISION, _('When new post is published')), + (FOR_ANY_REVISION, _('When post is published or revised')), +) +#need more options for web posts b/c user is looking at the page +#when posting. when posts are made by email - user is not looking +#at the site and therefore won't get any feedback unless an email is sent back +#todo: rename INITIAL -> FIRST and make values of type string +#FOR_INITIAL_REVISION_WHEN_APPROVED = 1 +#FOR_ANY_REVISION_WHEN_APPROVED = 2 +#FOR_INITIAL_REVISION_ALWAYS = 3 +#FOR_ANY_REVISION_ALWAYS = 4 +#SELF_NOTIFY_WEB_POST_AUTHOR_WHEN_CHOICES = ( +# (NEVER, _('Never')), +# ( +# FOR_INITIAL_REVISION_WHEN_APPROVED, +# _('When inital revision is approved by moderator') +# ), +# ( +# FOR_ANY_REVISION_WHEN_APPROVED, +# _('When any revision is approved by moderator') +# ), +# ( +# FOR_INITIAL_REVISION_ALWAYS, +# _('Any time when inital revision is published') +# ), +# ( +# FOR_ANY_REVISION_ALWAYS, +# _('Any time when revision is published') +# ) +#) + REPLY_SEPARATOR_TEMPLATE = '==== %(user_action)s %(instruction)s -=-==' -REPLY_SEPARATOR_REGEX = re.compile('==== .* -=-==', re.MULTILINE) +REPLY_WITH_COMMENT_TEMPLATE = _( + 'Note: to reply with a comment, ' + 'please use <a href="mailto:%(addr)s?subject=%(subject)s">this link</a>' +) +REPLY_SEPARATOR_REGEX = re.compile(r'==== .* -=-==', re.MULTILINE|re.DOTALL) ANSWER_SORT_METHODS = (#no translations needed here 'latest', 'oldest', 'votes' @@ -129,6 +173,7 @@ TYPE_ACTIVITY_MODERATED_NEW_POST = 24 TYPE_ACTIVITY_MODERATED_POST_EDIT = 25 TYPE_ACTIVITY_CREATE_REJECT_REASON = 26 TYPE_ACTIVITY_UPDATE_REJECT_REASON = 27 +TYPE_ACTIVITY_VALIDATION_EMAIL_SENT = 28 #TYPE_ACTIVITY_EDIT_QUESTION = 17 #TYPE_ACTIVITY_EDIT_ANSWER = 18 @@ -182,6 +227,10 @@ TYPE_ACTIVITY = ( TYPE_ACTIVITY_UPDATE_REJECT_REASON, _('updated post reject reason') ), + ( + TYPE_ACTIVITY_VALIDATION_EMAIL_SENT, + 'sent email address validation message'#don't translate, internal + ), ) diff --git a/askbot/const/message_keys.py b/askbot/const/message_keys.py index 0ee99e0c..9ac82e57 100644 --- a/askbot/const/message_keys.py +++ b/askbot/const/message_keys.py @@ -33,6 +33,9 @@ _('click to see the most answered questions') _('click to see least voted questions') _('by votes') _('click to see most voted questions') +_('interesting') +_('ignored') +_('subscribed') def get_i18n_message(key): messages = { diff --git a/askbot/deps/django_authopenid/backends.py b/askbot/deps/django_authopenid/backends.py index f3d8f64b..ed99e44f 100644 --- a/askbot/deps/django_authopenid/backends.py +++ b/askbot/deps/django_authopenid/backends.py @@ -10,6 +10,7 @@ from django.utils.translation import ugettext as _ from askbot.deps.django_authopenid.models import UserAssociation from askbot.deps.django_authopenid import util from askbot.conf import settings as askbot_settings +from askbot.models.signals import user_registered log = logging.getLogger('configuration') @@ -62,6 +63,7 @@ def ldap_authenticate(username, password): user.is_superuser = False user.is_active = True user.save() + user_registered.send(None, user = user) log.info('Created New User : [{0}]'.format(exact_username)) return user @@ -157,11 +159,13 @@ class AuthBackend(object): if created: user.set_password(password) user.save() + user_registered.send(None, user = user) else: #have username collision - so make up a more unique user name #bug: - if user already exists with the new username - we are in trouble new_username = '%s@%s' % (username, provider_name) user = User.objects.create_user(new_username, '', password) + user_registered.send(None, user = user) message = _( 'Welcome! Please set email address (important!) in your ' 'profile and adjust screen name, if necessary.' diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py index 22be8460..2f80d366 100644 --- a/askbot/deps/django_authopenid/views.py +++ b/askbot/deps/django_authopenid/views.py @@ -78,11 +78,11 @@ from askbot.deps.django_authopenid.backends import AuthBackend import logging from askbot.utils.forms import get_next_url from askbot.utils.http import get_request_info +from askbot.models.signals import user_logged_in, user_registered #todo: decouple from askbot def login(request,user): from django.contrib.auth import login as _login - from askbot.models import signals # get old session key session_key = request.session.session_key @@ -93,7 +93,7 @@ def login(request,user): # send signal with old session key as argument logging.debug('logged in user %s with session key %s' % (user.username, session_key)) #todo: move to auth app - signals.user_logged_in.send( + user_logged_in.send( request = request, user = user, session_key=session_key, @@ -326,7 +326,7 @@ def signin(request): request = request, user = user, user_identifier = username, - login_provider_name = ldap_provider_name, + login_provider_name = provider_name, redirect_url = next_url ) @@ -829,6 +829,7 @@ def register(request, login_provider_name=None, user_identifier=None): email = register_form.cleaned_data['email'] user = User.objects.create_user(username, email) + user_registered.send(None, user = user) logging.debug('creating new openid user association for %s') @@ -951,7 +952,9 @@ def signup_with_password(request): email = form.cleaned_data['email'] provider_name = form.cleaned_data['login_provider'] - User.objects.create_user(username, email, password) + new_user = User.objects.create_user(username, email, password) + user_registered.send(None, user = new_user) + logging.debug('new user %s created' % username) if provider_name != 'local': raise NotImplementedError('must run create external user code') diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index 7d1a2fc0..430d98de 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -1,12 +1,17 @@ Changes in Askbot ================= -In development ---------------------- +Development version +------------------- +* Welcome email for the case when replying by email is enabled (Evgeny) +* Detection of email signature based on the response to the welcome email (Evgeny) * Hide "website" and "about" section of the blocked user profiles to help prevent user profile spam (Evgeny) * Added a function to create a custom user profile tab, the feature requires access to the server (Evgeny) +* Added optional top banner to the question page (Evgeny) +* Made "bootstrap mode" default and created instead "large site mode" (Evgeny) +* Added interesting/ignored/subscribed tags to the user profile page (Paul Backhouse, Evgeny) 0.7.43 (May 14, 2012) --------------------- diff --git a/askbot/doc/source/contributors.rst b/askbot/doc/source/contributors.rst index a0881532..69348b84 100644 --- a/askbot/doc/source/contributors.rst +++ b/askbot/doc/source/contributors.rst @@ -40,6 +40,7 @@ Programming and documentation * `Jim Tittsler <http://wikieducator.org/User:JimTittsler>`_ * Silvio Heuberger * `Alexandros <https://github.com/alexandros-z>`_ +* `Paul Backhouse <https://github.com/powlo>`_ Translations ------------ diff --git a/askbot/doc/source/management-commands.rst b/askbot/doc/source/management-commands.rst index f6c5beec..b96251dc 100644 --- a/askbot/doc/source/management-commands.rst +++ b/askbot/doc/source/management-commands.rst @@ -104,6 +104,14 @@ Any configurable options, related to these commands are accessible via "Email" s +-------------------------------------+-------------------------------------------------------------+ | command | purpose | +=====================================+=============================================================+ +| `send_respondable_welcome_email` | Will send a respondable welcome email to **all** registered | +| | users whose email address was not validated. | +| | This feature requires "reply by email" enabled and "lamson" | +| | email processor installed on the system. | +| | The email will be respondable. When the user responds, | +| | askbot will validate the email and capture the signature in | +| | the end of the message. | ++-------------------------------------+-------------------------------------------------------------+ | `send_email_alerts` | Dispatches email alerts to the users according to | | | their subscription settings. This command does not | | | send instant" alerts because those are sent automatically | @@ -187,5 +195,5 @@ the developers of the Askbot project: +--------------------------------+-------------------------------------------------------------+ | `askbot_add_test_content` | Creates content with dummy data for testing | +--------------------------------+-------------------------------------------------------------+ -| `askbot_create_test_fixture` | Creates a test fixture at `askbot/tests/test_data.json` | +| `askbot_create_test_fixture` | Creates a test fixture at `askbot/tests/test_data.json` | +--------------------------------+-------------------------------------------------------------+ diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst index 6e211f4d..25bb5cc8 100644 --- a/askbot/doc/source/optional-modules.rst +++ b/askbot/doc/source/optional-modules.rst @@ -226,7 +226,7 @@ The minimum settings required to enable this feature are defining the port and b LAMSON_RECEIVER_CONFIG = {'host': 'your.ip.address', 'port': 25} - LAMSON_HANDLERS = ['askbot.lamson_handlers'] + LAMSON_HANDLERS = ['askbot.mail.lamson_handlers'] LAMSON_ROUTER_DEFAULTS = {'host': '.+'} diff --git a/askbot/feed.py b/askbot/feed.py index 776aad5e..285bb452 100644 --- a/askbot/feed.py +++ b/askbot/feed.py @@ -123,7 +123,7 @@ class RssLastestQuestionsFeed(Feed): def item_author_link(self, item): """get url of the author's profile """ - return item.author.get_profile_url() + return askbot_settings.APP_URL + item.author.get_profile_url() def item_pubdate(self, item): """get date of creation for the item diff --git a/askbot/forms.py b/askbot/forms.py index 252c002e..81382a2c 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -8,7 +8,7 @@ from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django_countries import countries from askbot.utils.forms import NextUrlField, UserNameField -from askbot.utils.mail import extract_first_email_address +from askbot.mail import extract_first_email_address from recaptcha_works.fields import RecaptchaField from askbot.conf import settings as askbot_settings import logging @@ -979,6 +979,11 @@ class EditUserForm(forms.Form): required=False ) + show_marked_tags = forms.BooleanField( + label=_('Show tag choices'), + required=False + ) + birthday = forms.DateField( label=_('Date of birth'), help_text=_('will not be shown, used to calculate age, format: YYYY-MM-DD'), @@ -1009,6 +1014,7 @@ class EditUserForm(forms.Form): country = user.country self.fields['country'].initial = country self.fields['show_country'].initial = user.show_country + self.fields['show_marked_tags'].initial = user.show_marked_tags if user.date_of_birth is not None: self.fields['birthday'].initial = user.date_of_birth diff --git a/askbot/lamson_handlers.py b/askbot/lamson_handlers.py deleted file mode 100644 index 9062594c..00000000 --- a/askbot/lamson_handlers.py +++ /dev/null @@ -1,192 +0,0 @@ -import re -from lamson.routing import route, stateless -from lamson.server import Relay -from django.utils.translation import ugettext as _ -from django.core.files.uploadedfile import SimpleUploadedFile -from django.conf import settings as django_settings -from askbot.models import ReplyAddress, Tag -from askbot.utils import mail -from askbot.conf import settings as askbot_settings - - -#we might end up needing to use something like this -#to distinguish the reply text from the quoted original message -""" -def _strip_message_qoute(message_text): - import re - result = message_text - pattern = "(?P<qoute>" + \ - "On ([a-zA-Z0-9, :/<>@\.\"\[\]]* wrote:.*)|" + \ - "From: [\w@ \.]* \[mailto:[\w\.]*@[\w\.]*\].*|" + \ - "From: [\w@ \.]*(\n|\r\n)+Sent: [\*\w@ \.,:/]*(\n|\r\n)+To:.*(\n|\r\n)+.*|" + \ - "[- ]*Forwarded by [\w@ \.,:/]*.*|" + \ - "From: [\w@ \.<>\-]*(\n|\r\n)To: [\w@ \.<>\-]*(\n|\r\n)Date: [\w@ \.<>\-:,]*\n.*|" + \ - "From: [\w@ \.<>\-]*(\n|\r\n)To: [\w@ \.<>\-]*(\n|\r\n)Sent: [\*\w@ \.,:/]*(\n|\r\n).*|" + \ - "From: [\w@ \.<>\-]*(\n|\r\n)To: [\w@ \.<>\-]*(\n|\r\n)Subject:.*|" + \ - "(-| )*Original Message(-| )*.*)" - groups = re.search(pattern, email_text, re.IGNORECASE + re.DOTALL) - qoute = None - if not groups is None: - if groups.groupdict().has_key("qoute"): - qoute = groups.groupdict()["qoute"] - if qoute: - result = reslut.split(qoute)[0] - #if the last line contains an email message remove that one too - lines = result.splitlines(True) - if re.search(r'[\w\.]*@[\w\.]*\].*', lines[-1]): - result = '\n'.join(lines[:-1]) - return result -""" - -def get_disposition(part): - """return list of part's content dispositions - or an empty list - """ - dispositions = part.content_encoding.get('Content-Disposition', None) - if dispositions: - return dispositions[0] - else: - return list() - -def get_attachment_info(part): - return part.content_encoding['Content-Disposition'][1] - -def is_attachment(part): - """True if part content disposition is - attachment""" - return get_disposition(part) == 'attachment' - -def is_inline_attachment(part): - """True if part content disposition is - inline""" - return get_disposition(part) == 'inline' - -def format_attachment(part): - """takes message part and turns it into SimpleUploadedFile object""" - att_info = get_attachment_info(part) - name = att_info.get('filename', None) - content_type = get_content_type(part) - return SimpleUploadedFile(name, part.body, content_type) - -def get_content_type(part): - """return content type of the message part""" - return part.content_encoding.get('Content-Type', (None,))[0] - -def is_body(part): - """True, if part is plain text and is not attachment""" - if get_content_type(part) == 'text/plain': - if not is_attachment(part): - return True - return False - -def get_part_type(part): - if is_body(part): - return 'body' - elif is_attachment(part): - return 'attachment' - elif is_inline_attachment(part): - return 'inline' - -def get_parts(message): - """returns list of tuples (<part_type>, <formatted_part>), - where <part-type> is one of 'body', 'attachment', 'inline' - and <formatted-part> - will be in the directly usable form: - * if it is 'body' - then it will be unicode text - * for attachment - it will be django's SimpleUploadedFile instance - - There may be multiple 'body' parts as well as others - usually the body is split when there are inline attachments present. - """ - - parts = list() - - if message.body(): - parts.append(('body', message.body())) - - for part in message.walk(): - part_type = get_part_type(part) - if part_type == 'body': - part_content = part.body - elif part_type in ('attachment', 'inline'): - part_content = format_attachment(part) - else: - continue - parts.append((part_type, part_content)) - return parts - -@route('(addr)@(host)', addr = '.+') -@stateless -def ASK(message, host = None, addr = None): - if addr.startswith('reply-'): - return - parts = get_parts(message) - from_address = message.From - subject = message['Subject']#why lamson does not give it normally? - if addr == 'ask': - mail.process_emailed_question(from_address, subject, parts) - else: - if askbot_settings.GROUP_EMAIL_ADDRESSES_ENABLED == False: - return - try: - group_tag = Tag.group_tags.get( - deleted = False, - name__iexact = addr - ) - mail.process_emailed_question( - from_address, subject, parts, tags = [group_tag.name, ] - ) - except Tag.DoesNotExist: - #do nothing because this handler will match all emails - return - except Tag.MultipleObjectsReturned: - return - -@route('reply-(address)@(host)', address='.+') -@stateless -def PROCESS(message, address = None, host = None): - """handler to process the emailed message - and make a post to askbot based on the contents of - the email, including the text body and the file attachments""" - try: - for rule in django_settings.LAMSON_FORWARD: - if re.match(rule['pattern'], message.base['to']): - relay = Relay(host=rule['host'], - port=rule['port'], debug=1) - relay.deliver(message) - return - except AttributeError: - pass - - error = None - try: - reply_address = ReplyAddress.objects.get( - address = address, - allowed_from_email = message.From - ) - parts = get_parts(message) - if reply_address.was_used: - reply_address.edit_post(parts) - else: - reply_address.create_reply(parts) - except ReplyAddress.DoesNotExist: - error = _("You were replying to an email address\ - unknown to the system or you were replying from a different address from the one where you\ - received the notification.") - except Exception, e: - import sys - sys.stderr.write(str(e)) - import traceback - sys.stderr.write(traceback.format_exc()) - - if error is not None: - from askbot.utils import mail - from django.template import Context - from askbot.skins.loaders import get_template - - template = get_template('reply_by_email_error.html') - body_text = template.render(Context({'error':error})) - mail.send_mail( - subject_line = "Error posting your reply", - body_text = body_text, - recipient_list = [message.From], - ) diff --git a/askbot/locale/fr/LC_MESSAGES/django.mo b/askbot/locale/fr/LC_MESSAGES/django.mo Binary files differindex 41834b2b..f8b94d4e 100644 --- a/askbot/locale/fr/LC_MESSAGES/django.mo +++ b/askbot/locale/fr/LC_MESSAGES/django.mo diff --git a/askbot/locale/fr/LC_MESSAGES/django.po b/askbot/locale/fr/LC_MESSAGES/django.po index f33e37f4..327f0e79 100644 --- a/askbot/locale/fr/LC_MESSAGES/django.po +++ b/askbot/locale/fr/LC_MESSAGES/django.po @@ -2,35 +2,35 @@ # Copyright (C) 2009 Gang Chen, 2010 Askbot # This file is distributed under the same license as the CNPROG package. # Evgeny Fadeev <evgeny.fadeev@gmail.com>, 2009. +# Alexandre Lissy <alexandrelissy@free.fr>, 2012. +# msgid "" msgstr "" "Project-Id-Version: Askbot\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-04-18 18:54-0500\n" -"PO-Revision-Date: 2010-08-25 19:15+0100\n" -"Last-Translator: - <->\n" -"Language-Team: FrenchTranslationTeam <toto@toto.com>\n" +"PO-Revision-Date: 2012-06-14 20:15+0100\n" +"Last-Translator: Goofy <goofy@frenchmozilla.org>\n" +"Language-Team: français <>\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n>1);\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" "X-Generator: Translate Toolkit 1.9.0\n" "X-Translated-Using: django-rosetta 0.5.3\n" "X-Poedit-SourceCharset: utf-8\n" #: exceptions.py:13 msgid "Sorry, but anonymous visitors cannot access this function" -msgstr "" -"Désolé, mais les utilisateurs anonymes ne peuvent pas accéder à cette " -"fonction" +msgstr "Désolé, mais les utilisateurs anonymes ne peuvent pas accéder à cette fonction" -#: feed.py:28 feed.py:90 +#: feed.py:28 +#: feed.py:90 msgid " - " msgstr " - " #: feed.py:28 -#, fuzzy msgid "Individual question feed" msgstr "questions sélectionnées individuellement" @@ -39,20 +39,19 @@ msgid "latest questions" msgstr "dernières questions" #: forms.py:74 -#, fuzzy msgid "select country" -msgstr "Supprimer le compte" +msgstr "sélection du pays" #: forms.py:83 msgid "Country" -msgstr "" +msgstr "Pays" #: forms.py:91 -#, fuzzy msgid "Country field is required" -msgstr "ce champ est obligatoire" +msgstr "Le champ pays est obligatoire" -#: forms.py:104 skins/default/templates/widgets/answer_edit_tips.html:45 +#: forms.py:104 +#: skins/default/templates/widgets/answer_edit_tips.html:45 #: skins/default/templates/widgets/answer_edit_tips.html:49 #: skins/default/templates/widgets/question_edit_tips.html:40 #: skins/default/templates/widgets/question_edit_tips.html:45 @@ -61,52 +60,44 @@ msgstr "titre" #: forms.py:105 msgid "please enter a descriptive title for your question" -msgstr "Veuillez saisir un titre descriptif pour votre question." +msgstr "Veuillez saisir un titre suffisamment explicite pour votre question." #: forms.py:113 -#, fuzzy, python-format +#, python-format msgid "title must be > %d character" msgid_plural "title must be > %d characters" -msgstr[0] "le titre doit comporter plus de 10 caractères." -msgstr[1] "le titre doit comporter plus de 10 caractères." +msgstr[0] "le titre doit comporter plus de %d caractères." +msgstr[1] "le titre doit comporter plus de %d caractères." #: forms.py:123 #, python-format msgid "The title is too long, maximum allowed size is %d characters" -msgstr "" +msgstr "Le titre est trop long, taille maximum autorisée de %d caractères" #: forms.py:130 #, python-format msgid "The title is too long, maximum allowed size is %d bytes" -msgstr "" +msgstr "Le titre est trop long, taille maximum autorisée de %d octets" #: forms.py:149 msgid "content" msgstr "contenu" -#: forms.py:185 skins/common/templates/widgets/edit_post.html:21 +#: forms.py:185 +#: skins/common/templates/widgets/edit_post.html:21 #: skins/default/templates/widgets/meta_nav.html:5 msgid "tags" -msgstr "Mots-clés (tags)" +msgstr "Mots-clés" #: forms.py:188 -#, fuzzy, python-format -msgid "" -"Tags are short keywords, with no spaces within. Up to %(max_tags)d tag can " -"be used." -msgid_plural "" -"Tags are short keywords, with no spaces within. Up to %(max_tags)d tags can " -"be used." -msgstr[0] "" -"Les mots-clés sont utilisés en guise de marqueurs (\"tags\" en anglais). Ils " -"doivent être courts, et ne pas comporter d'espaces. Vous pouvez utiliser " -"jusqu'à 5 mots-clés." -msgstr[1] "" -"Les mots-clés sont utilisés en guise de marqueurs (\"tags\" en anglais). Ils " -"doivent être courts, et ne pas comporter d'espaces. Vous pouvez utiliser " -"jusqu'à 5 mots-clés." +#, python-format +msgid "Tags are short keywords, with no spaces within. Up to %(max_tags)d tag can be used." +msgid_plural "Tags are short keywords, with no spaces within. Up to %(max_tags)d tags can be used." +msgstr[0] "Les mots-clés sont utilisés en guise de marqueurs (\"tags\" en anglais). Ils doivent être courts, et ne pas comporter d'espaces. Vous pouvez utiliser jusqu'à %(max_tags)d mots-clés." +msgstr[1] "Les mots-clés sont utilisés en guise de marqueurs (\"tags\" en anglais). Ils doivent être courts, et ne pas comporter d'espaces. Vous pouvez utiliser jusqu'à %(max_tags)d mots-clés." -#: forms.py:222 skins/default/templates/question_retag.html:58 +#: forms.py:222 +#: skins/default/templates/question_retag.html:58 msgid "tags are required" msgstr "Les mots-clés sont obligatoires." @@ -114,80 +105,74 @@ msgstr "Les mots-clés sont obligatoires." #, python-format msgid "please use %(tag_count)d tag or less" msgid_plural "please use %(tag_count)d tags or less" -msgstr[0] "Veuillez utiliser %(tag_count)d mot-clé, ou moins" +msgstr[0] "Veuillez utiliser %(tag_count)d mots-clés, ou moins" msgstr[1] "Veuillez utiliser %(tag_count)d mots-clés, ou moins" #: forms.py:240 #, python-format msgid "At least one of the following tags is required : %(tags)s" -msgstr "" +msgstr "Au moins un des mots-clés suivants est nécessaire : %(tags)s" #: forms.py:249 #, python-format msgid "each tag must be shorter than %(max_chars)d character" msgid_plural "each tag must be shorter than %(max_chars)d characters" -msgstr[0] "Chaque mot-clé doit comporter moins de %(max_chars)d caractère" +msgstr[0] "Chaque mot-clé doit comporter moins de %(max_chars)d caractères" msgstr[1] "Chaque mot-clé doit comporter moins de %(max_chars)d caractères" #: forms.py:258 msgid "In tags, please use letters, numbers and characters \"-+.#\"" -msgstr "" +msgstr "Dans les mots-clés, merci de n'utiliser que des lettres, chiffres et les caractères \"-+.#\"" #: forms.py:294 msgid "community wiki (karma is not awarded & many others can edit wiki post)" -msgstr "" -"wiki communautaire (le karma n'est pas mis à jour & peu de personnes peuvent " -"éditer la question" +msgstr "wiki communautaire (le karma n'est pas mis à jour & peu de personnes peuvent éditer la question" #: forms.py:295 -msgid "" -"if you choose community wiki option, the question and answer do not generate " -"points and name of author will not be shown" -msgstr "" -"Si vous choisissez l'option \"Wiki communautaire\" , questions et réponses " -"ne génèrent pas de points, et le nom de l'auteur ne sera pas affiché." +msgid "if you choose community wiki option, the question and answer do not generate points and name of author will not be shown" +msgstr "Si vous choisissez l'option \"Wiki communautaire\" , questions et réponses ne génèrent pas de points, et le nom de l'auteur ne sera pas affiché." #: forms.py:311 msgid "update summary:" -msgstr "Résumé des modifications:" +msgstr "Résumé des modifications :" #: forms.py:312 -msgid "" -"enter a brief summary of your revision (e.g. fixed spelling, grammar, " -"improved style, this field is optional)" -msgstr "" -"Saisissez un bref résumé à propos de la révision (par exemple : correction " -"orthographique, amélioration du style, ce champ est optionnel)" +msgid "enter a brief summary of your revision (e.g. fixed spelling, grammar, improved style, this field is optional)" +msgstr "Saisissez un bref résumé à propos de la révision (par exemple : correction orthographique, amélioration du style, ce champ est optionnel)" #: forms.py:386 msgid "Enter number of points to add or subtract" msgstr "Saisissez le nombre de points à ajouter ou retirer" -#: forms.py:400 const/__init__.py:253 +#: forms.py:400 +#: const/__init__.py:253 msgid "approved" msgstr "approuvée" -#: forms.py:401 const/__init__.py:254 +#: forms.py:401 +#: const/__init__.py:254 msgid "watched" msgstr "consultée" -#: forms.py:402 const/__init__.py:255 +#: forms.py:402 +#: const/__init__.py:255 msgid "suspended" -msgstr "suspendu" +msgstr "suspendue" -#: forms.py:403 const/__init__.py:256 +#: forms.py:403 +#: const/__init__.py:256 msgid "blocked" msgstr "bloquée" #: forms.py:405 -#, fuzzy msgid "administrator" msgstr "Administrateur du site" # FIXME -#: forms.py:406 const/__init__.py:252 +#: forms.py:406 +#: const/__init__.py:252 msgid "moderator" -msgstr "moderateur" +msgstr "modérateur" #: forms.py:426 msgid "Change status to" @@ -210,18 +195,13 @@ msgid "Cannot change status of another moderator" msgstr "Impossible de changer le statut d'un autre modérateur" #: forms.py:493 -#, fuzzy msgid "Cannot change status to admin" -msgstr "Impossible de changer son propre statut" +msgstr "Impossible de changer le statut en administateur" #: forms.py:499 #, python-format -msgid "" -"If you wish to change %(username)s's status, please make a meaningful " -"selection." -msgstr "" -"Si vous souhaitez changer le statut de %(username)s, effectuez une " -"selection pertinente." +msgid "If you wish to change %(username)s's status, please make a meaningful selection." +msgstr "Si vous souhaitez changer le statut de %(username)s, effectuez une sélection pertinente." #: forms.py:508 msgid "Subject line" @@ -232,26 +212,24 @@ msgid "Message text" msgstr "Corps du message" #: forms.py:530 -#, fuzzy msgid "Your name (optional):" -msgstr "Votre nom:" +msgstr "Votre nom (optionnel) :" #: forms.py:531 -#, fuzzy msgid "Email:" -msgstr "email" +msgstr "Email :" #: forms.py:533 msgid "Your message:" -msgstr "Votre message:" +msgstr "Votre message :" #: forms.py:538 msgid "I don't want to give my email or receive a response:" -msgstr "" +msgstr "Je ne veux pas donner mon email ou recevoir de réponse :" #: forms.py:560 msgid "Please mark \"I dont want to give my mail\" field." -msgstr "" +msgstr "Merci de cocher le champ « je ne veux pas donner mon email »" #: forms.py:599 msgid "ask anonymously" @@ -259,35 +237,27 @@ msgstr "être anonyme" #: forms.py:601 msgid "Check if you do not want to reveal your name when asking this question" -msgstr "" +msgstr "Cochez si vous ne voulez pas afficher votre nom en posant cette question" #: forms.py:624 -msgid "" -"Subject line is expected in the format: [tag1, tag2, tag3,...] question title" -msgstr "" +msgid "Subject line is expected in the format: [tag1, tag2, tag3,...] question title" +msgstr "Le sujet doit suivre le format : [tag1, tag2, tag3, ...]" #: forms.py:769 -msgid "" -"You have asked this question anonymously, if you decide to reveal your " -"identity, please check this box." -msgstr "" +msgid "You have asked this question anonymously, if you decide to reveal your identity, please check this box." +msgstr "Vous avez posé cette question de manière anonyme, si vous voulez révéler votre identité, merci de cocher cette case." #: forms.py:773 msgid "reveal identity" -msgstr "" +msgstr "révéler mon identité" #: forms.py:831 -msgid "" -"Sorry, only owner of the anonymous question can reveal his or her identity, " -"please uncheck the box" -msgstr "" +msgid "Sorry, only owner of the anonymous question can reveal his or her identity, please uncheck the box" +msgstr "Désolé, seul le propriétaire de la question anonyme peut choisir de révéler son identité, merci de décocher la case" #: forms.py:844 -msgid "" -"Sorry, apparently rules have just changed - it is no longer possible to ask " -"anonymously. Please either check the \"reveal identity\" box or reload this " -"page and try editing the question again." -msgstr "" +msgid "Sorry, apparently rules have just changed - it is no longer possible to ask anonymously. Please either check the \"reveal identity\" box or reload this page and try editing the question again." +msgstr "Désolé, apparemment les règles viennent de changer - il n'est plus possible de poser une question anonyme. Merci de cocher la case « révéler mon identité » ou recharger la page et réessayer de modifier la question." #: forms.py:888 msgid "Real name" @@ -299,11 +269,11 @@ msgstr "Site web" #: forms.py:902 msgid "City" -msgstr "" +msgstr "Ville" #: forms.py:911 msgid "Show country" -msgstr "" +msgstr "Afficher le pays" #: forms.py:916 msgid "Date of birth" @@ -311,9 +281,7 @@ msgstr "Date de naissance" #: forms.py:917 msgid "will not be shown, used to calculate age, format: YYYY-MM-DD" -msgstr "" -"ne sera pas affichée; utilisée pour calculer votre âge. Format: AAAA-MM-" -"JJPar exemple: 1980-12-25 pour le 25 décembre 1980" +msgstr "ne sera pas affichée ; utilisée pour calculer votre âge. Format: AAAA-MM-JJPar exemple: 1980-12-25 pour le 25 décembre 1980" #: forms.py:923 msgid "Profile" @@ -323,9 +291,10 @@ msgstr "Profil" msgid "Screen name" msgstr "Pseudo" -#: forms.py:963 forms.py:964 +#: forms.py:963 +#: forms.py:964 msgid "this email has already been registered, please use another one" -msgstr "Cet email a déjà été enregistré; merci d'utiliser une autre adresse" +msgstr "Cet email a déjà été enregistré ; merci d'utiliser une autre adresse" #: forms.py:971 msgid "Choose email tag filter" @@ -357,29 +326,25 @@ msgstr "Veuillez choisir une des options ci-dessus" #: forms.py:1115 msgid "okay, let's try!" -msgstr "D'accord, j'essaye !" +msgstr "D'accord, j'essaie !" #: forms.py:1118 -#, fuzzy, python-format +#, python-format msgid "no %(sitename)s email please, thanks" -msgstr "pas d'emails s'il vous plait, merci" +msgstr "pas d'email %(sitename)s s'il vous plait, merci" -#: lamson_handlers.py:126 tests/reply_by_email_tests.py:49 +#: lamson_handlers.py:126 +#: tests/reply_by_email_tests.py:49 msgid "======= Reply above this line. ====-=-=" -msgstr "" +msgstr "======= Répondre sous cette ligne. ====-=-=" #: lamson_handlers.py:130 -msgid "" -"Your message was malformed. Please make sure to qoute the " -"original notification you received at the end of your reply." -msgstr "" +msgid "Your message was malformed. Please make sure to qoute the original notification you received at the end of your reply." +msgstr "Votre message était mal formaté. Merci de vous assurer de citer la notification que vous avez reçue à la fin de votre réponse." #: lamson_handlers.py:147 -msgid "" -"You were replying to an email address unknown to the system or you " -"were replying from a different address from the one where you " -"received the notification." -msgstr "" +msgid "You were replying to an email address unknown to the system or you were replying from a different address from the one where you received the notification." +msgstr "Vous répondiez à une adresse email inconnue du système ou bien vous répondiez depuis une adresse différente de celle où vous avez reçu la notification." #: urls.py:41 msgid "about/" @@ -395,17 +360,21 @@ msgstr "vieprivee/" #: urls.py:44 msgid "help/" -msgstr "" +msgstr "aide/" -#: urls.py:46 urls.py:51 +#: urls.py:46 +#: urls.py:51 msgid "answers/" msgstr "reponses/" -#: urls.py:46 urls.py:87 urls.py:212 +#: urls.py:46 +#: urls.py:87 +#: urls.py:212 msgid "edit/" msgstr "modifier/" -#: urls.py:51 urls.py:117 +#: urls.py:51 +#: urls.py:117 msgid "revisions/" msgstr "revisions/" @@ -413,8 +382,16 @@ msgstr "revisions/" msgid "questions" msgstr "questions" -#: urls.py:82 urls.py:87 urls.py:92 urls.py:97 urls.py:102 urls.py:107 -#: urls.py:112 urls.py:117 urls.py:123 urls.py:299 +#: urls.py:82 +#: urls.py:87 +#: urls.py:92 +#: urls.py:97 +#: urls.py:102 +#: urls.py:107 +#: urls.py:112 +#: urls.py:117 +#: urls.py:123 +#: urls.py:299 msgid "questions/" msgstr "questions/" @@ -444,7 +421,7 @@ msgstr "voter/" #: urls.py:123 msgid "widgets/" -msgstr "" +msgstr "applets/" #: urls.py:158 msgid "tags/" @@ -452,22 +429,25 @@ msgstr "mots-cles/" #: urls.py:201 msgid "subscribe-for-tags/" -msgstr "" +msgstr "suivi-des-tags/" -#: urls.py:206 urls.py:212 urls.py:218 urls.py:226 +#: urls.py:206 +#: urls.py:212 +#: urls.py:218 +#: urls.py:226 msgid "users/" msgstr "utilisateurs/" #: urls.py:219 -#, fuzzy msgid "subscriptions/" -msgstr "Abonnements aux emails" +msgstr "inscriptions/" #: urls.py:231 msgid "users/update_has_custom_avatar/" -msgstr "" +msgstr "utilisateurs/avatar_personnel/" -#: urls.py:236 urls.py:241 +#: urls.py:236 +#: urls.py:241 msgid "badges/" msgstr "Badges/" @@ -492,19 +472,19 @@ msgstr "retour/" msgid "question/" msgstr "question/" -#: urls.py:312 setup_templates/settings.py:210 +#: urls.py:312 +#: setup_templates/settings.py:210 #: skins/common/templates/authopenid/providers_javascript.html:7 msgid "account/" msgstr "compte/" #: conf/access_control.py:8 -#, fuzzy msgid "Access control settings" -msgstr "Paramétrage de la base de données" +msgstr "Paramétrage du contrôle d'accès" #: conf/access_control.py:17 msgid "Allow only registered user to access the forum" -msgstr "" +msgstr "Autoriser l'accès aux seuls utilisateurs inscrits" #: conf/badges.py:13 msgid "Badge settings" @@ -512,103 +492,99 @@ msgstr "Paramétrage du badge" #: conf/badges.py:23 msgid "Disciplined: minimum upvotes for deleted post" -msgstr "" +msgstr "Discipliné : votes positifs minimum pour les messages supprimés" #: conf/badges.py:32 msgid "Peer Pressure: minimum downvotes for deleted post" -msgstr "" +msgstr "Pression de pairs : votes négatifs minimum pour les messages supprimés" #: conf/badges.py:41 msgid "Teacher: minimum upvotes for the answer" -msgstr "" +msgstr "Enseignant : votes positifs minimum pour la réponse" #: conf/badges.py:50 msgid "Nice Answer: minimum upvotes for the answer" -msgstr "" +msgstr "Réponse sympa : votes positifs minimum pour la réponse" #: conf/badges.py:59 msgid "Good Answer: minimum upvotes for the answer" -msgstr "" +msgstr "Bonne réponse : votes positifs minimum pour la réponse" #: conf/badges.py:68 msgid "Great Answer: minimum upvotes for the answer" -msgstr "" +msgstr "Très bonne réponse : votes positifs minimum pour la réponse" #: conf/badges.py:77 msgid "Nice Question: minimum upvotes for the question" -msgstr "" +msgstr "Question sympa : votes positifs minimum pour la question" #: conf/badges.py:86 msgid "Good Question: minimum upvotes for the question" -msgstr "" +msgstr "Bonne question : votes positifs minimum pour la question" #: conf/badges.py:95 msgid "Great Question: minimum upvotes for the question" -msgstr "" +msgstr "Excellente question : votes positifs minimum pour la question" #: conf/badges.py:104 msgid "Popular Question: minimum views" msgstr "Question populaire : nombre minimum de consultations" #: conf/badges.py:113 -#, fuzzy msgid "Notable Question: minimum views" -msgstr "Question remarquable" +msgstr "Question remarquable : vues minimum" #: conf/badges.py:122 -#, fuzzy msgid "Famous Question: minimum views" -msgstr "Question célèbre" +msgstr "Question célèbre : vues minimum" #: conf/badges.py:131 msgid "Self-Learner: minimum answer upvotes" -msgstr "" +msgstr "Autodidacte : minimum de réponses avec votes positifs" #: conf/badges.py:140 msgid "Civic Duty: minimum votes" -msgstr "" +msgstr "Devoir civique : votes minimum" #: conf/badges.py:149 msgid "Enlightened Duty: minimum upvotes" -msgstr "" +msgstr "Devoir éclairé : votes minimum" #: conf/badges.py:158 msgid "Guru: minimum upvotes" -msgstr "" +msgstr "Gourou : votes minimum" #: conf/badges.py:167 msgid "Necromancer: minimum upvotes" -msgstr "" +msgstr "Nécromancien : votes minimum" #: conf/badges.py:176 msgid "Necromancer: minimum delay in days" -msgstr "" +msgstr "Nécromancien : délai minimum en jours" #: conf/badges.py:185 msgid "Associate Editor: minimum number of edits" -msgstr "" +msgstr "Éditeur associé : nombre d'éditions minimum" #: conf/badges.py:194 -#, fuzzy msgid "Favorite Question: minimum stars" -msgstr "Question favorite" +msgstr "Question favorite : étoiles minimum" #: conf/badges.py:203 -#, fuzzy msgid "Stellar Question: minimum stars" -msgstr "Excellente question" +msgstr "Excellente question : étoiles minimum" #: conf/badges.py:212 msgid "Commentator: minimum comments" -msgstr "" +msgstr "Commentateur : commentaires minimum" #: conf/badges.py:221 msgid "Taxonomist: minimum tag use count" -msgstr "" +msgstr "Taxonomiste : minimum d'utilisation des mots-clés" #: conf/badges.py:230 msgid "Enthusiast: minimum days" -msgstr "" +msgstr "Enthousiaste : jours minimum" #: conf/email.py:15 msgid "Email and email alert settings" @@ -619,145 +595,104 @@ msgid "Prefix for the email subject line" msgstr "Préfixe pour la ligne de sujet de l'email" #: conf/email.py:26 -msgid "" -"This setting takes default from the django settingEMAIL_SUBJECT_PREFIX. A " -"value entered here will overridethe default." -msgstr "" +msgid "This setting takes default from the django settingEMAIL_SUBJECT_PREFIX. A value entered here will overridethe default." +msgstr "Ce paramètre prend par défaut la valeur définie EMAIL_SUBJECT_PREFIX par Django. Toute valeur entrée ici prendra le dessus." #: conf/email.py:38 -#, fuzzy msgid "Enable email alerts" -msgstr "Paramétrage des emails, et des alertes par email." +msgstr "Activer les alertes par email" #: conf/email.py:47 msgid "Maximum number of news entries in an email alert" msgstr "Nombre maximum de nouvelles dans une alerte par email" #: conf/email.py:57 -#, fuzzy msgid "Default notification frequency all questions" -msgstr "" -"Fréquence par défaut pour l'envoi des mails de notification de nouvelles" +msgstr "Fréquence par défaut pour l'envoi des mails de notification pour toutes les questions" #: conf/email.py:59 msgid "Option to define frequency of emailed updates for: all questions." -msgstr "" +msgstr "Option pour définir la fréquence des mises à jour envoyées par email pour toutes les questions." #: conf/email.py:71 -#, fuzzy msgid "Default notification frequency questions asked by the user" -msgstr "" -"Fréquence par défaut pour l'envoi des mails de notification de nouvelles" +msgstr "Fréquence par défaut pour l'envoi des mails de notification quant aux questions posées par l'utilisateur" #: conf/email.py:73 -msgid "" -"Option to define frequency of emailed updates for: Question asked by the " -"user." -msgstr "" +msgid "Option to define frequency of emailed updates for: Question asked by the user." +msgstr "Option pour définir la fréquence des mises à jour envoyées par email pour les questions posées par l'utlisateur." #: conf/email.py:85 -#, fuzzy msgid "Default notification frequency questions answered by the user" -msgstr "" -"Fréquence par défaut pour l'envoi des mails de notification de nouvelles" +msgstr "Fréquence par défaut pour l'envoi des mails de notification quant aux questions où l'utilisateur a répondu" #: conf/email.py:87 -msgid "" -"Option to define frequency of emailed updates for: Question answered by the " -"user." -msgstr "" +msgid "Option to define frequency of emailed updates for: Question answered by the user." +msgstr "Option pour définir la fréquence des mises à jour envoyées par email pour les questions où l'utilisateur a répondu." #: conf/email.py:99 -msgid "" -"Default notification frequency questions individually " -"selected by the user" -msgstr "" +msgid "Default notification frequency questions individually selected by the user" +msgstr "Fréquence par défaut pour l'envoi des mails de notification quant aux questions sélectionnées par l'utilisateur" #: conf/email.py:102 -msgid "" -"Option to define frequency of emailed updates for: Question individually " -"selected by the user." -msgstr "" +msgid "Option to define frequency of emailed updates for: Question individually selected by the user." +msgstr "Option pour définir la fréquence des mises à jour envoyées par email pour les questions sélectionnées par l'utilisateur." #: conf/email.py:114 -msgid "" -"Default notification frequency for mentions and " -"comments" -msgstr "" +msgid "Default notification frequency for mentions and comments" +msgstr "Fréquence par défaut pour l'envoi des mails de notification quant aux commentaires et mentions de l'utilisateur" #: conf/email.py:117 -msgid "" -"Option to define frequency of emailed updates for: Mentions and comments." -msgstr "" +msgid "Option to define frequency of emailed updates for: Mentions and comments." +msgstr "Option pour définir la fréquence des mises à jour envoyées par email pour toutes mentions ou commentaires." #: conf/email.py:128 -#, fuzzy msgid "Send periodic reminders about unanswered questions" -msgstr "Il n'y a aucune question sans réponse" +msgstr "Envoi de rappels réguliers pour les questions sans réponses" #: conf/email.py:130 -msgid "" -"NOTE: in order to use this feature, it is necessary to run the management " -"command \"send_unanswered_question_reminders\" (for example, via a cron job " -"- with an appropriate frequency) " -msgstr "" +msgid "NOTE: in order to use this feature, it is necessary to run the management command \"send_unanswered_question_reminders\" (for example, via a cron job - with an appropriate frequency) " +msgstr "Note : pour pouvoir utiliser cette fonctionnalité, il est nécessaire d'exécuter la commande \"send_unanswered_question_reminders\" (par exemple, via une tâche cron de fréquence adaptée)" #: conf/email.py:143 -#, fuzzy msgid "Days before starting to send reminders about unanswered questions" -msgstr "Il n'y a aucune question sans réponse" +msgstr "Jours avant de commencer à envoyer des rappels pour les questions restées sans réponse" #: conf/email.py:154 -msgid "" -"How often to send unanswered question reminders (in days between the " -"reminders sent)." -msgstr "" +msgid "How often to send unanswered question reminders (in days between the reminders sent)." +msgstr "Fréquence d'envoi de rappels pour les questions sans réponse (en jours entre les envois)." #: conf/email.py:166 -#, fuzzy msgid "Max. number of reminders to send about unanswered questions" -msgstr "Cliquez ici pour voir les questions ayant obtenu le plus de votes" +msgstr "Nombre maximal de rappels à envoyer à propos des questions sans réponses" #: conf/email.py:177 -#, fuzzy msgid "Send periodic reminders to accept the best answer" -msgstr "Il n'y a aucune question sans réponse" +msgstr "Envoyer des rappels réguliers pour accepter la meilleure réponse" #: conf/email.py:179 -msgid "" -"NOTE: in order to use this feature, it is necessary to run the management " -"command \"send_accept_answer_reminders\" (for example, via a cron job - with " -"an appropriate frequency) " -msgstr "" +msgid "NOTE: in order to use this feature, it is necessary to run the management command \"send_accept_answer_reminders\" (for example, via a cron job - with an appropriate frequency) " +msgstr "Note : pour pouvoir utiliser cette fonctionnalité, il est nécessaire d'exécuter la commande \"send_accept_answer_reminders\" (par exemple, via une tâche cron de fréquence adaptée)" #: conf/email.py:192 -#, fuzzy msgid "Days before starting to send reminders to accept an answer" -msgstr "Il n'y a aucune question sans réponse" +msgstr "Jours avant de commencer à envoyer des rappels pour accepter une réponse" #: conf/email.py:203 -msgid "" -"How often to send accept answer reminders (in days between the reminders " -"sent)." -msgstr "" +msgid "How often to send accept answer reminders (in days between the reminders sent)." +msgstr "Fréquence à laquelle envoyer des rappels d'acceptation de réponse (en jours entre les envois)." #: conf/email.py:215 -#, fuzzy msgid "Max. number of reminders to send to accept the best answer" -msgstr "Cliquez ici pour voir les questions ayant obtenu le plus de votes" +msgstr "Nombre maximal de rappels à envoyer pour accepter la meilleure réponse" #: conf/email.py:227 msgid "Require email verification before allowing to post" -msgstr "" -"Nous devons valider votre adresse email avant que vous ne puissiez publier " -"des messages" +msgstr "Nous devons valider votre adresse email avant que vous ne puissiez publier des messages" #: conf/email.py:228 -msgid "" -"Active email verification is done by sending a verification key in email" -msgstr "" -"Nous vérifions que l'adresse email est active en y envoyant un email " -"contenant une clé de vérification." +msgid "Active email verification is done by sending a verification key in email" +msgstr "Nous vérifions que l'adresse email est active en y envoyant un email contenant une clé de vérification." #: conf/email.py:237 msgid "Allow only one account per email address" @@ -769,99 +704,65 @@ msgstr "Faux email pour utilisateur anonyme" #: conf/email.py:247 msgid "Use this setting to control gravatar for email-less user" -msgstr "" -"Utilisez ce paramétrage pour contrôler Gravatar (pour les utilisateurs sans " -"adresse email)" +msgstr "Utilisez ce paramétrage pour contrôler Gravatar (pour les utilisateurs sans adresse email)" #: conf/email.py:256 -#, fuzzy msgid "Allow posting questions by email" -msgstr "" -"<span class=\"strong big\">Formulez votre question à l'aide du formulaire ci-" -"dessous (un court titre résumant la question, puis la question à proprement " -"parler, aussi détaillée que vous le souhaitez...)</span>. A l'étape " -"suivante, vous devrez saisir votre email et votre nom (ou un pseudo si vous " -"souhaitez rester anonyme...). Ces éléments sont nécessaires pour bénéficier " -"des fonctionnalités de notre module de questions/réponses, qui repose sur un " -"principe communautaire." +msgstr "Autoriser l'envoi de questions par email" #: conf/email.py:258 -msgid "" -"Before enabling this setting - please fill out IMAP settings in the settings." -"py file" -msgstr "" +msgid "Before enabling this setting - please fill out IMAP settings in the settings.py file" +msgstr "Avant d'activer cette fonctionnalité - merci de configurer les paramètres IMAP dans le fichier settings.py" #: conf/email.py:269 msgid "Replace space in emailed tags with dash" -msgstr "" +msgstr "Remplacer les espaces dans les tags envoyés par email avec des tirets" #: conf/email.py:271 -msgid "" -"This setting applies to tags written in the subject line of questions asked " -"by email" -msgstr "" +msgid "This setting applies to tags written in the subject line of questions asked by email" +msgstr "Ce paramètre s'applique aux tags indiqués dans le sujet des questions envoyées par email" #: conf/email.py:284 -#, fuzzy msgid "Enable posting answers and comments by email" -msgstr "" -"<span class=\"strong big\">Formulez votre question à l'aide du formulaire ci-" -"dessous (un court titre résumant la question, puis la question à proprement " -"parler, aussi détaillée que vous le souhaitez...)</span>. A l'étape " -"suivante, vous devrez saisir votre email et votre nom (ou un pseudo si vous " -"souhaitez rester anonyme...). Ces éléments sont nécessaires pour bénéficier " -"des fonctionnalités de notre module de questions/réponses, qui repose sur un " -"principe communautaire." +msgstr "Activer l'envoi de réponses et commentaires par email" #: conf/email.py:287 msgid "To enable this feature make sure lamson is running" -msgstr "" +msgstr "Pour activer cette fonctionnalité, assurez-vous que lamson fonctionne" #: conf/email.py:298 msgid "Reply by email hostname" -msgstr "" +msgstr "Répondre par nom d'hôte d'email" #: conf/email.py:311 -msgid "" -"Email replies having fewer words than this number will be posted as comments " -"instead of answers" -msgstr "" +msgid "Email replies having fewer words than this number will be posted as comments instead of answers" +msgstr "Les réponses par email avec moins de mots que cette limite seront traitées comme des commentaires au lieu de réponses" #: conf/external_keys.py:11 msgid "Keys for external services" -msgstr "" +msgstr "Clefs pour les services externes" #: conf/external_keys.py:19 msgid "Google site verification key" msgstr "Clé de vérification de site Google" #: conf/external_keys.py:21 -#, fuzzy, python-format -msgid "" -"This key helps google index your site please obtain is at <a href=\"%(url)s?" -"hl=%(lang)s\">google webmasters tools site</a>" -msgstr "" -"Cette clé aide Google à indexer votre site; vous pouvez en obtenir une pour " -"votre site à <a href=\"%(google_webmasters_tools_url)s\">\"Google - Outils " -"pour les webmasters\"</a>" +#, python-format +msgid "This key helps google index your site please obtain is at <a href=\"%(url)s?hl=%(lang)s\">google webmasters tools site</a>" +msgstr "Cette clé aide Google à indexer votre site ; vous pouvez en obtenir une pour votre site à <a href=\"%(url)s?hl=%(lang)s\">Google - Outils pour les webmasters</a>" #: conf/external_keys.py:36 msgid "Google Analytics key" msgstr "Clé Google Analytics" #: conf/external_keys.py:38 -#, fuzzy, python-format -msgid "" -"Obtain is at <a href=\"%(url)s\">Google Analytics</a> site, if you wish to " -"use Google Analytics to monitor your site" -msgstr "" -"Vous pouvez en obtenir une sur le site <a href=\"%(ga_site)s\">Google " -"Analytics</a>, si vous souhaitez utiliser Google Analytics pour mesurer " -"l'audience de votre site." +#, python-format +msgid "Obtain is at <a href=\"%(url)s\">Google Analytics</a> site, if you wish to use Google Analytics to monitor your site" +msgstr "Vous pouvez en obtenir une sur le site <a href=\"%(url)s\">Google Analytics</a>, si vous souhaitez utiliser Google Analytics pour mesurer l'audience de votre site." #: conf/external_keys.py:51 msgid "Enable recaptcha (keys below are required)" -msgstr "" +msgstr "Activer ReCAPTCHA (les clefs ci-dessous sont nécessaires)" #: conf/external_keys.py:62 msgid "Recaptcha public key" @@ -872,33 +773,18 @@ msgid "Recaptcha private key" msgstr "Clé privée Recaptcha" #: conf/external_keys.py:72 -#, fuzzy, python-format -msgid "" -"Recaptcha is a tool that helps distinguish real people from annoying spam " -"robots. Please get this and a public key at the <a href=\"%(url)s\">%(url)s</" -"a>" -msgstr "" -"Recaptcha est un outil permettant de vérifier qu'une personne qui se " -"connecte à votre site est bien un humain (et non pas un robot spammeur). " -"L'utilisation de cet outil permet notamment d'empêcher des robots de se " -"connecter à votre site pour y publier automatiquement et massivement des " -"messages publicitaires." +#, python-format +msgid "Recaptcha is a tool that helps distinguish real people from annoying spam robots. Please get this and a public key at the <a href=\"%(url)s\">%(url)s</a>" +msgstr "Recaptcha est un outil permettant de vérifier qu'une personne qui se connecte à votre site est bien un humain (et non pas un robot spammeur). Merci de l'obtenir avec une clef publique sur <a href=\"%(url)s\">%(url)s</a>" #: conf/external_keys.py:84 msgid "Facebook public API key" msgstr "Clé d'API publique FACEBOOK" #: conf/external_keys.py:86 -#, fuzzy, python-format -msgid "" -"Facebook API key and Facebook secret allow to use Facebook Connect login " -"method at your site. Please obtain these keys at <a href=\"%(url)s" -"\">facebook create app</a> site" -msgstr "" -"La clé d'API FACEBOOK et le secret FACEBOOK vous permettent de proposer aux " -"utilisateurs de votre de se connecter en utilisant leur compte facebook. " -"Vous pouvez obtenir ces clés sur le site <a href=\"http://www.facebook.com/" -"developers/createapp.php\">facebook create app</a>" +#, python-format +msgid "Facebook API key and Facebook secret allow to use Facebook Connect login method at your site. Please obtain these keys at <a href=\"%(url)s\">facebook create app</a> site" +msgstr "La clé d'API Facebook et le secret Facebook vous permettent de proposer aux utilisateurs de votre site de se connecter en utilisant Facebook Connect. Vous pouvez obtenir ces clés sur le site <a href=\"%(url)s\">de création d'application Facebook</a>" #: conf/external_keys.py:99 msgid "Facebook secret key" @@ -906,47 +792,42 @@ msgstr "Clé secrète FACEBOOK" #: conf/external_keys.py:107 msgid "Twitter consumer key" -msgstr "" +msgstr "Clef de consommateur Twitter" #: conf/external_keys.py:109 #, python-format -msgid "" -"Please register your forum at <a href=\"%(url)s\">twitter applications site</" -"a>" -msgstr "" +msgid "Please register your forum at <a href=\"%(url)s\">twitter applications site</a>" +msgstr "Merci d'enregistrer votre forum sur <a href=\"%(url)s\">le site d'applications twitter</a>" #: conf/external_keys.py:120 msgid "Twitter consumer secret" -msgstr "" +msgstr "Secret de consommateur Twitter" #: conf/external_keys.py:128 msgid "LinkedIn consumer key" -msgstr "" +msgstr "Clef de consommateur LinkedIn" #: conf/external_keys.py:130 #, python-format -msgid "" -"Please register your forum at <a href=\"%(url)s\">LinkedIn developer site</a>" -msgstr "" +msgid "Please register your forum at <a href=\"%(url)s\">LinkedIn developer site</a>" +msgstr "Merci d'enregistrer votre forum sur <a href=\"%(url)s\">le site développeur LinkedIn</a>" #: conf/external_keys.py:141 msgid "LinkedIn consumer secret" -msgstr "" +msgstr "Secret de consommateur LinkedIn" #: conf/external_keys.py:149 msgid "ident.ca consumer key" -msgstr "" +msgstr "Clef consommateur ident.ca" #: conf/external_keys.py:151 #, python-format -msgid "" -"Please register your forum at <a href=\"%(url)s\">Identi.ca applications " -"site</a>" -msgstr "" +msgid "Please register your forum at <a href=\"%(url)s\">Identi.ca applications site</a>" +msgstr "Merci d'enregistrer votre forum sur <a href=\"%(url)s\">le site des applications Identi.ca</a>" #: conf/external_keys.py:162 msgid "ident.ca consumer secret" -msgstr "" +msgstr "Secret de consommateur identi.ca" #: conf/flatpages.py:11 msgid "Flatpages - about, privacy policy, etc." @@ -954,222 +835,166 @@ msgstr "Pages \"à propos\", \"vie privée\", etc." #: conf/flatpages.py:19 msgid "Text of the Q&A forum About page (html format)" -msgstr "" -"Insérez ici le texte de la page \"à propos\" du forum de Questions/Réponses" +msgstr "Insérez ici le texte de la page \"à propos\" du forum de Questions/Réponses" #: conf/flatpages.py:22 -msgid "" -"Save, then <a href=\"http://validator.w3.org/\">use HTML validator</a> on " -"the \"about\" page to check your input." -msgstr "" -"Enregistrez, puis utilisez <a href=\"http://validator.w3.org/\">un " -"validateur HTML</a> sur la page \"à propos\" pour vérifier ce que vous avez " -"saisi." +msgid "Save, then <a href=\"http://validator.w3.org/\">use HTML validator</a> on the \"about\" page to check your input." +msgstr "Enregistrez, puis utilisez <a href=\"http://validator.w3.org/\">un validateur HTML</a> sur la page \"à propos\" pour vérifier ce que vous avez saisi." #: conf/flatpages.py:32 -#, fuzzy msgid "Text of the Q&A forum FAQ page (html format)" -msgstr "" -"Insérez ici le texte de la page \"à propos\" du forum de Questions/Réponses" +msgstr "Insérez ici le texte de la page Foire Aux Questions du forum de Questions/Réponses (format HTML)" #: conf/flatpages.py:35 -#, fuzzy -msgid "" -"Save, then <a href=\"http://validator.w3.org/\">use HTML validator</a> on " -"the \"faq\" page to check your input." -msgstr "" -"Enregistrez, puis utilisez <a href=\"http://validator.w3.org/\">un " -"validateur HTML</a> sur la page \"à propos\" pour vérifier ce que vous avez " -"saisi." +msgid "Save, then <a href=\"http://validator.w3.org/\">use HTML validator</a> on the \"faq\" page to check your input." +msgstr "Enregistrez, puis utilisez <a href=\"http://validator.w3.org/\">un validateur HTML</a> sur la page \"Foire Aux Questions\" pour vérifier ce que vous avez saisi." #: conf/flatpages.py:46 msgid "Text of the Q&A forum Privacy Policy (html format)" -msgstr "" -"Insérez ici le texte de la page \"vie privée\" du forum de Questions/Réponses" +msgstr "Insérez ici le texte de la page \"vie privée\" du forum de Questions/Réponses" #: conf/flatpages.py:49 -msgid "" -"Save, then <a href=\"http://validator.w3.org/\">use HTML validator</a> on " -"the \"privacy\" page to check your input." -msgstr "" -"Enregistrez, puis utilisez <a href=\"http://validator.w3.org/\">un " -"validateur HTML</a> sur la page \"vie privée\" pour vérifier ce que vous " -"avez saisi." +msgid "Save, then <a href=\"http://validator.w3.org/\">use HTML validator</a> on the \"privacy\" page to check your input." +msgstr "Enregistrez, puis utilisez <a href=\"http://validator.w3.org/\">un validateur HTML</a> sur la page \"vie privée\" pour vérifier ce que vous avez saisi." #: conf/forum_data_rules.py:12 -#, fuzzy msgid "Data entry and display rules" msgstr "Paramétrage de l'affichage et de la saisie de données" #: conf/forum_data_rules.py:21 msgid "Enable embedding videos. " -msgstr "" +msgstr "Activer les vidéos embarquées." #: conf/forum_data_rules.py:23 #, python-format msgid "<em>Note: please read <a href=\"%(url)s\">read this</a> first.</em>" -msgstr "" +msgstr "<em>Note : merci de <a href=\"%(url)s\">lire ceci</a> en premier.</em>" #: conf/forum_data_rules.py:33 msgid "Check to enable community wiki feature" -msgstr "" -"Cochez cette case pour activer la fonctionnalité \"wiki communautaire\"" +msgstr "Cochez cette case pour activer la fonctionnalité \"wiki communautaire\"" #: conf/forum_data_rules.py:42 msgid "Allow asking questions anonymously" -msgstr "" +msgstr "Autoriser les questions anonymes" #: conf/forum_data_rules.py:44 -msgid "" -"Users do not accrue reputation for anonymous questions and their identity is " -"not revealed until they change their mind" -msgstr "" +msgid "Users do not accrue reputation for anonymous questions and their identity is not revealed until they change their mind" +msgstr "Les utilisateurs n'améliorent pas leur réputation avec les questions anonymes, et leur identité reste cachée jusqu'à ce qu'ils changent d'avis" #: conf/forum_data_rules.py:56 -#, fuzzy msgid "Allow posting before logging in" -msgstr "" -"<span class=\"strong big\">Formulez votre question à l'aide du formulaire ci-" -"dessous (un court titre résumant la question, puis la question à proprement " -"parler, aussi détaillée que vous le souhaitez...)</span>. A l'étape " -"suivante, vous devrez saisir votre email et votre nom (ou un pseudo si vous " -"souhaitez rester anonyme...). Ces éléments sont nécessaires pour bénéficier " -"des fonctionnalités de notre module de questions/réponses, qui repose sur un " -"principe communautaire." +msgstr "Autoriser l'envoi avant la connexion" #: conf/forum_data_rules.py:58 -msgid "" -"Check if you want to allow users start posting questions or answers before " -"logging in. Enabling this may require adjustments in the user login system " -"to check for pending posts every time the user logs in. The builtin Askbot " -"login system supports this feature." -msgstr "" +msgid "Check if you want to allow users start posting questions or answers before logging in. Enabling this may require adjustments in the user login system to check for pending posts every time the user logs in. The builtin Askbot login system supports this feature." +msgstr "Cochez si vous voulez autoriser vos utilisateurs à poser des questions ou réponses avant de se connecter. Activer ceci peut nécessiter des modifications dans le système de connexion pour vérifier les messages en attente à chaque connexion des utilisateurs. Le système de connexion intégré à Askbot supporte cette fonctionnalité." #: conf/forum_data_rules.py:73 -#, fuzzy msgid "Allow swapping answer with question" -msgstr "Répondre à cette question" +msgstr "Autoriser le remplacement d'une réponse par une question" #: conf/forum_data_rules.py:75 -msgid "" -"This setting will help import data from other forums such as zendesk, when " -"automatic data import fails to detect the original question correctly." -msgstr "" +msgid "This setting will help import data from other forums such as zendesk, when automatic data import fails to detect the original question correctly." +msgstr "Ce paramètre aide l'import de données depuis d'autres forums tels que zendesk lorsque l'import automatique échoue à la détection de la question d'origine." #: conf/forum_data_rules.py:87 msgid "Maximum length of tag (number of characters)" msgstr "Taille maximale d'un mot-clé (tag), en nombre de caractères" #: conf/forum_data_rules.py:96 -#, fuzzy msgid "Minimum length of title (number of characters)" -msgstr "Taille maximale d'un mot-clé (tag), en nombre de caractères" +msgstr "Taille minimale du titre, en nombre de caractères" #: conf/forum_data_rules.py:106 -#, fuzzy msgid "Minimum length of question body (number of characters)" -msgstr "Taille maximale d'un mot-clé (tag), en nombre de caractères" +msgstr "Taille minimale du corps de la question, en nombre de caractères" #: conf/forum_data_rules.py:117 -#, fuzzy msgid "Minimum length of answer body (number of characters)" -msgstr "Taille maximale d'un mot-clé (tag), en nombre de caractères" +msgstr "Taille minimale du corps de la réponse, en nombre de caractères" #: conf/forum_data_rules.py:126 -#, fuzzy msgid "Are tags required?" -msgstr "Les mots-clés sont obligatoires." +msgstr "Les mots-clés sont-ils obligatoires ?" # FXME ou "offensive" ? #: conf/forum_data_rules.py:135 -#, fuzzy msgid "Mandatory tags" -msgstr "Mots-clés" +msgstr "Mots-clés obligatoires" #: conf/forum_data_rules.py:138 -msgid "" -"At least one of these tags will be required for any new or newly edited " -"question. A mandatory tag may be wildcard, if the wildcard tags are active." -msgstr "" +msgid "At least one of these tags will be required for any new or newly edited question. A mandatory tag may be wildcard, if the wildcard tags are active." +msgstr "Au moins un de ces mots-clés sera nécessaire pour toute nouvelle question ou modification. Un mot-clé obligatoire peut-être un mot-clé associé s'ils sont activés." #: conf/forum_data_rules.py:150 msgid "Force lowercase the tags" -msgstr "" +msgstr "Forcer la casse minuscule des mots-clés." #: conf/forum_data_rules.py:152 -msgid "" -"Attention: after checking this, please back up the database, and run a " -"management command: <code>python manage.py fix_question_tags</code> to " -"globally rename the tags" -msgstr "" +msgid "Attention: after checking this, please back up the database, and run a management command: <code>python manage.py fix_question_tags</code> to globally rename the tags" +msgstr "Attention : après avoir activé ceci, merci de faire une sauvegarde de la base de données, puis exécuter la commande <code>python manage.py fix_question_tags</code> pour renommer tous les mots-clés." #: conf/forum_data_rules.py:166 msgid "Format of tag list" -msgstr "" +msgstr "Format de la liste des mots-clés" #: conf/forum_data_rules.py:168 -msgid "" -"Select the format to show tags in, either as a simple list, or as a tag cloud" -msgstr "" +msgid "Select the format to show tags in, either as a simple list, or as a tag cloud" +msgstr "Sélectionnez le format pour afficher les mots-clés, soit une liste soit un nuage de mots-clés" #: conf/forum_data_rules.py:180 -#, fuzzy msgid "Use wildcard tags" -msgstr "Tags associés" +msgstr "Utiliser les mots-clés associés" #: conf/forum_data_rules.py:182 -msgid "" -"Wildcard tags can be used to follow or ignore many tags at once, a valid " -"wildcard tag has a single wildcard at the very end" -msgstr "" +msgid "Wildcard tags can be used to follow or ignore many tags at once, a valid wildcard tag has a single wildcard at the very end" +msgstr "Les mots-clés associés peuvent être utilisés pour suivre ou ignorer plusieurs mots-clés en une fois, un mot-clé associé valide a un seul mot-clé associé à la toute fin" #: conf/forum_data_rules.py:195 msgid "Default max number of comments to display under posts" -msgstr "" +msgstr "Nombre maximum par défaut de commentaires à afficher sous les messages" #: conf/forum_data_rules.py:206 #, python-format msgid "Maximum comment length, must be < %(max_len)s" -msgstr "" +msgstr "Taille maximale des commentaires, doit être < %(max_len)s" #: conf/forum_data_rules.py:216 msgid "Limit time to edit comments" -msgstr "" +msgstr "Limite de temps pour modifier les commentaires" #: conf/forum_data_rules.py:218 msgid "If unchecked, there will be no time limit to edit the comments" -msgstr "" +msgstr "Si non coché, il n'y aura pas de limite pour modifier les commentaires" #: conf/forum_data_rules.py:229 msgid "Minutes allowed to edit a comment" -msgstr "" +msgstr "Minutes autorisées pour modifier un commentaire" #: conf/forum_data_rules.py:230 msgid "To enable this setting, check the previous one" -msgstr "" +msgstr "Pour activer ce paramètre, cochez le précédent" #: conf/forum_data_rules.py:239 msgid "Save comment by pressing <Enter> key" -msgstr "" +msgstr "Sauvegarder le commentaire en appuyant sur la touche <Entrée>" #: conf/forum_data_rules.py:248 msgid "Minimum length of search term for Ajax search" -msgstr "" +msgstr "Taille minimale pour un terme de recherche avec Ajax" #: conf/forum_data_rules.py:249 msgid "Must match the corresponding database backend setting" -msgstr "" +msgstr "Doit correspondre au paramètre de la base de données" #: conf/forum_data_rules.py:258 msgid "Do not make text query sticky in search" -msgstr "" +msgstr "Ne pas épingler les recherches textuelles" #: conf/forum_data_rules.py:260 -msgid "" -"Check to disable the \"sticky\" behavior of the search query. This may be " -"useful if you want to move the search bar away from the default position or " -"do not like the default sticky behavior of the text search query." -msgstr "" +msgid "Check to disable the \"sticky\" behavior of the search query. This may be useful if you want to move the search bar away from the default position or do not like the default sticky behavior of the text search query." +msgstr "Cocher pour désactiver le comportement « épinglé » de la zone de recherche. Cela peut-être utile si vous voulez déplacer la barre de recherche de sa position par défaut ou si vous n'aimez pas ce comportement." #: conf/forum_data_rules.py:273 msgid "Maximum number of tags per question" @@ -1185,228 +1010,190 @@ msgstr "Que signifie \"questions sans réponses\" ?" #: conf/ldap.py:9 msgid "LDAP login configuration" -msgstr "" +msgstr "Configuration de l'authentification LDAP" #: conf/ldap.py:17 msgid "Use LDAP authentication for the password login" -msgstr "" +msgstr "Utiliser l'authentification LDAP pour la connexion par mot de passe" #: conf/ldap.py:26 msgid "LDAP URL" -msgstr "" +msgstr "Adresse LDAP" #: conf/ldap.py:35 msgid "LDAP BASE DN" -msgstr "" +msgstr "DN de base LDAP" #: conf/ldap.py:43 msgid "LDAP Search Scope" -msgstr "" +msgstr "Niveau de recherche LDAP" #: conf/ldap.py:52 msgid "LDAP Server USERID field name" -msgstr "" +msgstr "Champ LDAP pour USERID" #: conf/ldap.py:61 msgid "LDAP Server \"Common Name\" field name" -msgstr "" +msgstr "Champ LDAP pour le nom commun (CommonName)" #: conf/ldap.py:70 msgid "LDAP Server EMAIL field name" -msgstr "" +msgstr "Champ LDAP pour EMAIL" #: conf/leading_sidebar.py:12 -#, fuzzy msgid "Common left sidebar" -msgstr "Tags de la question" +msgstr "Barre latérale de gauche" #: conf/leading_sidebar.py:20 -#, fuzzy msgid "Enable left sidebar" -msgstr "Profil utilisateur" +msgstr "Activer la barre latérale de gauche" #: conf/leading_sidebar.py:29 msgid "HTML for the left sidebar" -msgstr "" +msgstr "Code HTML pour la barre latérale de gauche" #: conf/leading_sidebar.py:32 -msgid "" -"Use this area to enter content at the LEFT sidebarin HTML format. When " -"using this option, please use the HTML validation service to make sure that " -"your input is valid and works well in all browsers." -msgstr "" +msgid "Use this area to enter content at the LEFT sidebarin HTML format. When using this option, please use the HTML validation service to make sure that your input is valid and works well in all browsers." +msgstr "Utilisez cette zone pour entrer le contenu de la barre latérale de gauche au format HTML. Lors de l'utilisation de cette option, utilisez un outil de validation du code HTML pour vous assurer qu'il soit correct." #: conf/license.py:13 msgid "Content License" -msgstr "" +msgstr "Licence du contenu" #: conf/license.py:21 msgid "Show license clause in the site footer" -msgstr "" +msgstr "Afficher la clause de la licence dans le pied de page" #: conf/license.py:30 msgid "Short name for the license" -msgstr "" +msgstr "Nom court de la licence" #: conf/license.py:39 msgid "Full name of the license" -msgstr "" +msgstr "Nom complet de la licence" #: conf/license.py:40 msgid "Creative Commons Attribution Share Alike 3.0" -msgstr "" +msgstr "Creative Commons Attribution Share Alike 3.0" #: conf/license.py:48 msgid "Add link to the license page" -msgstr "" +msgstr "Ajouter un lien vers la page de la licence" #: conf/license.py:57 -#, fuzzy msgid "License homepage" -msgstr "Retour à l'accueil" +msgstr "Page d'accueil de la licence" #: conf/license.py:59 msgid "URL of the official page with all the license legal clauses" -msgstr "" +msgstr "Adresse de la page officielle de la licence avec toutes les clauses légales" #: conf/license.py:69 -#, fuzzy msgid "Use license logo" -msgstr "Logo du site de Questions/Réponses" +msgstr "Utiliser le logo de la licence" #: conf/license.py:78 msgid "License logo image" -msgstr "" +msgstr "Image logo de la licence" #: conf/login_providers.py:13 msgid "Login provider setings" -msgstr "" +msgstr "Paramètre du fournisseur de connexion" #: conf/login_providers.py:22 -msgid "" -"Show alternative login provider buttons on the password \"Sign Up\" page" -msgstr "" +msgid "Show alternative login provider buttons on the password \"Sign Up\" page" +msgstr "Afficher les fournisseurs de connexion alternatifs sur la page « Connexion »" #: conf/login_providers.py:31 msgid "Always display local login form and hide \"Askbot\" button." -msgstr "" +msgstr "Toujours afficher le formulaire de connexion locale et cacher le bouton « Askbot »" #: conf/login_providers.py:40 msgid "Activate to allow login with self-hosted wordpress site" -msgstr "" +msgstr "Activer pour autoriser la connexion avec un site WordPress auto-hébergé" #: conf/login_providers.py:41 -msgid "" -"to activate this feature you must fill out the wordpress xml-rpc setting " -"bellow" -msgstr "" +msgid "to activate this feature you must fill out the wordpress xml-rpc setting bellow" +msgstr "pour activer cette fonctionnalité vous devez configurer les paramètres XML-RPC de WordPress ci-dessous" #: conf/login_providers.py:50 -msgid "" -"Fill it with the wordpress url to the xml-rpc, normally http://mysite.com/" -"xmlrpc.php" -msgstr "" +msgid "Fill it with the wordpress url to the xml-rpc, normally http://mysite.com/xmlrpc.php" +msgstr "Indiquez l'adresse XML-RPM de WordPress, par exemple http://example.com/xmlrpc.php" #: conf/login_providers.py:51 -msgid "" -"To enable, go to Settings->Writing->Remote Publishing and check the box for " -"XML-RPC" -msgstr "" +msgid "To enable, go to Settings->Writing->Remote Publishing and check the box for XML-RPC" +msgstr "Pour activer, allez dans Paramères -> Publication -> Publication distante et cochez la case XML-RPC" #: conf/login_providers.py:60 msgid "Upload your icon" -msgstr "" +msgstr "Charger votre icône" #: conf/login_providers.py:90 #, python-format msgid "Activate %(provider)s login" -msgstr "" +msgstr "Activer la connexion %(provider)s" #: conf/login_providers.py:95 #, python-format -msgid "" -"Note: to really enable %(provider)s login some additional parameters will " -"need to be set in the \"External keys\" section" -msgstr "" +msgid "Note: to really enable %(provider)s login some additional parameters will need to be set in the \"External keys\" section" +msgstr "Note : pour réellement activer la connexion %(provider)s des paramètres supplémentaires doivent être indiqués dans la partie « Clefs externe »" #: conf/markup.py:15 msgid "Markup in posts" -msgstr "" +msgstr "Balisage dans les messages" #: conf/markup.py:41 msgid "Enable code-friendly Markdown" -msgstr "" +msgstr "Activer le support Markdown" #: conf/markup.py:43 -msgid "" -"If checked, underscore characters will not trigger italic or bold formatting " -"- bold and italic text can still be marked up with asterisks. Note that " -"\"MathJax support\" implicitly turns this feature on, because underscores " -"are heavily used in LaTeX input." -msgstr "" +msgid "If checked, underscore characters will not trigger italic or bold formatting - bold and italic text can still be marked up with asterisks. Note that \"MathJax support\" implicitly turns this feature on, because underscores are heavily used in LaTeX input." +msgstr "Si coché, les soulignements n'activeront pas le formatage italique ou gras - ils peuvent toujours être activés avec des astérisques. Merci de noter que « le support MathJax » active cette option implicitement, car les soulignements sont beaucoup utilisés avec LaTeX." #: conf/markup.py:58 msgid "Mathjax support (rendering of LaTeX)" -msgstr "" +msgstr "Support MathJax (rendu LaTeX)" #: conf/markup.py:60 #, python-format -msgid "" -"If you enable this feature, <a href=\"%(url)s\">mathjax</a> must be " -"installed on your server in its own directory." -msgstr "" +msgid "If you enable this feature, <a href=\"%(url)s\">mathjax</a> must be installed on your server in its own directory." +msgstr "Si vous activez cette fonctionnalité, <a href=\"%(url)s\">mathjax</a> doit être installé sur le serveur dans son propre répertoire." #: conf/markup.py:74 msgid "Base url of MathJax deployment" -msgstr "" +msgstr "Adresse de base de l'installation de MathJax" #: conf/markup.py:76 -msgid "" -"Note - <strong>MathJax is not included with askbot</strong> - you should " -"deploy it yourself, preferably at a separate domain and enter url pointing " -"to the \"mathjax\" directory (for example: http://mysite.com/mathjax)" -msgstr "" +msgid "Note - <strong>MathJax is not included with askbot</strong> - you should deploy it yourself, preferably at a separate domain and enter url pointing to the \"mathjax\" directory (for example: http://mysite.com/mathjax)" +msgstr "Note - <strong>MathJax n'est pas inclus avec askbot</strong> - vous devriez le déployer vous-même, de préférence sur un sous-domaine et indiquer l'adresse correspondant au répertoire mathjax (par exemple : http://example.com/mathjax)" #: conf/markup.py:91 msgid "Enable autolinking with specific patterns" -msgstr "" +msgstr "Activer les liens automatiques avec des motifs particuliers" #: conf/markup.py:93 -msgid "" -"If you enable this feature, the application will be able to detect patterns " -"and auto link to URLs" -msgstr "" +msgid "If you enable this feature, the application will be able to detect patterns and auto link to URLs" +msgstr "Si vous activez cette fonctionnalité, l'application sera capable de détecter et créer automatiquement les liens" #: conf/markup.py:106 msgid "Regexes to detect the link patterns" -msgstr "" +msgstr "Expressions régulières pour les motifs de lien" #: conf/markup.py:108 -msgid "" -"Enter valid regular expressions for the patters, one per line. For example " -"to detect a bug pattern like #bug123, use the following regex: #bug(\\d+). " -"The numbers captured by the pattern in the parentheses will be transferred " -"to the link url template. Please look up more information about regular " -"expressions elsewhere." -msgstr "" +msgid "Enter valid regular expressions for the patters, one per line. For example to detect a bug pattern like #bug123, use the following regex: #bug(\\d+). The numbers captured by the pattern in the parentheses will be transferred to the link url template. Please look up more information about regular expressions elsewhere." +msgstr "Indiquez une expression régulière valide pour les motifs, une par ligne. Par exemple pour détecter un motif de bug tel que #bug123, utilisez l'expression régulière : #bug(\\d+). Les nombres capturés par les parenthèses seront utilisés dans le modèle de lien. Merci de chercher des informations sur les expressions régulières ailleurs." #: conf/markup.py:127 msgid "URLs for autolinking" -msgstr "" +msgstr "URLs pour les liens automatiques" #: conf/markup.py:129 -msgid "" -"Here, please enter url templates for the patterns entered in the previous " -"setting, also one entry per line. <strong>Make sure that number of lines in " -"this setting and the previous one are the same</strong> For example template " -"https://bugzilla.redhat.com/show_bug.cgi?id=\\1 together with the pattern " -"shown above and the entry in the post #123 will produce link to the bug 123 " -"in the redhat bug tracker." -msgstr "" +msgid "Here, please enter url templates for the patterns entered in the previous setting, also one entry per line. <strong>Make sure that number of lines in this setting and the previous one are the same</strong> For example template https://bugzilla.redhat.com/show_bug.cgi?id=\\1 together with the pattern shown above and the entry in the post #123 will produce link to the bug 123 in the redhat bug tracker." +msgstr "Ici, indiquez les modèles pour les motifs entrés précédemment, également un par ligne. <strong>Assurez-vous que les deux ont bien autant de lignes</strong>. Par exemple, le modèle https://bugzilla.redhat.com/show_bug.cgi?id=\\1 avec le motif précédent et un message contenant #123 générera un lien vers le bug 123 dans l'outil de suivi de bugs de RedHat." #: conf/minimum_reputation.py:12 msgid "Karma thresholds" -msgstr "" +msgstr "Seuils de karma" #: conf/minimum_reputation.py:22 msgid "Upvote" @@ -1417,14 +1204,12 @@ msgid "Downvote" msgstr "vote négatif" #: conf/minimum_reputation.py:40 -#, fuzzy msgid "Answer own question immediately" -msgstr "Répondre à votre propre question" +msgstr "Répondre à votre propre question immédiatement" #: conf/minimum_reputation.py:49 -#, fuzzy msgid "Accept own answer" -msgstr "\"modifier n'importe quelle réponse" +msgstr "Accepter votre propre réponse" #: conf/minimum_reputation.py:58 msgid "Flag offensive" @@ -1449,13 +1234,11 @@ msgstr "Transférer des fichiers sur notre serveur" #: conf/minimum_reputation.py:103 msgid "Close own questions" -msgstr "Cloturer ses propres questions" +msgstr "Clôturer ses propres questions" #: conf/minimum_reputation.py:112 msgid "Retag questions posted by other people" -msgstr "" -"Requalifier les questions publiées par d'autres utilisateurs (c'est à dire " -"changer les mots-clés servant de marqueurs sémantiques)" +msgstr "Requalifier les questions publiées par d'autres utilisateurs (c'est-à-dire changer les mots-clés servant de marqueurs sémantiques)" #: conf/minimum_reputation.py:121 msgid "Reopen own questions" @@ -1475,7 +1258,7 @@ msgstr "Voir les drapeaux signalant des messages à contenu abusif" #: conf/minimum_reputation.py:157 msgid "Close questions asked by others" -msgstr "Cloturer les questions posées par d'autres" +msgstr "Clôturer les questions posées par d'autres" #: conf/minimum_reputation.py:166 msgid "Lock posts" @@ -1483,27 +1266,23 @@ msgstr "Verrouiller des messages" #: conf/minimum_reputation.py:175 msgid "Remove rel=nofollow from own homepage" -msgstr "" +msgstr "Enlever rel=nofollow de la page d'accueil" #: conf/minimum_reputation.py:177 -msgid "" -"When a search engine crawler will see a rel=nofollow attribute on a link - " -"the link will not count towards the rank of the users personal site." -msgstr "" +msgid "When a search engine crawler will see a rel=nofollow attribute on a link - the link will not count towards the rank of the users personal site." +msgstr "Quand un indexeur de moteur de recherche voit un lien avec l'attribut rel=nofollow, ce dernier sera exclu du calcul du rang du site." #: conf/minimum_reputation.py:190 msgid "Post answers and comments by email" -msgstr "" +msgstr "Envoyer réponses et commentaires par email" #: conf/reputation_changes.py:13 -#, fuzzy msgid "Karma loss and gain rules" msgstr "Règles concernant le gain et la perte de points de réputation" #: conf/reputation_changes.py:23 msgid "Maximum daily reputation gain per user" -msgstr "" -"Nombre maximal de points de réputation qu'un utilisateur peut gagner par jour" +msgstr "Nombre maximal de points de réputation qu'un utilisateur peut gagner par jour" #: conf/reputation_changes.py:32 msgid "Gain for receiving an upvote" @@ -1528,9 +1307,7 @@ msgstr "Gain pour un votant en cas de vote négatif annulé" #: conf/reputation_changes.py:78 msgid "Loss for voter for canceling of answer acceptance" -msgstr "" -"Perte pour un utilisateur ayant voté pour l'annulation d'une acceptation de " -"réponse" +msgstr "Perte pour un utilisateur ayant voté pour l'annulation d'une acceptation de réponse" #: conf/reputation_changes.py:88 msgid "Loss for author whose answer was \"un-accepted\"" @@ -1546,146 +1323,123 @@ msgstr "Perte pour l'auteur d'un message à contenu signalé 'abusif'" #: conf/reputation_changes.py:118 msgid "Loss for owner of post that was downvoted" -msgstr "" -"Perte pour l'auteur d'un message ayant abouti à un vote (globalement) négatif" +msgstr "Perte pour l'auteur d'un message ayant abouti à un vote (globalement) négatif" #: conf/reputation_changes.py:128 msgid "Loss for owner of post that was flagged 3 times per same revision" -msgstr "" -"Perte pour l'auteur d'un message qui a été marqué/étiqueté 3 fois dans une " -"même révision" +msgstr "Perte pour l'auteur d'un message qui a été marqué/étiqueté 3 fois dans une même révision" #: conf/reputation_changes.py:138 msgid "Loss for owner of post that was flagged 5 times per same revision" -msgstr "" -"Perte pour l'auteur d'un message qui a été marqué/étiqueté 5 fois dans une " -"même révision" +msgstr "Perte pour l'auteur d'un message qui a été marqué/étiqueté 5 fois dans une même révision" #: conf/reputation_changes.py:148 msgid "Loss for post owner when upvote is canceled" -msgstr "" -"Perte pour l'auteur d'un message quand un vote (global) positif est annulé" +msgstr "Perte pour l'auteur d'un message quand un vote (global) positif est annulé" #: conf/sidebar_main.py:12 msgid "Main page sidebar" -msgstr "" +msgstr "Barre latérale de la page principale" -#: conf/sidebar_main.py:20 conf/sidebar_profile.py:20 +#: conf/sidebar_main.py:20 +#: conf/sidebar_profile.py:20 #: conf/sidebar_question.py:19 msgid "Custom sidebar header" -msgstr "" +msgstr "Entête personnalisé de la barre latérale" -#: conf/sidebar_main.py:23 conf/sidebar_profile.py:23 +#: conf/sidebar_main.py:23 +#: conf/sidebar_profile.py:23 #: conf/sidebar_question.py:22 -msgid "" -"Use this area to enter content at the TOP of the sidebarin HTML format. " -"When using this option (as well as the sidebar footer), please use the HTML " -"validation service to make sure that your input is valid and works well in " -"all browsers." -msgstr "" +msgid "Use this area to enter content at the TOP of the sidebarin HTML format. When using this option (as well as the sidebar footer), please use the HTML validation service to make sure that your input is valid and works well in all browsers." +msgstr "Utilisez cette zone pour ajouter du contenu en haut de la barre latérale, au format HTML. Pensez à vérifier que votre code est valide et fonctionne dans tous les navigateurs." #: conf/sidebar_main.py:36 msgid "Show avatar block in sidebar" -msgstr "" +msgstr "Afficher le bloc avatar dans la barre latérale" #: conf/sidebar_main.py:38 msgid "Uncheck this if you want to hide the avatar block from the sidebar " -msgstr "" +msgstr "Décochez si vous voulez cacher le bloc avatar de la barre latérale" #: conf/sidebar_main.py:49 msgid "Limit how many avatars will be displayed on the sidebar" -msgstr "" +msgstr "Limiter le nombre d'avatars affichés sur la barre latérale" #: conf/sidebar_main.py:59 msgid "Show tag selector in sidebar" -msgstr "" +msgstr "Afficher le sélecteur de mots-clés dans la barre latérale" #: conf/sidebar_main.py:61 -msgid "" -"Uncheck this if you want to hide the options for choosing interesting and " -"ignored tags " -msgstr "" +msgid "Uncheck this if you want to hide the options for choosing interesting and ignored tags " +msgstr "Décochez si vous voulez cacher l'option pour décider entre les mots-clés intéressants et ceux à ignorer" #: conf/sidebar_main.py:72 msgid "Show tag list/cloud in sidebar" -msgstr "" +msgstr "Afficher la liste ou le nuage de mots-clés dans la barre latérale" #: conf/sidebar_main.py:74 -msgid "" -"Uncheck this if you want to hide the tag cloud or tag list from the sidebar " -msgstr "" +msgid "Uncheck this if you want to hide the tag cloud or tag list from the sidebar " +msgstr "Décochez si vous voulez cacher la liste ou le nuage de mots-clés de la barre latérale" -#: conf/sidebar_main.py:85 conf/sidebar_profile.py:36 +#: conf/sidebar_main.py:85 +#: conf/sidebar_profile.py:36 #: conf/sidebar_question.py:75 msgid "Custom sidebar footer" -msgstr "" +msgstr "Pied-de-page personnalisé pour la barre latérale" -#: conf/sidebar_main.py:88 conf/sidebar_profile.py:39 +#: conf/sidebar_main.py:88 +#: conf/sidebar_profile.py:39 #: conf/sidebar_question.py:78 -msgid "" -"Use this area to enter content at the BOTTOM of the sidebarin HTML format. " -"When using this option (as well as the sidebar header), please use the HTML " -"validation service to make sure that your input is valid and works well in " -"all browsers." -msgstr "" +msgid "Use this area to enter content at the BOTTOM of the sidebarin HTML format. When using this option (as well as the sidebar header), please use the HTML validation service to make sure that your input is valid and works well in all browsers." +msgstr "Utilisez cette zone pour rajouter du contenu en bas de cette barre latérale, au format HTML. Pensez à vérifier que votre code est valide et fonctionne dans tous les navigateurs." #: conf/sidebar_profile.py:12 -#, fuzzy msgid "User profile sidebar" -msgstr "Profil utilisateur" +msgstr "Barre latérale profil utilisateur" #: conf/sidebar_question.py:11 -#, fuzzy msgid "Question page sidebar" -msgstr "Tags de la question" +msgstr "Barre latérale de la page de question" #: conf/sidebar_question.py:35 msgid "Show tag list in sidebar" -msgstr "" +msgstr "Afficher la liste des mots-clés dans la barre latérale" #: conf/sidebar_question.py:37 msgid "Uncheck this if you want to hide the tag list from the sidebar " -msgstr "" +msgstr "Décochez si vous voulez cacher la liste des mots-clés de la barre latérale" #: conf/sidebar_question.py:48 msgid "Show meta information in sidebar" -msgstr "" +msgstr "Affichage des méta-informations dans la barre latérale" #: conf/sidebar_question.py:50 -msgid "" -"Uncheck this if you want to hide the meta information about the question " -"(post date, views, last updated). " -msgstr "" +msgid "Uncheck this if you want to hide the meta information about the question (post date, views, last updated). " +msgstr "Décochez si vous voulez cacher les méta-informations de la question (date du message, vues, dernière mise à jour)." #: conf/sidebar_question.py:62 -#, fuzzy msgid "Show related questions in sidebar" -msgstr "Questions liées" +msgstr "Afficher les questions liées dans la barre latérale" #: conf/sidebar_question.py:64 -#, fuzzy msgid "Uncheck this if you want to hide the list of related questions. " -msgstr "Cliquez ici pour voir les questions mises à jour le moins récemment" +msgstr "Décochez si vous voulez cacher la liste des questions liées." #: conf/site_modes.py:64 msgid "Bootstrap mode" -msgstr "" +msgstr "Mode d'initialisation" #: conf/site_modes.py:74 msgid "Activate a \"Bootstrap\" mode" -msgstr "" +msgstr "Activer un mode d'initialisation" #: conf/site_modes.py:76 -msgid "" -"Bootstrap mode lowers reputation and certain badge thresholds, to values, " -"more suitable for the smaller communities, <strong>WARNING:</strong> your " -"current value for Minimum reputation, Bagde Settings and Vote Rules will be " -"changed after you modify this setting." -msgstr "" +msgid "Bootstrap mode lowers reputation and certain badge thresholds, to values, more suitable for the smaller communities, <strong>WARNING:</strong> your current value for Minimum reputation, Bagde Settings and Vote Rules will be changed after you modify this setting." +msgstr "Le mode d'initialisation réduit les seuils de réputation et de certains badges à des valeurs plus accessibles à des petites communautés. <strong>ATTENTION :</strong> vos valeurs actuelles de réputation minimale, paramètres de badges et règles de votes seront changées après modification de ce paramètre." #: conf/site_settings.py:12 msgid "URLS, keywords & greetings" -msgstr "" +msgstr "URLs, mots-clés et messages d'accueil" #: conf/site_settings.py:21 msgid "Site title for the Q&A forum" @@ -1693,9 +1447,7 @@ msgstr "Titre du forum de Questions/Réponses" #: conf/site_settings.py:30 msgid "Comma separated list of Q&A site keywords" -msgstr "" -"Mots-clés pour qualifier votre site de Questions/Réponses (séparez les mots " -"par des virgules)" +msgstr "Mots-clés pour qualifier votre site de Questions/Réponses (séparez les mots par des virgules)" #: conf/site_settings.py:39 msgid "Copyright message to show in the footer" @@ -1711,24 +1463,19 @@ msgstr "Insérez ici le nom court de votre forum de Questions/Réponses" #: conf/site_settings.py:67 msgid "Base URL for your Q&A forum, must start with http or https" -msgstr "" -"BaseURL de votre forum de Questions/Réponses (doit commencer par http ou " -"https)" +msgstr "BaseURL de votre forum de Questions/Réponses (doit commencer par http ou https)" #: conf/site_settings.py:78 -#, fuzzy msgid "Check to enable greeting for anonymous user" -msgstr "Faux email pour utilisateur anonyme" +msgstr "Cocher pour activer l'accueil pour les utilisateurs anonymes" #: conf/site_settings.py:89 -#, fuzzy msgid "Text shown in the greeting message shown to the anonymous user" -msgstr "" -"Lien inséré dans le message d'accueil affiché aux utilisateurs anonymes" +msgstr "Texte affiché dans le message d'accueil aux utilisateurs anonymes" #: conf/site_settings.py:93 msgid "Use HTML to format the message " -msgstr "" +msgstr "Formater le message avec HTML" #: conf/site_settings.py:102 msgid "Feedback site URL" @@ -1748,19 +1495,28 @@ msgstr "Valeur du compteur de votes pour \"pleines couleurs\"" #: conf/skin_counter_settings.py:29 msgid "Background color for votes = 0" -msgstr "Couleur d'arrière plan pour votes = 0" - -#: conf/skin_counter_settings.py:30 conf/skin_counter_settings.py:41 -#: conf/skin_counter_settings.py:52 conf/skin_counter_settings.py:62 -#: conf/skin_counter_settings.py:72 conf/skin_counter_settings.py:85 -#: conf/skin_counter_settings.py:106 conf/skin_counter_settings.py:117 -#: conf/skin_counter_settings.py:128 conf/skin_counter_settings.py:138 -#: conf/skin_counter_settings.py:148 conf/skin_counter_settings.py:163 -#: conf/skin_counter_settings.py:186 conf/skin_counter_settings.py:196 -#: conf/skin_counter_settings.py:206 conf/skin_counter_settings.py:216 -#: conf/skin_counter_settings.py:228 conf/skin_counter_settings.py:239 -#: conf/skin_counter_settings.py:252 conf/skin_counter_settings.py:262 -#, fuzzy +msgstr "Couleur d'arrière-plan pour votes = 0" + +#: conf/skin_counter_settings.py:30 +#: conf/skin_counter_settings.py:41 +#: conf/skin_counter_settings.py:52 +#: conf/skin_counter_settings.py:62 +#: conf/skin_counter_settings.py:72 +#: conf/skin_counter_settings.py:85 +#: conf/skin_counter_settings.py:106 +#: conf/skin_counter_settings.py:117 +#: conf/skin_counter_settings.py:128 +#: conf/skin_counter_settings.py:138 +#: conf/skin_counter_settings.py:148 +#: conf/skin_counter_settings.py:163 +#: conf/skin_counter_settings.py:186 +#: conf/skin_counter_settings.py:196 +#: conf/skin_counter_settings.py:206 +#: conf/skin_counter_settings.py:216 +#: conf/skin_counter_settings.py:228 +#: conf/skin_counter_settings.py:239 +#: conf/skin_counter_settings.py:252 +#: conf/skin_counter_settings.py:262 msgid "HTML color name or hex value" msgstr "Nom de couleur HTML ou valeur hexadécimale" @@ -1818,7 +1574,7 @@ msgstr "Valeur du compteur de réponses pour \"pleines couleurs\"" #: conf/skin_counter_settings.py:185 msgid "Background color for answers = 0" -msgstr "Couleur d'arrière plan pour réponses = 0" +msgstr "Couleur d'arrière-plan pour réponses = 0" #: conf/skin_counter_settings.py:195 msgid "Foreground color for answers = 0" @@ -1850,7 +1606,7 @@ msgstr "Couleur de premier plan pour réponse acceptée" #: conf/skin_general_settings.py:15 msgid "Logos and HTML <head> parts" -msgstr "" +msgstr "Logos et partie <head> HTML" #: conf/skin_general_settings.py:23 msgid "Q&A site logo" @@ -1858,58 +1614,40 @@ msgstr "Logo du site de Questions/Réponses" #: conf/skin_general_settings.py:25 msgid "To change the logo, select new file, then submit this whole form." -msgstr "" -"Pour changer le logo, sélectionnez un nouveau fichier, puis soumettez le " -"formulaire complet." +msgstr "Pour changer le logo, sélectionnez un nouveau fichier, puis soumettez le formulaire complet." #: conf/skin_general_settings.py:37 msgid "Show logo" -msgstr "" +msgstr "Afficher le logo" #: conf/skin_general_settings.py:39 -msgid "" -"Check if you want to show logo in the forum header or uncheck in the case " -"you do not want the logo to appear in the default location" -msgstr "" +msgid "Check if you want to show logo in the forum header or uncheck in the case you do not want the logo to appear in the default location" +msgstr "Cochez si vous voulez afficher le logo dans l'entête du forum ou décochez si vous ne voulez pas que le logo apparaisse à l'endroit par défaut" #: conf/skin_general_settings.py:51 msgid "Site favicon" -msgstr "" +msgstr "Favicône du site" #: conf/skin_general_settings.py:53 #, python-format -msgid "" -"A small 16x16 or 32x32 pixel icon image used to distinguish your site in the " -"browser user interface. Please find more information about favicon at <a " -"href=\"%(favicon_info_url)s\">this page</a>." -msgstr "" +msgid "A small 16x16 or 32x32 pixel icon image used to distinguish your site in the browser user interface. Please find more information about favicon at <a href=\"%(favicon_info_url)s\">this page</a>." +msgstr "Une petite icône, 16x16 ou 32x32 pixels, utilisée pour distinguer votre site dans l'interface du navigateur. Vous trouverez plus d'informations sur les favicônes <a href=\"%(favicon_info_url)s\">sur cette page</a>." #: conf/skin_general_settings.py:69 msgid "Password login button" -msgstr "" +msgstr "Bouton de connexion par mot de passe" #: conf/skin_general_settings.py:71 -msgid "" -"An 88x38 pixel image that is used on the login screen for the password login " -"button." -msgstr "" +msgid "An 88x38 pixel image that is used on the login screen for the password login button." +msgstr "Une image de 88x88 pixels utilisée sur l'écran de connexion pour l'authentification par mot de passe." #: conf/skin_general_settings.py:84 msgid "Show all UI functions to all users" -msgstr "" -"Afficher toutes les fonctionnalités de l'interface utilisateur à tous les " -"utilisateurs" +msgstr "Afficher toutes les fonctionnalités de l'interface utilisateur à tous les utilisateurs" #: conf/skin_general_settings.py:86 -msgid "" -"If checked, all forum functions will be shown to users, regardless of their " -"reputation. However to use those functions, moderation rules, reputation and " -"other limits will still apply." -msgstr "" -"Si cette case est cochée, toutes les fonctionnalités du forum seront " -"afifchées aux utilisateurs, sans tenir compte de leur réputation. Cependant, " -"pour utiliser ces fonctions, les règles de modération, le concept de " -"réputation et les autres limites s'appliqueront toujours..." +msgid "If checked, all forum functions will be shown to users, regardless of their reputation. However to use those functions, moderation rules, reputation and other limits will still apply." +msgstr "Si cette case est cochée, toutes les fonctionnalités du forum seront affichées aux utilisateurs, sans tenir compte de leur réputation. Cependant, pour utiliser ces fonctions, les règles de modération, le concept de réputation et les autres limites s'appliqueront toujours..." #: conf/skin_general_settings.py:101 msgid "Select skin" @@ -1917,105 +1655,71 @@ msgstr "Sélectionnez un thème (skin)" #: conf/skin_general_settings.py:112 msgid "Customize HTML <HEAD>" -msgstr "" +msgstr "Personnaliser la partie <head> HTML" #: conf/skin_general_settings.py:121 msgid "Custom portion of the HTML <HEAD>" -msgstr "" +msgstr "Portion <head> personnalisée" #: conf/skin_general_settings.py:123 -msgid "" -"<strong>To use this option</strong>, check \"Customize HTML <HEAD>\" " -"above. Contents of this box will be inserted into the <HEAD> portion " -"of the HTML output, where elements such as <script>, <link>, <" -"meta> may be added. Please, keep in mind that adding external javascript " -"to the <HEAD> is not recommended because it slows loading of the " -"pages. Instead, it will be more efficient to place links to the javascript " -"files into the footer. <strong>Note:</strong> if you do use this setting, " -"please test the site with the W3C HTML validator service." -msgstr "" +msgid "<strong>To use this option</strong>, check \"Customize HTML <HEAD>\" above. Contents of this box will be inserted into the <HEAD> portion of the HTML output, where elements such as <script>, <link>, <meta> may be added. Please, keep in mind that adding external javascript to the <HEAD> is not recommended because it slows loading of the pages. Instead, it will be more efficient to place links to the javascript files into the footer. <strong>Note:</strong> if you do use this setting, please test the site with the W3C HTML validator service." +msgstr "<strong>Pour utiliser cette option</strong>, cochez « Personnaliser la partie <head> HTML » ci-dessus. Le contenu de cette boîte sera inséré dans la balise <HEAD> du code HTML généré,où des éléments tels que <script>, <link>, <meta> peuvent être ajoutés. S'il vous plaît, gardez en mémoire que l'ajout de JavaScript externe dans <HEAD> est fortement déconseillé du fait du ralentissement potentiel. Dans ce cas-là, placez plutôt les liens vers le code JavaScript externe dans le pied de page. <strong>Note :</strong> Si vous utilisez ce paramètre, merci de vérifier votre site avec un outil de validation HTML." #: conf/skin_general_settings.py:145 msgid "Custom header additions" -msgstr "" +msgstr "En-têtes personnalisées" #: conf/skin_general_settings.py:147 -msgid "" -"Header is the bar at the top of the content that contains user info and site " -"links, and is common to all pages. Use this area to enter contents of the " -"headerin the HTML format. When customizing the site header (as well as " -"footer and the HTML <HEAD>), use the HTML validation service to make " -"sure that your input is valid and works well in all browsers." -msgstr "" +msgid "Header is the bar at the top of the content that contains user info and site links, and is common to all pages. Use this area to enter contents of the headerin the HTML format. When customizing the site header (as well as footer and the HTML <HEAD>), use the HTML validation service to make sure that your input is valid and works well in all browsers." +msgstr "L'en-tête est la barre au dessus du contenu qui contient les informations utilisateurs et les liens du site, et est commune à toutes les pages. Utilisez cette zone pour entrer du contenu pour l'entête, au format HTML. Lorsque vous personnalisez l'en-tête (ainsi que le pied de page et la balise <head>), merci d'utiliser un outil de vérification pour vous assurer que votre code fonctionne bien dans tous les navigateurs" #: conf/skin_general_settings.py:162 msgid "Site footer mode" -msgstr "" +msgstr "Mode pied de page du site" #: conf/skin_general_settings.py:164 -msgid "" -"Footer is the bottom portion of the content, which is common to all pages. " -"You can disable, customize, or use the default footer." -msgstr "" +msgid "Footer is the bottom portion of the content, which is common to all pages. You can disable, customize, or use the default footer." +msgstr "Le pied de page est la partie basse du contenu, commune à toutes les pages. Vous pouvez le désactiver, personnaliser ou utiliser celui par défaut." #: conf/skin_general_settings.py:181 msgid "Custom footer (HTML format)" -msgstr "" +msgstr "Pied de page personnalité (format HTML)" #: conf/skin_general_settings.py:183 -msgid "" -"<strong>To enable this function</strong>, please select option 'customize' " -"in the \"Site footer mode\" above. Use this area to enter contents of the " -"footer in the HTML format. When customizing the site footer (as well as the " -"header and HTML <HEAD>), use the HTML validation service to make sure " -"that your input is valid and works well in all browsers." -msgstr "" +msgid "<strong>To enable this function</strong>, please select option 'customize' in the \"Site footer mode\" above. Use this area to enter contents of the footer in the HTML format. When customizing the site footer (as well as the header and HTML <HEAD>), use the HTML validation service to make sure that your input is valid and works well in all browsers." +msgstr "<strong>Pour utiliser cette option</strong>, sélectionnez l'option « personnaliser » dans le « Mode pied de page du site » ci-dessus. Utilisez cette zone pour entrer le contenu du pied de page au format HTML. Lors de la personnalisation (de même que pour l'en-tête et la balise <HEAD>), utilisez des outils de validation HTML pour vous assurer que votre code est valide." #: conf/skin_general_settings.py:198 msgid "Apply custom style sheet (CSS)" -msgstr "" +msgstr "Appliquer une feuille de style CSS personnalisée" #: conf/skin_general_settings.py:200 -msgid "" -"Check if you want to change appearance of your form by adding custom style " -"sheet rules (please see the next item)" -msgstr "" +msgid "Check if you want to change appearance of your form by adding custom style sheet rules (please see the next item)" +msgstr "Cochez pour changer l'apparence de votre formulaire en ajoutant des règles CSS (confère le prochain élément)" #: conf/skin_general_settings.py:212 msgid "Custom style sheet (CSS)" -msgstr "" +msgstr "Feuille de style CSS personnalisée" #: conf/skin_general_settings.py:214 -msgid "" -"<strong>To use this function</strong>, check \"Apply custom style sheet\" " -"option above. The CSS rules added in this window will be applied after the " -"default style sheet rules. The custom style sheet will be served dynamically " -"at url \"<forum url>/custom.css\", where the \"<forum url> part " -"depends (default is empty string) on the url configuration in your urls.py." -msgstr "" +msgid "<strong>To use this function</strong>, check \"Apply custom style sheet\" option above. The CSS rules added in this window will be applied after the default style sheet rules. The custom style sheet will be served dynamically at url \"<forum url>/custom.css\", where the \"<forum url> part depends (default is empty string) on the url configuration in your urls.py." +msgstr "<strong>Pour utiliser cette option</strong>, cochez l'option « Appliquer une feuille de style CSS personnalisée » ci-dessus. Les règles CSS ajoutées dans cette fenêtre seront appliquées après les règles par défaut. Les feuilles de style personnalisées seront servies dynamiquement depuis l'adresse « <URL forum>/custom.css », où la partie <URL forum> (par défaut, une chaîne vide) dépend de la configuration dans urls.py." #: conf/skin_general_settings.py:230 msgid "Add custom javascript" -msgstr "" +msgstr "Ajouter du JavaScript personnalisé" #: conf/skin_general_settings.py:233 msgid "Check to enable javascript that you can enter in the next field" -msgstr "" +msgstr "Cochez pour activer le code JavaScript que vous entrerez dans le prochain champ" #: conf/skin_general_settings.py:243 msgid "Custom javascript" -msgstr "" +msgstr "JavaScript personnalisé" #: conf/skin_general_settings.py:245 -msgid "" -"Type or paste plain javascript that you would like to run on your site. Link " -"to the script will be inserted at the bottom of the HTML output and will be " -"served at the url \"<forum url>/custom.js\". Please, bear in mind that " -"your javascript code may break other functionalities of the site and that " -"the behavior may not be consistent across different browsers (<strong>to " -"enable your custom code</strong>, check \"Add custom javascript\" option " -"above)." -msgstr "" +msgid "Type or paste plain javascript that you would like to run on your site. Link to the script will be inserted at the bottom of the HTML output and will be served at the url \"<forum url>/custom.js\". Please, bear in mind that your javascript code may break other functionalities of the site and that the behavior may not be consistent across different browsers (<strong>to enable your custom code</strong>, check \"Add custom javascript\" option above)." +msgstr "Mettez du code JavaScript que vous voudriez exécuter sur vote site. Un lien vers le script sera ajouté au bas du rendu HTML et sera servi depuis l'adresse « <URL forum>/custom.js ». Merci de garder en tête que votre code JavaScript risque de casser d'autres fonctionnalités du site et que le comportement peut ne pas être cohérent dans les différents navigateurs (<strong>pour activer votre code personnalisé</strong>, cochez l'option « Ajouter du JavaScript personnalisé » ci-dessus)." #: conf/skin_general_settings.py:263 msgid "Skin media revision number" @@ -2023,160 +1727,132 @@ msgstr "Numéro de révision du thème" #: conf/skin_general_settings.py:265 msgid "Will be set automatically but you can modify it if necessary." -msgstr "" +msgstr "Sera activé automatiquement mais vous pourrez le modifier si vous voulez." #: conf/skin_general_settings.py:276 msgid "Hash to update the media revision number automatically." -msgstr "" +msgstr "Condensé pour mettre à jour le numéro de révision du média automatiquement." #: conf/skin_general_settings.py:280 msgid "Will be set automatically, it is not necesary to modify manually." -msgstr "" +msgstr "Sera activé automatiquement, il n'est pas nécessaire de modifier manuellement." #: conf/social_sharing.py:11 msgid "Sharing content on social networks" -msgstr "" +msgstr "Partage de contenu sur les réseaux sociaux" #: conf/social_sharing.py:20 -#, fuzzy msgid "Check to enable sharing of questions on Twitter" -msgstr "Partager cette question sur twitter" +msgstr "Cochez pour autoriser le partage de questions sur Twitter" #: conf/social_sharing.py:29 -#, fuzzy msgid "Check to enable sharing of questions on Facebook" -msgstr "Partager cette question sur facebook" +msgstr "Cochez pour autoriser le partage de questions sur Facebook" #: conf/social_sharing.py:38 msgid "Check to enable sharing of questions on LinkedIn" -msgstr "" +msgstr "Cochez pour activer le partage de questions sur LinkedIn" #: conf/social_sharing.py:47 msgid "Check to enable sharing of questions on Identi.ca" -msgstr "" +msgstr "Cochez pour activer le partage de questions sur Identi.ca" #: conf/social_sharing.py:56 msgid "Check to enable sharing of questions on Google+" -msgstr "" +msgstr "Cochez pour activer le partage de questions sur Google+" #: conf/spam_and_moderation.py:10 msgid "Akismet spam protection" -msgstr "" +msgstr "Protection de pollution Akismet" #: conf/spam_and_moderation.py:18 msgid "Enable Akismet spam detection(keys below are required)" -msgstr "" +msgstr "Activer la détection de pollution par Akismet (clefs nécessaires ci-dessous)" #: conf/spam_and_moderation.py:21 #, python-format msgid "To get an Akismet key please visit <a href=\"%(url)s\">Akismet site</a>" -msgstr "" +msgstr "Pour obtenir une clef Akismet merci de voir le <a href=\"%(url)s\">site Akismet</a>" #: conf/spam_and_moderation.py:31 msgid "Akismet key for spam detection" -msgstr "" +msgstr "Clef Akismet pour la détection de pollution" #: conf/super_groups.py:5 msgid "Reputation, Badges, Votes & Flags" -msgstr "" +msgstr "Réputation, badges, votes et drapeaux" #: conf/super_groups.py:6 msgid "Static Content, URLS & UI" -msgstr "" +msgstr "Contenu statique, URLs et interface" #: conf/super_groups.py:7 msgid "Data rules & Formatting" -msgstr "" +msgstr "Règles de données et formatage " #: conf/super_groups.py:8 -#, fuzzy msgid "External Services" -msgstr "Autres services" +msgstr "Services externes" #: conf/super_groups.py:9 msgid "Login, Users & Communication" -msgstr "" +msgstr "Compte, utilisateurs et communication" #: conf/user_settings.py:14 -#, fuzzy msgid "User settings" -msgstr "FIXME - User policy settings" +msgstr "Paramètres utilisateur" #: conf/user_settings.py:23 msgid "Allow editing user screen name" -msgstr "" -"Autoriser la modification du pseudo (nom d'utilisateur affiché à l'écran)" +msgstr "Autoriser la modification du pseudo (nom d'utilisateur affiché à l'écran)" #: conf/user_settings.py:32 -#, fuzzy msgid "Allow users change own email addresses" -msgstr "N'autoriser qu'un compte par adresse email" +msgstr "Autoriser les utilisateurs à changer leur adresse email" #: conf/user_settings.py:41 -#, fuzzy msgid "Allow account recovery by email" -msgstr "" -"<span class=\"strong big\">Formulez votre question à l'aide du formulaire ci-" -"dessous (un court titre résumant la question, puis la question à proprement " -"parler, aussi détaillée que vous le souhaitez...)</span>. A l'étape " -"suivante, vous devrez saisir votre email et votre nom (ou un pseudo si vous " -"souhaitez rester anonyme...). Ces éléments sont nécessaires pour bénéficier " -"des fonctionnalités de notre module de questions/réponses, qui repose sur un " -"principe communautaire." +msgstr "Autoriser la récupération de compte par email" #: conf/user_settings.py:50 msgid "Allow adding and removing login methods" -msgstr "" +msgstr "Autoriser l'ajout et le retrait de méthodes de connexion" #: conf/user_settings.py:60 msgid "Minimum allowed length for screen name" msgstr "Taille minimale du pseudo (nom d'utilisateur affiché à l'écran)" #: conf/user_settings.py:68 -#, fuzzy msgid "Default avatar for users" -msgstr "Valeur par défaut: %s" +msgstr "Avatar par défaut pour les utilisateurs" #: conf/user_settings.py:70 -#, fuzzy -msgid "" -"To change the avatar image, select new file, then submit this whole form." -msgstr "" -"Pour changer le logo, sélectionnez un nouveau fichier, puis soumettez le " -"formulaire complet." +msgid "To change the avatar image, select new file, then submit this whole form." +msgstr "Pour changer l'avatar, sélectionnez un nouveau fichier, puis envoyez le formulaire complet." #: conf/user_settings.py:83 msgid "Use automatic avatars from gravatar.com" -msgstr "" +msgstr "Utiliser automatiquement les avatars depuis gravatar.com" #: conf/user_settings.py:85 -msgid "" -"Check this option if you want to allow the use of gravatar.com for avatars. " -"Please, note that this feature might take about 10 minutes to become fully " -"effective. You will have to enable uploaded avatars as well. For more " -"information, please visit <a href=\"http://askbot.org/doc/optional-modules." -"html#uploaded-avatars\">this page</a>." -msgstr "" +msgid "Check this option if you want to allow the use of gravatar.com for avatars. Please, note that this feature might take about 10 minutes to become fully effective. You will have to enable uploaded avatars as well. For more information, please visit <a href=\"http://askbot.org/doc/optional-modules.html#uploaded-avatars\">this page</a>." +msgstr "Cochez cette option si vous voulez autoriser l'utilisation de gravatar.com pour les avatars. Merci de noter que cette fonctionnalité peut nécessiter 10 minutes pour être effectivement active. Vous devrez aussi activer les avatars envoyés également. Pour plus d'information, merci de visiter <a href=\"http://askbot.org/doc/optional-modules.html#uploaded-avatars\">cette page</a>." #: conf/user_settings.py:97 msgid "Default Gravatar icon type" -msgstr "" +msgstr "Type d'icône Gravatar par défaut" #: conf/user_settings.py:99 -msgid "" -"This option allows you to set the default avatar type for email addresses " -"without associated gravatar images. For more information, please visit <a " -"href=\"http://en.gravatar.com/site/implement/images/\">this page</a>." -msgstr "" +msgid "This option allows you to set the default avatar type for email addresses without associated gravatar images. For more information, please visit <a href=\"http://en.gravatar.com/site/implement/images/\">this page</a>." +msgstr "Cette option vous permet de définir le type d'avatar par défaut pour les adresses sans gravatar associé. Pour plus d'informations, merci de visiter <a href=\"http://en.gravatar.com/site/implement/images/\"> page</a>." #: conf/user_settings.py:109 -#, fuzzy msgid "Name for the Anonymous user" -msgstr "Faux email pour utilisateur anonyme" +msgstr "Nom de l'utilisateur anonyme" #: conf/vote_rules.py:14 msgid "Vote and flag limits" -msgstr "" +msgstr "Limites de votes et de drapeaux" #: conf/vote_rules.py:24 msgid "Number of votes a user can cast per day" @@ -2188,67 +1864,51 @@ msgstr "Nombre maximum de drapeaux par utilisateur et par jour" #: conf/vote_rules.py:42 msgid "Threshold for warning about remaining daily votes" -msgstr "" -"Seuil pour lancer un avertissement à propos des votes quotidiens restants" +msgstr "Seuil pour lancer un avertissement à propos des votes quotidiens restants" #: conf/vote_rules.py:51 msgid "Number of days to allow canceling votes" msgstr "Nombre de jours pour autoriser l'annulation de votes" #: conf/vote_rules.py:60 -#, fuzzy msgid "Number of days required before answering own question" -msgstr "Nombre de jours pour autoriser l'annulation de votes" +msgstr "Nombre de jours nécessaires avant de se répondre" #: conf/vote_rules.py:69 msgid "Number of flags required to automatically hide posts" -msgstr "" -"Nombre de marqueurs/drapeaux requis pour masquer automatiquement des messages" +msgstr "Nombre de marqueurs/drapeaux requis pour masquer automatiquement des messages" #: conf/vote_rules.py:78 msgid "Number of flags required to automatically delete posts" -msgstr "" -"Nombre de marqueurs/drapeaux requis pour supprimer automatiquement des " -"messages" +msgstr "Nombre de marqueurs/drapeaux requis pour supprimer automatiquement des messages" #: conf/vote_rules.py:87 -msgid "" -"Minimum days to accept an answer, if it has not been accepted by the " -"question poster" -msgstr "" +msgid "Minimum days to accept an answer, if it has not been accepted by the question poster" +msgstr "Délai minimum en jours pour accepter une réponse, si elle n'a pas été acceptée par la personne ayant posé la question" #: conf/widgets.py:13 msgid "Embeddable widgets" -msgstr "" +msgstr "Éléments embarquables" #: conf/widgets.py:25 -#, fuzzy msgid "Number of questions to show" -msgstr "Nombre de questions par défaut à afficher dans la liste " +msgstr "Nombre de questions à afficher " #: conf/widgets.py:28 -msgid "" -"To embed the widget, add the following code to your site (and fill in " -"correct base url, preferred tags, width and height):<iframe src=" -"\"{{base_url}}/widgets/questions?tags={{comma-separated-tags}}\" width=\"100%" -"\" height=\"300\"scrolling=\"no\"><p>Your browser does not support iframes.</" -"p></iframe>" -msgstr "" +msgid "To embed the widget, add the following code to your site (and fill in correct base url, preferred tags, width and height):<iframe src=\"{{base_url}}/widgets/questions?tags={{comma-separated-tags}}\" width=\"100%\" height=\"300\"scrolling=\"no\"><p>Your browser does not support iframes.</p></iframe>" +msgstr "Pour embarquer un élément, ajoutez le code suivant à votre site (et indiquez l'URL de base correcte, les mots-clés préférés, la largeur et la hauteur) : <iframe src=\"{{base_url}}/widgets/questions?tags={{comma-separated-tags}}\" width=\"100%\" height=\"300\"scrolling=\"no\"><p>Votre navigateur ne supporte pas les iframes.</p></iframe>" #: conf/widgets.py:73 -#, fuzzy msgid "CSS for the questions widget" -msgstr "Clore la question" +msgstr "CSS pour l'élément questions" #: conf/widgets.py:81 -#, fuzzy msgid "Header for the questions widget" -msgstr "Continuer de masquer les questions ignorées" +msgstr "Entête pour l'élément questions" #: conf/widgets.py:90 -#, fuzzy msgid "Footer for the questions widget" -msgstr "questions favorites des utilisateurs" +msgstr "Pied de page pour l'élément questions" #: const/__init__.py:10 msgid "duplicate question" @@ -2296,7 +1956,8 @@ msgstr "trop spécifique à un lieu" msgid "newest" msgstr "date (↓)" -#: const/__init__.py:44 skins/default/templates/users.html:27 +#: const/__init__.py:44 +#: skins/default/templates/users.html:27 #: skins/default/templates/question/answer_tab_bar.html:15 msgid "oldest" msgstr "date (↑)" @@ -2354,13 +2015,12 @@ msgid "favorite" msgstr "favorite" #: const/__init__.py:70 -#, fuzzy msgid "list" -msgstr "Liste des tags" +msgstr "liste" #: const/__init__.py:71 msgid "cloud" -msgstr "" +msgstr "nuage" #: const/__init__.py:79 msgid "Question has no answers" @@ -2378,11 +2038,13 @@ msgstr "a posé une question" msgid "answered a question" msgstr "a répondu à une question" -#: const/__init__.py:127 const/__init__.py:203 +#: const/__init__.py:127 +#: const/__init__.py:203 msgid "commented question" msgstr "question commentée" -#: const/__init__.py:128 const/__init__.py:204 +#: const/__init__.py:128 +#: const/__init__.py:204 msgid "commented answer" msgstr "réponse commentée" @@ -2395,9 +2057,8 @@ msgid "edited answer" msgstr "réponse modifiée" #: const/__init__.py:131 -#, fuzzy msgid "received badge" -msgstr "récompense obtenue" +msgstr "badge obtenu" # FIXME ou "ayant reçu une récompense" #: const/__init__.py:132 @@ -2451,29 +2112,25 @@ msgid "email update sent to user" msgstr "Mise à jour d'email envoyée à l'utilisateur" #: const/__init__.py:145 -#, fuzzy msgid "reminder about unanswered questions sent" -msgstr "Voir les questions sans réponses" +msgstr "rappel envoyé pour les questions sans réponse" # FIXME ou "ayant reçu une récompense" #: const/__init__.py:149 -#, fuzzy msgid "reminder about accepting the best answer sent" -msgstr "Gain pour accepter une meilleure réponse" +msgstr "rappel envoyé pour accepter la meilleure réponse" #: const/__init__.py:151 msgid "mentioned in the post" msgstr "mentionné dans le message" #: const/__init__.py:202 -#, fuzzy msgid "answered question" -msgstr "a répondu à une question" +msgstr "question résolue" #: const/__init__.py:205 -#, fuzzy msgid "accepted answer" -msgstr "réponse modifiée" +msgstr "réponse acceptée" #: const/__init__.py:209 msgid "[closed]" @@ -2483,7 +2140,8 @@ msgstr "[close]" msgid "[deleted]" msgstr "[supprimée]" -#: const/__init__.py:211 views/readers.py:566 +#: const/__init__.py:211 +#: views/readers.py:566 msgid "initial version" msgstr "version initiale" @@ -2522,61 +2180,58 @@ msgstr "Aucun email" #: const/__init__.py:236 msgid "identicon" -msgstr "" +msgstr "identicon" #: const/__init__.py:237 -#, fuzzy msgid "mystery-man" -msgstr "hier" +msgstr "homme-mystère" #: const/__init__.py:238 msgid "monsterid" -msgstr "" +msgstr "monsterid" #: const/__init__.py:239 -#, fuzzy msgid "wavatar" -msgstr "Qu'est ce que 'Gravatar' ?" +msgstr "wavatar" #: const/__init__.py:240 msgid "retro" -msgstr "" +msgstr "retro" -#: const/__init__.py:287 skins/default/templates/badges.html:38 +#: const/__init__.py:287 +#: skins/default/templates/badges.html:38 msgid "gold" msgstr "or" -#: const/__init__.py:288 skins/default/templates/badges.html:48 +#: const/__init__.py:288 +#: skins/default/templates/badges.html:48 msgid "silver" msgstr "argent" -#: const/__init__.py:289 skins/default/templates/badges.html:55 +#: const/__init__.py:289 +#: skins/default/templates/badges.html:55 msgid "bronze" msgstr "bronze" #: const/__init__.py:301 msgid "None" -msgstr "" +msgstr "Aucun" #: const/__init__.py:302 -#, fuzzy msgid "Gravatar" -msgstr "Qu'est ce que 'Gravatar' ?" +msgstr "Gravatar" #: const/__init__.py:303 -#, fuzzy msgid "Uploaded Avatar" -msgstr "Qu'est ce que 'Gravatar' ?" +msgstr "Envoyer un avatar" #: const/message_keys.py:21 -#, fuzzy msgid "most relevant questions" -msgstr "Merci de poser une question pertinente." +msgstr "questions les plus pertinentes" #: const/message_keys.py:22 -#, fuzzy msgid "click to see most relevant questions" -msgstr "Cliquez ici pour voir les questions ayant obtenu le plus de votes" +msgstr "Cliquez ici pour voir les questions les plus pertinentes" # FIXME ou "peu de votes" #: const/message_keys.py:23 @@ -2609,18 +2264,16 @@ msgid "click to see the most recently updated questions" msgstr "Cliquez ici pour voir les questions mises à jour le plus récemment" #: const/message_keys.py:30 -#, fuzzy msgid "click to see the least answered questions" -msgstr "Cliquez ici pour voir les questions les moins récentes" +msgstr "Cliquez ici pour voir les questions avec le moins de réponse" #: const/message_keys.py:31 msgid "by answers" msgstr "par réponses" #: const/message_keys.py:32 -#, fuzzy msgid "click to see the most answered questions" -msgstr "Cliquez ici pour voir les questions ayant obtenu le plus de votes" +msgstr "cliquez ici pour voir les questions avec le plus de réponse" #: const/message_keys.py:33 msgid "click to see least voted questions" @@ -2635,86 +2288,76 @@ msgid "click to see most voted questions" msgstr "Cliquez ici pour voir les questions ayant obtenu le plus de votes" #: const/message_keys.py:40 -msgid "" -"Sorry, your account appears to be blocked and you cannot make new posts " -"until this issue is resolved. Please contact the forum administrator to " -"reach a resolution." -msgstr "" +msgid "Sorry, your account appears to be blocked and you cannot make new posts until this issue is resolved. Please contact the forum administrator to reach a resolution." +msgstr "Désolé, votre compte semble bloqué et vous ne pouvez pas envoyer de nouveaux messages jusqu'à nouvel ordre. Merci de contacter l'administrateur du forum pour trouver une solution." -#: const/message_keys.py:45 models/__init__.py:788 -msgid "" -"Sorry, your account appears to be suspended and you cannot make new posts " -"until this issue is resolved. You can, however edit your existing posts. " -"Please contact the forum administrator to reach a resolution." -msgstr "" +#: const/message_keys.py:45 +#: models/__init__.py:788 +msgid "Sorry, your account appears to be suspended and you cannot make new posts until this issue is resolved. You can, however edit your existing posts. Please contact the forum administrator to reach a resolution." +msgstr "Désolé, votre compte semble bloqué et vous ne pouvez pas envoyer de nouveaux messages jusqu'à nouvel ordre. Vous pouvez, en revanche éditer vos propres messages. Merci de contacter l'administrateur du forum pour trouver une solution." #: deps/django_authopenid/backends.py:166 -msgid "" -"Welcome! Please set email address (important!) in your profile and adjust " -"screen name, if necessary." -msgstr "" +msgid "Welcome! Please set email address (important!) in your profile and adjust screen name, if necessary." +msgstr "Bienvenue ! Merci de spécifier une adresse email (c'est important !) dans votre profil et de mettre à jour votre pseudonyme, si nécessaire." -#: deps/django_authopenid/forms.py:110 deps/django_authopenid/views.py:142 +#: deps/django_authopenid/forms.py:110 +#: deps/django_authopenid/views.py:142 msgid "i-names are not supported" msgstr "Les i-names ne sont pas supportés." #: deps/django_authopenid/forms.py:233 -#, fuzzy, python-format +#, python-format msgid "Please enter your %(username_token)s" -msgstr "Veuillez saisir votre nom d'utilisateur" +msgstr "Veuillez saisir votre %(username_token)s" #: deps/django_authopenid/forms.py:259 -#, fuzzy msgid "Please, enter your user name" msgstr "Veuillez saisir votre nom d'utilisateur" # TODO "votre" ou "un" ? #: deps/django_authopenid/forms.py:263 -#, fuzzy msgid "Please, enter your password" msgstr "Veuillez saisir votre mot de passe" # TODO "votre" ou "un" ? -#: deps/django_authopenid/forms.py:270 deps/django_authopenid/forms.py:274 -#, fuzzy +#: deps/django_authopenid/forms.py:270 +#: deps/django_authopenid/forms.py:274 msgid "Please, enter your new password" -msgstr "Veuillez saisir votre mot de passe" +msgstr "Veuillez saisir votre nouveau mot de passe" #: deps/django_authopenid/forms.py:285 msgid "Passwords did not match" -msgstr "" +msgstr "Les mots de passe ne correspondent pas" #: deps/django_authopenid/forms.py:297 #, python-format msgid "Please choose password > %(len)s characters" -msgstr "" +msgstr "Merci de choisir un mot de passe avec plus de %(len)s caractères" #: deps/django_authopenid/forms.py:335 msgid "Current password" msgstr "Mot de passe actuel" #: deps/django_authopenid/forms.py:346 -msgid "" -"Old password is incorrect. Please enter the correct " -"password." -msgstr "" -"L'ancien mot de passe est erroné. Veuillez le corriger." +msgid "Old password is incorrect. Please enter the correct password." +msgstr "L'ancien mot de passe est erroné. Veuillez le corriger." #: deps/django_authopenid/forms.py:399 msgid "Sorry, we don't have this email address in the database" -msgstr "" +msgstr "Désolé, nous n'avons pas cette adresse email dans la base de données" #: deps/django_authopenid/forms.py:435 msgid "Your user name (<i>required</i>)" msgstr "Votre nom d'utilisateur (<i>obligatoire</i>)" #: deps/django_authopenid/forms.py:450 -#, fuzzy msgid "sorry, there is no such user name" msgstr "désolé, aucun utilisateur ne porte ce nom" -#: deps/django_authopenid/urls.py:9 deps/django_authopenid/urls.py:12 -#: deps/django_authopenid/urls.py:15 setup_templates/settings.py:210 +#: deps/django_authopenid/urls.py:9 +#: deps/django_authopenid/urls.py:12 +#: deps/django_authopenid/urls.py:15 +#: setup_templates/settings.py:210 msgid "signin/" msgstr "connexion/" @@ -2727,9 +2370,8 @@ msgid "complete/" msgstr "termine/" #: deps/django_authopenid/urls.py:15 -#, fuzzy msgid "complete-oauth/" -msgstr "termine/" +msgstr "termine-oauth/" #: deps/django_authopenid/urls.py:19 msgid "register/" @@ -2744,192 +2386,179 @@ msgid "logout/" msgstr "deconnecter/" #: deps/django_authopenid/urls.py:30 -#, fuzzy msgid "recover/" -msgstr "reouvrir/" +msgstr "récuperer/" #: deps/django_authopenid/util.py:378 -#, fuzzy, python-format +#, python-format msgid "%(site)s user name and password" -msgstr "Veuillez saisir votre nom d'utilisateur et un mot de passe" +msgstr "nom d'utilisateur et mot de passe pour %(site)s" #: deps/django_authopenid/util.py:384 #: skins/common/templates/authopenid/signin.html:115 msgid "Create a password-protected account" -msgstr "" +msgstr "Créer un compte protégé par mot de passe" #: deps/django_authopenid/util.py:385 -#, fuzzy msgid "Change your password" -msgstr "Changer de mot de passe" +msgstr "Changer votre mot de passe" #: deps/django_authopenid/util.py:473 msgid "Sign in with Yahoo" -msgstr "" +msgstr "Se connecter avec Yahoo" #: deps/django_authopenid/util.py:480 -#, fuzzy msgid "AOL screen name" -msgstr "Pseudo" +msgstr "Pseudonyme AOL" #: deps/django_authopenid/util.py:488 -#, fuzzy msgid "OpenID url" -msgstr "URL OpenID:" +msgstr "URL OpenID :" #: deps/django_authopenid/util.py:517 -#, fuzzy msgid "Flickr user name" -msgstr "Nom d'utilisateur" +msgstr "Nom d'utilisateur Flickr" #: deps/django_authopenid/util.py:525 -#, fuzzy msgid "Technorati user name" -msgstr "choisissez un nom d'utilisateur" +msgstr "Nom d'utilisateur Technorati" #: deps/django_authopenid/util.py:533 msgid "WordPress blog name" -msgstr "" +msgstr "Nom du blog WordPress" #: deps/django_authopenid/util.py:541 msgid "Blogger blog name" -msgstr "" +msgstr "Nom du blog Blogger" #: deps/django_authopenid/util.py:549 msgid "LiveJournal blog name" -msgstr "" +msgstr "Nom du blog LiveJournal" #: deps/django_authopenid/util.py:557 -#, fuzzy msgid "ClaimID user name" -msgstr "Nom d'utilisateur" +msgstr "Nom d'utilisateur ClaimID" #: deps/django_authopenid/util.py:565 -#, fuzzy msgid "Vidoop user name" -msgstr "Nom d'utilisateur" +msgstr "Nom d'utilisateur Vidoop" #: deps/django_authopenid/util.py:573 -#, fuzzy msgid "Verisign user name" -msgstr "Nom d'utilisateur" +msgstr "Nom d'utilisateur VeriSign" #: deps/django_authopenid/util.py:608 -#, fuzzy, python-format +#, python-format msgid "Change your %(provider)s password" -msgstr "Changer de mot de passe" +msgstr "Changer votre mot de passe %(provider)s" #: deps/django_authopenid/util.py:612 #, python-format msgid "Click to see if your %(provider)s signin still works for %(site_name)s" -msgstr "" +msgstr "Vérifier si votre authentification %(provider)s fonctionne toujours sur %(site_name)s" #: deps/django_authopenid/util.py:621 #, python-format msgid "Create password for %(provider)s" -msgstr "" +msgstr "Créer un mot de passe pour %(provider)s" # FIXME #: deps/django_authopenid/util.py:625 -#, fuzzy, python-format +#, python-format msgid "Connect your %(provider)s account to %(site_name)s" -msgstr "Associez votre OpenID avec votre compte sur ce site" +msgstr "Connecter votre compte %(provider)s à %(site_name)s" #: deps/django_authopenid/util.py:634 -#, fuzzy, python-format +#, python-format msgid "Signin with %(provider)s user name and password" -msgstr "Veuillez saisir votre nom d'utilisateur et un mot de passe" +msgstr "Se connecter avec le nom d'utilisateur et le mot de passe %(provider)s" #: deps/django_authopenid/util.py:641 #, python-format msgid "Sign in with your %(provider)s account" -msgstr "" +msgstr "Se connecter avec le compte %(provider)s" #: deps/django_authopenid/views.py:149 #, python-format msgid "OpenID %(openid_url)s is invalid" msgstr "L'OpenID %(openid_url)s est invalide" -#: deps/django_authopenid/views.py:261 deps/django_authopenid/views.py:408 +#: deps/django_authopenid/views.py:261 +#: deps/django_authopenid/views.py:408 #: deps/django_authopenid/views.py:436 #, python-format -msgid "" -"Unfortunately, there was some problem when connecting to %(provider)s, " -"please try again or use another provider" -msgstr "" +msgid "Unfortunately, there was some problem when connecting to %(provider)s, please try again or use another provider" +msgstr "Malheureusement, il y a eu des problèmes pendant la connexion avec %(provider)s, merci de réessayer ou d'utiliser un autre fournisseur" #: deps/django_authopenid/views.py:358 -#, fuzzy msgid "Your new password saved" -msgstr "Votre mot de passe a été mis à jour." +msgstr "Votre nouveau mot de passe a été enregistré." #: deps/django_authopenid/views.py:462 msgid "The login password combination was not correct" -msgstr "" +msgstr "La combinaison nom d'utilisateur et mot de passe n'est pas correcte" #: deps/django_authopenid/views.py:564 msgid "Please click any of the icons below to sign in" -msgstr "" +msgstr "Merci de cliquer sur l'une des icônes ci-dessous pour vous connecter" #: deps/django_authopenid/views.py:566 msgid "Account recovery email sent" -msgstr "" +msgstr "Email de récupération de compte envoyé" #: deps/django_authopenid/views.py:569 msgid "Please add one or more login methods." -msgstr "" +msgstr "Merci d'ajouter une ou plusieurs méthodes de connexion" #: deps/django_authopenid/views.py:571 msgid "If you wish, please add, remove or re-validate your login methods" -msgstr "" +msgstr "Si vous le souhaitez, merci d'ajouter, supprimer ou re-valider vos méthodes de connexion" #: deps/django_authopenid/views.py:573 msgid "Please wait a second! Your account is recovered, but ..." -msgstr "" +msgstr "Merci d'attendre une seconde ! Votre compte est récupéré, mais…" #: deps/django_authopenid/views.py:575 msgid "Sorry, this account recovery key has expired or is invalid" -msgstr "" +msgstr "Désolé, la clef de récupération de ce compte est expirée ou invalide" #: deps/django_authopenid/views.py:648 #, python-format msgid "Login method %(provider_name)s does not exist" -msgstr "" +msgstr "La méthode de connexion %(provider_name)s n'existe pas" #: deps/django_authopenid/views.py:654 -#, fuzzy msgid "Oops, sorry - there was some error - please try again" -msgstr "désolé, les 2 mots de passe sont différents, veuillez recommencer" +msgstr "Oops, désolé - une erreur est survenue - merci de réessayer" #: deps/django_authopenid/views.py:745 #, python-format msgid "Your %(provider)s login works fine" -msgstr "" +msgstr "Votre connexion %(provider)s fonctionne bien" -#: deps/django_authopenid/views.py:1056 deps/django_authopenid/views.py:1062 +#: deps/django_authopenid/views.py:1056 +#: deps/django_authopenid/views.py:1062 #, python-format msgid "your email needs to be validated see %(details_url)s" -msgstr "" -"Nous avons besoin de valider votre adresse email. <a " -"id='validate_email_alert' href='%(details_url)s'>Cliquez ici</a> pour en " -"savoir plus." +msgstr "Nous avons besoin de valider votre adresse email. <a id='validate_email_alert' href='%(details_url)s'>Cliquez ici</a> pour en savoir plus." #: deps/django_authopenid/views.py:1083 -#, fuzzy, python-format +#, python-format msgid "Recover your %(site)s account" -msgstr "Changer le mot de passe de votre compte" +msgstr "Récupérer votre compte %(site)s" #: deps/django_authopenid/views.py:1155 msgid "Please check your email and visit the enclosed link." -msgstr "" +msgstr "Merci de vérifier votre email et revenir via le lien indiqué." -#: deps/livesettings/models.py:101 deps/livesettings/models.py:140 +#: deps/livesettings/models.py:101 +#: deps/livesettings/models.py:140 msgid "Site" msgstr "Site" #: deps/livesettings/values.py:69 msgid "Main" -msgstr "" +msgstr "Principal" #: deps/livesettings/values.py:128 msgid "Base Settings" @@ -2992,7 +2621,7 @@ msgstr[1] "Merci de bien vouloir corriger les erreurs ci-dessous." #: deps/livesettings/templates/livesettings/group_settings.html:28 #, python-format msgid "Settings included in %(name)s." -msgstr "Paramètrages inclus dans %(name)s." +msgstr "Paramétrages inclus dans %(name)s." #: deps/livesettings/templates/livesettings/group_settings.html:62 #: deps/livesettings/templates/livesettings/site_settings.html:97 @@ -3001,22 +2630,20 @@ msgstr "Vous n'avez pas la permission de modifier ces valeurs" #: deps/livesettings/templates/livesettings/site_settings.html:27 msgid "Edit Site Settings" -msgstr "Modifier le paramètrage du site" +msgstr "Modifier le paramétrage du site" #: deps/livesettings/templates/livesettings/site_settings.html:43 msgid "Livesettings are disabled for this site." -msgstr "Les paramètrages \"en direct\" sont désactivés pour ce site" +msgstr "Les paramétrages \"en direct\" sont désactivés pour ce site" #: deps/livesettings/templates/livesettings/site_settings.html:44 msgid "All configuration options must be edited in the site settings.py file" -msgstr "" -"Toutes le soptions de configurations doivent être modifiées dans le fichier " -"settings.py du site" +msgstr "Toutes les options de configuration doivent être modifiées dans le fichier settings.py du site" #: deps/livesettings/templates/livesettings/site_settings.html:66 #, python-format msgid "Group settings: %(name)s" -msgstr "paramètres du groupe : %(name)s" +msgstr "Paramètres du groupe : %(name)s" #: deps/livesettings/templates/livesettings/site_settings.html:93 msgid "Uncollapse all" @@ -3029,35 +2656,29 @@ msgstr "Félicitations, vous êtes maintenant administrateur" #: management/commands/send_accept_answer_reminders.py:58 #, python-format msgid "Accept the best answer for %(question_count)d of your questions" -msgstr "" +msgstr "Accepter la meilleure réponse pour %(question_count)d de vos questions" #: management/commands/send_accept_answer_reminders.py:63 -#, fuzzy msgid "Please accept the best answer for this question:" -msgstr "Soyez le premier à répondre à cette quesion !" +msgstr "Merci d'accepter la meilleure réponse à cette question :" #: management/commands/send_accept_answer_reminders.py:65 -#, fuzzy msgid "Please accept the best answer for these questions:" -msgstr "Cliquez ici pour voir les questions les moins récentes" +msgstr "Merci d'accepter la meilleure réponse à ces questions :" #: management/commands/send_email_alerts.py:414 #, python-format msgid "%(question_count)d updated question about %(topics)s" msgid_plural "%(question_count)d updated questions about %(topics)s" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(question_count)d question mise à jour à propos %(topics)s" +msgstr[1] "%(question_count)d questions mises à jour à propos %(topics)s" #: management/commands/send_email_alerts.py:425 #, python-format -msgid "" -"<p>Dear %(name)s,</p><p>The following question has been updated " -"%(sitename)s</p>" -msgid_plural "" -"<p>Dear %(name)s,</p><p>The following %(num)d questions have been updated on " -"%(sitename)s:</p>" -msgstr[0] "" -msgstr[1] "" +msgid "<p>Dear %(name)s,</p><p>The following question has been updated %(sitename)s</p>" +msgid_plural "<p>Dear %(name)s,</p><p>The following %(num)d questions have been updated on %(sitename)s:</p>" +msgstr[0] "<p>Bonjour %(name)s,</p><p>La question suivante a été mise à jour %(sitename)s</p>" +msgstr[1] "<p>Bonjour, %(name)s,</p><p>Les %(num)d questions ont été mises à jour %(sitename)s</p>" #: management/commands/send_email_alerts.py:449 msgid "new question" @@ -3065,70 +2686,47 @@ msgstr "nouvelle question" #: management/commands/send_email_alerts.py:474 #, python-format -msgid "" -"<p>Please remember that you can always <a hrefl\"%(email_settings_link)s" -"\">adjust</a> frequency of the email updates or turn them off entirely.<br/" -">If you believe that this message was sent in an error, please email about " -"it the forum administrator at %(admin_email)s.</p><p>Sincerely,</p><p>Your " -"friendly %(sitename)s server.</p>" -msgstr "" +msgid "<p>Please remember that you can always <a hrefl\"%(email_settings_link)s\">adjust</a> frequency of the email updates or turn them off entirely.<br/>If you believe that this message was sent in an error, please email about it the forum administrator at %(admin_email)s.</p><p>Sincerely,</p><p>Your friendly %(sitename)s server.</p>" +msgstr "<p>Merci de vous souvenir que vous pouvez toujours <a hrefl\"%(email_settings_link)s\">ajuster</a> la fréquence des mises à jour par email ou les désactiver totalement.<br/>Si vous pensez que ce message a été envoyé par erreur, merci de contacter l'administrateur du forum à l'adresse %(admin_email)s.</p><p>Sincèrement,</p><p>Votre ami le serveur de %(sitename)s.</p>" #: management/commands/send_unanswered_question_reminders.py:60 #, python-format msgid "%(question_count)d unanswered question about %(topics)s" msgid_plural "%(question_count)d unanswered questions about %(topics)s" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(question_count)d question sans réponse au sujet de %(topics)s" +msgstr[1] "%(question_count)d questions sans réponse au sujet de %(topics)s" #: middleware/forum_mode.py:53 -#, fuzzy, python-format +#, python-format msgid "Please log in to use %s" -msgstr "Bitte einloggen" +msgstr "Merci de vous connecter pour utiliser %s" #: models/__init__.py:320 -msgid "" -"Sorry, you cannot accept or unaccept best answers because your account is " -"blocked" -msgstr "" -"Désolé, vous ne pouvez pas accepter ou refuser les meilleures réponses car " -"votre compte est bloqué" +msgid "Sorry, you cannot accept or unaccept best answers because your account is blocked" +msgstr "Désolé, vous ne pouvez pas accepter ou refuser les meilleures réponses car votre compte est bloqué" #: models/__init__.py:324 -msgid "" -"Sorry, you cannot accept or unaccept best answers because your account is " -"suspended" -msgstr "" -"Désolé, vous ne pouvez pas accepter ou refuser les meilleures réponses car " -"votre compte est suspendu" +msgid "Sorry, you cannot accept or unaccept best answers because your account is suspended" +msgstr "Désolé, vous ne pouvez pas accepter ou refuser les meilleures réponses car votre compte est suspendu" #: models/__init__.py:337 -#, fuzzy, python-format -msgid "" -">%(points)s points required to accept or unaccept your own answer to your " -"own question" -msgstr "" -"Désolé, vous ne pouvez pas accepter ou rejeter votre propre réponse à votre " -"propre question !" +#, python-format +msgid ">%(points)s points required to accept or unaccept your own answer to your own question" +msgstr "> %(points)s points nécessaires pour accepter ou non votre propre réponse" #: models/__init__.py:359 #, python-format -msgid "" -"Sorry, you will be able to accept this answer only after %(will_be_able_at)s" -msgstr "" +msgid "Sorry, you will be able to accept this answer only after %(will_be_able_at)s" +msgstr "Désolé, vous ne pourrez accepter cette réponse qu'après %(will_be_able_at)s" #: models/__init__.py:367 -#, fuzzy, python-format -msgid "" -"Sorry, only moderators or original author of the question - %(username)s - " -"can accept or unaccept the best answer" -msgstr "" -"Désolé, seul l'auteur d'origine de la question - %(username)s - peut " -"accepter/désigner la meilleure réponse" +#, python-format +msgid "Sorry, only moderators or original author of the question - %(username)s - can accept or unaccept the best answer" +msgstr "Désolé, seul l'auteur de la question - %(username)s - ou un modérateur peut accepter ou non la meilleure réponse" #: models/__init__.py:390 -#, fuzzy msgid "Sorry, you cannot vote for your own posts" -msgstr "Il est interdit de voter pour ses propores publications" +msgstr "Il est interdit de voter pour ses propres publications" #: models/__init__.py:394 msgid "Sorry your account appears to be blocked " @@ -3154,338 +2752,229 @@ msgstr "Désolé, les utilisateurs bloqués ne peuvent pas transférer de fichie #: models/__init__.py:431 msgid "Sorry, suspended users cannot upload files" -msgstr "" -"Désolé, les utilisateurs suspendus ne peuvent pas transférer de fichier" +msgstr "Désolé, les utilisateurs suspendus ne peuvent pas transférer de fichier" #: models/__init__.py:433 #, python-format msgid "sorry, file uploading requires karma >%(min_rep)s" -msgstr "" +msgstr "désolé, l'envoi de fichier nécessite un karma supérieur à %(min_rep)s" #: models/__init__.py:482 #, python-format -msgid "" -"Sorry, comments (except the last one) are editable only within %(minutes)s " -"minute from posting" -msgid_plural "" -"Sorry, comments (except the last one) are editable only within %(minutes)s " -"minutes from posting" -msgstr[0] "" -msgstr[1] "" +msgid "Sorry, comments (except the last one) are editable only within %(minutes)s minute from posting" +msgid_plural "Sorry, comments (except the last one) are editable only within %(minutes)s minutes from posting" +msgstr[0] "Désolé, les commentaires (sauf le dernier) sont modifiables uniquement pendant %(minutes)s minute après l'envoi" +msgstr[1] "Désolé, les commentaires (sauf le dernier) sont modifiables uniquement pendant %(minutes)s minutes après l'envoi" #: models/__init__.py:494 -#, fuzzy msgid "Sorry, but only post owners or moderators can edit comments" -msgstr "" -"Désolé, seuls les propriétaires des questions, les administrateurs du site " -"et les modérateurs peuvent requalifier des questions supprimées (c'est à " -"dire modifier leurs mots-clés)" +msgstr "Désolé, seuls les propriétaires des questions ou les modérateurs peuvent modifier des commentaires" #: models/__init__.py:519 -msgid "" -"Sorry, since your account is suspended you can comment only your own posts" -msgstr "" -"Désolé, vous ne pouvez commenter que vos propres publications, car votre " -"compte est suspendu" +msgid "Sorry, since your account is suspended you can comment only your own posts" +msgstr "Désolé, vous ne pouvez commenter que vos propres publications, car votre compte est suspendu" #: models/__init__.py:523 #, python-format -msgid "" -"Sorry, to comment any post a minimum reputation of %(min_rep)s points is " -"required. You can still comment your own posts and answers to your questions" -msgstr "" -"Désolé, pour commenter des publications, un minimum de %(min_rep)s points de " -"réputation est requis. Vous pouvez toutefois commenter vos propres " -"publications et répondre à vos questions" +msgid "Sorry, to comment any post a minimum reputation of %(min_rep)s points is required. You can still comment your own posts and answers to your questions" +msgstr "Désolé, pour commenter des publications, un minimum de %(min_rep)s points de réputation est requis. Vous pouvez toutefois commenter vos propres publications et répondre à vos questions" #: models/__init__.py:553 -#, fuzzy -msgid "" -"This post has been deleted and can be seen only by post owners, site " -"administrators and moderators" -msgstr "" -"Ce message a été supprimé et peut seulement être consulté par ses " -"propriétaires, les administrateurs du site, et les modérateurs" +msgid "This post has been deleted and can be seen only by post owners, site administrators and moderators" +msgstr "Ce message a été supprimé et peut seulement être consulté par ses propriétaires, les administrateurs du site, et les modérateurs" #: models/__init__.py:570 -msgid "" -"Sorry, only moderators, site administrators and post owners can edit deleted " -"posts" -msgstr "" -"Désolé, seuls les modérateurs, les administrateurs du site et les " -"propriétaires des messages peuvent modifier les messages supprimés" +msgid "Sorry, only moderators, site administrators and post owners can edit deleted posts" +msgstr "Désolé, seuls les modérateurs, les administrateurs du site et les propriétaires des messages peuvent modifier les messages supprimés" #: models/__init__.py:585 msgid "Sorry, since your account is blocked you cannot edit posts" -msgstr "" -"Désolé, vous ne pouvez pas modifier de messages, car votre compte est bloqué" +msgstr "Désolé, vous ne pouvez pas modifier de messages, car votre compte est bloqué" #: models/__init__.py:589 msgid "Sorry, since your account is suspended you can edit only your own posts" -msgstr "" -"Désolé, vous ne pouvez modifier que vos propres messages, car votre compte " -"est suspendu" +msgstr "Désolé, vous ne pouvez modifier que vos propres messages, car votre compte est suspendu" #: models/__init__.py:594 -#, fuzzy, python-format -msgid "" -"Sorry, to edit wiki posts, a minimum reputation of %(min_rep)s is required" -msgstr "" -"Désolé, pour modifier les messages du wiki, un minimum de %(min_rep)s points " -"de réputation est requis" +#, python-format +msgid "Sorry, to edit wiki posts, a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour modifier les messages du wiki, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:601 -#, fuzzy, python-format -msgid "" -"Sorry, to edit other people's posts, a minimum reputation of %(min_rep)s is " -"required" -msgstr "" -"Désolé, pour éditer les messages des autres utilisateurs, un minimum de " -"%(min_rep)s points de réputation est requis" +#, python-format +msgid "Sorry, to edit other people's posts, a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour éditer les messages des autres utilisateurs, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:664 -msgid "" -"Sorry, cannot delete your question since it has an upvoted answer posted by " -"someone else" -msgid_plural "" -"Sorry, cannot delete your question since it has some upvoted answers posted " -"by other users" -msgstr[0] "" -"Désolé, impossible de supprimer votre question car elle a obtenu un vote " -"positif de la part d'un autre utilisateur" -msgstr[1] "" -"Désolé, impossible de supprimer votre question car elle a obtenu des votes " -"positifs de la part d'autres utilisateurs" +msgid "Sorry, cannot delete your question since it has an upvoted answer posted by someone else" +msgid_plural "Sorry, cannot delete your question since it has some upvoted answers posted by other users" +msgstr[0] "Désolé, impossible de supprimer votre question car elle a obtenu un vote positif de la part d'un autre utilisateur" +msgstr[1] "Désolé, impossible de supprimer votre question car elle a obtenu des votes positifs de la part d'autres utilisateurs" #: models/__init__.py:679 msgid "Sorry, since your account is blocked you cannot delete posts" -msgstr "" -"Désolé, vous ne pouvez pas supprimer de messages car votre compte est bloqué" +msgstr "Désolé, vous ne pouvez pas supprimer de messages car votre compte est bloqué" #: models/__init__.py:683 -msgid "" -"Sorry, since your account is suspended you can delete only your own posts" -msgstr "" -"Désolé, vous pouvez seulement supprimer vos propres messages car votre " -"compte est suspendu" +msgid "Sorry, since your account is suspended you can delete only your own posts" +msgstr "Désolé, vous pouvez seulement supprimer vos propres messages car votre compte est suspendu" #: models/__init__.py:687 #, python-format -msgid "" -"Sorry, to deleted other people' posts, a minimum reputation of %(min_rep)s " -"is required" -msgstr "" -"Désolé, pour supprimer les messages des autres utilisateurs, un minimum de " -"%(min_rep)s points de réputation est requis" +msgid "Sorry, to deleted other people' posts, a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour supprimer les messages des autres utilisateurs, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:707 msgid "Sorry, since your account is blocked you cannot close questions" -msgstr "" -"Désolé, vous ne pouvez pas cloturer de questions car votre compte est bloqué" +msgstr "Désolé, vous ne pouvez pas clôturer de questions car votre compte est bloqué" #: models/__init__.py:711 msgid "Sorry, since your account is suspended you cannot close questions" -msgstr "" -"Désolé, vous ne pouvez pas cloturer de questions car votre compte est " -"suspendu" +msgstr "Désolé, vous ne pouvez pas clôturer de questions car votre compte est suspendu" #: models/__init__.py:715 #, python-format -msgid "" -"Sorry, to close other people' posts, a minimum reputation of %(min_rep)s is " -"required" -msgstr "" -"Désolé, pour cloturer les messages des autres utilisateurs, un minimum de " -"%(min_rep)s points de réputation est requis" +msgid "Sorry, to close other people' posts, a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour clôturer les messages des autres utilisateurs, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:724 #, python-format -msgid "" -"Sorry, to close own question a minimum reputation of %(min_rep)s is required" -msgstr "" -"Désolé, pour clore vos propres questions, un minimum de %(min_rep)s points " -"de réputation est requis" +msgid "Sorry, to close own question a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour clore vos propres questions, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:748 #, python-format -msgid "" -"Sorry, only administrators, moderators or post owners with reputation > " -"%(min_rep)s can reopen questions." -msgstr "" -"Désolé, seuls les administrateurs, les modérateurs et les propriétaires des " -"messages avec plus de %(min_rep)s points de réputation peuvent réouvrir des " -"questions." +msgid "Sorry, only administrators, moderators or post owners with reputation > %(min_rep)s can reopen questions." +msgstr "Désolé, seuls les administrateurs, les modérateurs et les propriétaires des messages avec plus de %(min_rep)s points de réputation peuvent réouvrir des questions." #: models/__init__.py:754 #, python-format -msgid "" -"Sorry, to reopen own question a minimum reputation of %(min_rep)s is required" -msgstr "" -"Désolé, pour réouvrir vos propres questions, un minimum de %(min_rep)s " -"points de réputation est requis" +msgid "Sorry, to reopen own question a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour réouvrir vos propres questions, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:775 msgid "You have flagged this question before and cannot do it more than once" -msgstr "" +msgstr "Vous avez déjà mis un drapeau sur cette question et ne pouvez pas le refaire" #: models/__init__.py:783 -#, fuzzy msgid "Sorry, since your account is blocked you cannot flag posts as offensive" -msgstr "" -"Désolé, vous ne pouvez pas supprimer de messages car votre compte est bloqué" +msgstr "Désolé, vous ne pouvez pas marquer des messages comme offensifs car votre compte est bloqué" #: models/__init__.py:794 -#, fuzzy, python-format -msgid "" -"Sorry, to flag posts as offensive a minimum reputation of %(min_rep)s is " -"required" -msgstr "" -"Désolé, pour requalifier une question (c'est à dire changer ses mots-clés), " -"un minimum de %(min_rep)s points de réputation est requis" +#, python-format +msgid "Sorry, to flag posts as offensive a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour marquer des messages comme offensifs, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:815 #, python-format -msgid "" -"Sorry, you have exhausted the maximum number of %(max_flags_per_day)s " -"offensive flags per day." -msgstr "" +msgid "Sorry, you have exhausted the maximum number of %(max_flags_per_day)s offensive flags per day." +msgstr "Désolé, vous avez épuisé le nombre maximal par jour de notification %(max_flags_per_day)s de message offensifs." #: models/__init__.py:827 msgid "cannot remove non-existing flag" -msgstr "" +msgstr "impossible de supprimer un drapeau inexistent" #: models/__init__.py:833 -#, fuzzy msgid "Sorry, since your account is blocked you cannot remove flags" -msgstr "" -"Désolé, vous ne pouvez pas supprimer de messages car votre compte est bloqué" +msgstr "Désolé, vous ne pouvez pas supprimer d'étiquette car votre compte est bloqué" #: models/__init__.py:837 -msgid "" -"Sorry, your account appears to be suspended and you cannot remove flags. " -"Please contact the forum administrator to reach a resolution." -msgstr "" +msgid "Sorry, your account appears to be suspended and you cannot remove flags. Please contact the forum administrator to reach a resolution." +msgstr "Désolé, votre compte semble suspendu et vous ne pouvez pas supprimer des drapeaux. Merci de contacter l'administrateur du forum pour trouver une solution." #: models/__init__.py:843 -#, fuzzy, python-format +#, python-format msgid "Sorry, to flag posts a minimum reputation of %(min_rep)d is required" -msgid_plural "" -"Sorry, to flag posts a minimum reputation of %(min_rep)d is required" -msgstr[0] "" -"Désolé, pour requalifier une question (c'est à dire changer ses mots-clés), " -"un minimum de %(min_rep)s points de réputation est requis" -msgstr[1] "" -"Désolé, pour requalifier une question (c'est à dire changer ses mots-clés), " -"un minimum de %(min_rep)s points de réputation est requis" +msgid_plural "Sorry, to flag posts a minimum reputation of %(min_rep)d is required" +msgstr[0] "Désolé, pour étiqueter un message, un minimum de %(min_rep)d point de réputation est requis" +msgstr[1] "Désolé, pour étiqueter un message, un minimum de %(min_rep)d points de réputation est requis" #: models/__init__.py:862 -#, fuzzy msgid "you don't have the permission to remove all flags" -msgstr "Vous n'avez pas la permission de modifier ces valeurs" +msgstr "vous n'avez pas la permission de supprimer toutes les étiquettes" #: models/__init__.py:863 msgid "no flags for this entry" -msgstr "" +msgstr "pas de drapeau pour cette entrée" #: models/__init__.py:887 -msgid "" -"Sorry, only question owners, site administrators and moderators can retag " -"deleted questions" -msgstr "" -"Désolé, seuls les propriétaires des questions, les administrateurs du site " -"et les modérateurs peuvent requalifier des questions supprimées (c'est à " -"dire modifier leurs mots-clés)" +msgid "Sorry, only question owners, site administrators and moderators can retag deleted questions" +msgstr "Désolé, seuls les propriétaires des questions, les administrateurs du site et les modérateurs peuvent requalifier des questions supprimées (c'est à dire modifier leurs mots-clés)" #: models/__init__.py:894 msgid "Sorry, since your account is blocked you cannot retag questions" -msgstr "" -"Désolé, vous ne pouvez pas requalifier une question (c'est à dire changer " -"ses mots-clés) car votre compte est bloqué" +msgstr "Désolé, vous ne pouvez pas requalifier une question (c'est à dire changer ses mots-clés) car votre compte est bloqué" #: models/__init__.py:898 -msgid "" -"Sorry, since your account is suspended you can retag only your own questions" -msgstr "" -"Désolé, vous pouvez seulement requalifier vos propres questions (c'est à " -"dire changer leurs mots-clés) car votre compte est suspendu" +msgid "Sorry, since your account is suspended you can retag only your own questions" +msgstr "Désolé, vous pouvez seulement requalifier vos propres questions (c'est à dire changer leurs mots-clés) car votre compte est suspendu" #: models/__init__.py:902 #, python-format -msgid "" -"Sorry, to retag questions a minimum reputation of %(min_rep)s is required" -msgstr "" -"Désolé, pour requalifier une question (c'est à dire changer ses mots-clés), " -"un minimum de %(min_rep)s points de réputation est requis" +msgid "Sorry, to retag questions a minimum reputation of %(min_rep)s is required" +msgstr "Désolé, pour requalifier une question (c'est-à-dire changer ses mots-clés), un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:921 msgid "Sorry, since your account is blocked you cannot delete comment" -msgstr "" -"Désolé, vous ne pouvez pas supprimer de commentaires car votre compte est " -"bloqué" +msgstr "Désolé, vous ne pouvez pas supprimer de commentaires car votre compte est bloqué" #: models/__init__.py:925 -msgid "" -"Sorry, since your account is suspended you can delete only your own comments" -msgstr "" -"Désolé, vous pouvez seulement supprimer vos propres commentaires car votre " -"compte est suspendu" +msgid "Sorry, since your account is suspended you can delete only your own comments" +msgstr "Désolé, vous pouvez seulement supprimer vos propres commentaires car votre compte est suspendu" #: models/__init__.py:929 #, python-format msgid "Sorry, to delete comments reputation of %(min_rep)s is required" -msgstr "" -"Désolé, pour supprimer des commentaires, un minimum de %(min_rep)s points de " -"réputation est requis" +msgstr "Désolé, pour supprimer des commentaires, un minimum de %(min_rep)s points de réputation est requis" #: models/__init__.py:953 msgid "sorry, but older votes cannot be revoked" -msgstr "" +msgstr "désolé, mais les anciens votes ne peuvent être révoqués" -#: models/__init__.py:1469 utils/functions.py:78 +#: models/__init__.py:1469 +#: utils/functions.py:78 #, python-format msgid "on %(date)s" msgstr "le %(date)s" #: models/__init__.py:1471 msgid "in two days" -msgstr "" +msgstr "dans deux jours" #: models/__init__.py:1473 msgid "tomorrow" -msgstr "" +msgstr "demain" #: models/__init__.py:1475 -#, fuzzy, python-format +#, python-format msgid "in %(hr)d hour" msgid_plural "in %(hr)d hours" -msgstr[0] "il y a %(hr)d heure" -msgstr[1] "il y a %(hr)d heures" +msgstr[0] "dans %(hr)d heure" +msgstr[1] "dans %(hr)d heures" #: models/__init__.py:1477 -#, fuzzy, python-format +#, python-format msgid "in %(min)d min" msgid_plural "in %(min)d mins" -msgstr[0] "il y a %(min)d minute" -msgstr[1] "il y a %(min)d minutes" +msgstr[0] "dans %(min)d minute" +msgstr[1] "dans %(min)d minutes" #: models/__init__.py:1478 #, python-format msgid "%(days)d day" msgid_plural "%(days)d days" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(days)d jour" +msgstr[1] "%(days)d jours" #: models/__init__.py:1480 #, python-format -msgid "" -"New users must wait %(days)s before answering their own question. You can " -"post an answer %(left)s" -msgstr "" +msgid "New users must wait %(days)s before answering their own question. You can post an answer %(left)s" +msgstr "Les nouveaux utilisateurs doivent attendre %(days)s avant de répondre à leurs propres questions. Vous pouvez envoyer une réponse %(left)s" -#: models/__init__.py:1653 skins/default/templates/feedback_email.txt:9 -#, fuzzy +#: models/__init__.py:1653 +#: skins/default/templates/feedback_email.txt:9 msgid "Anonymous" -msgstr "anonyme" +msgstr "Anonyme" #: models/__init__.py:1749 msgid "Site Adminstrator" @@ -3516,74 +3005,69 @@ msgid "Approved User" msgstr "Utilisateur certifié" #: models/__init__.py:1870 -#, fuzzy, python-format +#, python-format msgid "%(username)s karma is %(reputation)s" -msgstr "Votre karma est %(reputation)s" +msgstr "Le karma de %(username)s est %(reputation)s" #: models/__init__.py:1880 #, python-format msgid "one gold badge" msgid_plural "%(count)d gold badges" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "un badge d'or" +msgstr[1] "%(count)d badges d'or" #: models/__init__.py:1887 -#, fuzzy, python-format +#, python-format msgid "one silver badge" msgid_plural "%(count)d silver badges" -msgstr[0] "Badge argent - blah blah" -msgstr[1] "Badge argent - blah blah" +msgstr[0] "un badge d'argent" +msgstr[1] "%(count)d badges d'argent" #: models/__init__.py:1894 -#, fuzzy, python-format +#, python-format msgid "one bronze badge" msgid_plural "%(count)d bronze badges" -msgstr[0] "Badge bronze - blah blah" -msgstr[1] "Badge bronze - blah blah" +msgstr[0] "un badge de bronze" +msgstr[1] "%(count)d badges de bronze" #: models/__init__.py:1905 #, python-format msgid "%(item1)s and %(item2)s" -msgstr "" +msgstr "%(item1)s et %(item2)s" #: models/__init__.py:1909 #, python-format msgid "%(user)s has %(badges)s" -msgstr "" +msgstr "%(user)s a %(badges)s" #: models/__init__.py:2389 -#, fuzzy, python-format +#, python-format msgid "\"%(title)s\"" -msgstr "Tags de la question" +msgstr "« %(title)s »" #: models/__init__.py:2542 #, python-format -msgid "" -"Congratulations, you have received a badge '%(badge_name)s'. Check out <a " -"href=\"%(user_profile)s\">your profile</a>." -msgstr "" -"Félicitations, vous avez reçu un badge '%(badge_name)s'. Consultez <a href=" -"\"%(user_profile)s\">votre profil</a>." +msgid "Congratulations, you have received a badge '%(badge_name)s'. Check out <a href=\"%(user_profile)s\">your profile</a>." +msgstr "Félicitations, vous avez reçu un badge '%(badge_name)s'. Consultez <a href=\"%(user_profile)s\">votre profil</a>." -#: models/__init__.py:2745 views/commands.py:460 +#: models/__init__.py:2745 +#: views/commands.py:460 msgid "Your tag subscription was saved, thanks!" -msgstr "" +msgstr "Votre inscription au mot-clé est enregistrée, merci !" #: models/badges.py:129 -#, fuzzy, python-format +#, python-format msgid "Deleted own post with %(votes)s or more upvotes" -msgstr "" -"Suppression de son propre message avec un score de 3 votes positifs ou moins" +msgstr "Suppression de son propre message avec un score de %(votes)s votes positifs ou plus" #: models/badges.py:133 msgid "Disciplined" msgstr "Discipliné" #: models/badges.py:151 -#, fuzzy, python-format +#, python-format msgid "Deleted own post with %(votes)s or more downvotes" -msgstr "" -"Suppression de son propre message avec un score de 3 votes positifs ou moins" +msgstr "Suppression de son propre message avec un score de %(votes)s votes négatifs ou plus" #: models/badges.py:155 msgid "Peer Pressure" @@ -3592,7 +3076,7 @@ msgstr "Pression des pairs" #: models/badges.py:174 #, python-format msgid "Received at least %(votes)s upvote for an answer for the first time" -msgstr "" +msgstr "Reçu au moins %(votes)s votes positifs pour une réponse pour la première fois" #: models/badges.py:178 msgid "Teacher" @@ -3603,7 +3087,6 @@ msgid "Supporter" msgstr "Supporteur" #: models/badges.py:219 -#, fuzzy msgid "First upvote" msgstr "Premier vote positif" @@ -3612,12 +3095,10 @@ msgid "Critic" msgstr "Critique" #: models/badges.py:228 -#, fuzzy msgid "First downvote" msgstr "Premier vote négatif" #: models/badges.py:237 -#, fuzzy msgid "Civic Duty" msgstr "Devoir civique" @@ -3639,7 +3120,9 @@ msgstr "Autodidacte" msgid "Nice Answer" msgstr "Jolie réponse" -#: models/badges.py:309 models/badges.py:321 models/badges.py:333 +#: models/badges.py:309 +#: models/badges.py:321 +#: models/badges.py:333 #, python-format msgid "Answer voted up %(num)s times" msgstr "Réponse ayant obtenu %(num)s votes positifs" @@ -3656,7 +3139,9 @@ msgstr "Très bonne réponse" msgid "Nice Question" msgstr "Jolie question" -#: models/badges.py:345 models/badges.py:357 models/badges.py:369 +#: models/badges.py:345 +#: models/badges.py:357 +#: models/badges.py:369 #, python-format msgid "Question voted up %(num)s times" msgstr "Question ayant obtenu %(num)s votes positifs" @@ -3671,7 +3156,7 @@ msgstr "Très bonne question" #: models/badges.py:376 msgid "Student" -msgstr "Etudiant" +msgstr "Étudiant" #: models/badges.py:381 msgid "Asked first question with at least one up vote" @@ -3681,7 +3166,9 @@ msgstr "A posté sa première question avec au moins un vote positif" msgid "Popular Question" msgstr "Question populaire" -#: models/badges.py:418 models/badges.py:429 models/badges.py:441 +#: models/badges.py:418 +#: models/badges.py:429 +#: models/badges.py:441 #, python-format msgid "Asked a question with %(views)s views" msgstr "A posé une question consultée plus de %(views)s fois" @@ -3722,12 +3209,8 @@ msgstr "Première question acceptée avec au moins %(num)s votes positifs" #: models/badges.py:518 #, python-format -msgid "" -"Answered a question more than %(days)s days later with at least %(votes)s " -"votes" -msgstr "" -"A répondu à une question avec %(days)s jours de retard, et avec au moins " -"%(votes)s votes" +msgid "Answered a question more than %(days)s days later with at least %(votes)s votes" +msgstr "A répondu à une question avec %(days)s jours de retard, et avec au moins %(votes)s votes" #: models/badges.py:525 msgid "Necromancer" @@ -3770,7 +3253,7 @@ msgstr "Première intervention" #: models/badges.py:623 msgid "Associate Editor" -msgstr "" +msgstr "Éditeur associé" #: models/badges.py:627 #, python-format @@ -3809,17 +3292,16 @@ msgstr "Question favorite" #: models/badges.py:710 msgid "Enthusiast" -msgstr "" +msgstr "Enthousiaste" #: models/badges.py:714 #, python-format msgid "Visited site every day for %(num)s days in a row" -msgstr "" +msgstr "Site visité tous les jours depuis %(num)s jours" #: models/badges.py:732 -#, fuzzy msgid "Commentator" -msgstr "Documentation" +msgstr "Commentateur" #: models/badges.py:736 #, python-format @@ -3848,42 +3330,33 @@ msgid "Sorry, this question has been deleted and is no longer accessible" msgstr "Désolé, cette question a été supprimée, et n'est plus accessible." #: models/post.py:1087 -msgid "" -"Sorry, the answer you are looking for is no longer available, because the " -"parent question has been removed" -msgstr "" +msgid "Sorry, the answer you are looking for is no longer available, because the parent question has been removed" +msgstr "Désolé, la réponse que vous demandez n'est plus disponible, car la question d'origine a été supprimée" #: models/post.py:1094 -#, fuzzy msgid "Sorry, this answer has been removed and is no longer accessible" msgstr "Désolé, cette question a été supprimée, et n'est plus accessible." #: models/post.py:1110 -msgid "" -"Sorry, the comment you are looking for is no longer accessible, because the " -"parent question has been removed" -msgstr "" +msgid "Sorry, the comment you are looking for is no longer accessible, because the parent question has been removed" +msgstr "Désolé, le commentaire que vous voulez voir n'est plus accessible, car la question d'origine a été supprimée" #: models/post.py:1117 -msgid "" -"Sorry, the comment you are looking for is no longer accessible, because the " -"parent answer has been removed" -msgstr "" +msgid "Sorry, the comment you are looking for is no longer accessible, because the parent answer has been removed" +msgstr "Désolé, le commentaire que vous voulez voir n'est plus accessible, car la réponse d'origine a été supprimée" #: models/question.py:54 #, python-format msgid "\" and \"%s\"" -msgstr "" +msgstr "\" et \"%s\"" #: models/question.py:57 -#, fuzzy msgid "\" and more" -msgstr "En savoir plus." +msgstr "\" et plus" #: models/reply_by_email.py:71 -#, fuzzy msgid "edited by email" -msgstr "Valider l'email" +msgstr "modifié par email" #: models/repute.py:143 #, python-format @@ -3892,21 +3365,13 @@ msgstr "<em>Modifié par un modérateur. Motif :</em> %(reason)s" #: models/repute.py:154 #, python-format -msgid "" -"%(points)s points were added for %(username)s's contribution to question " -"%(question_title)s" -msgstr "" -"%(points)s points ont été accordés pour la contribution de %(username)s à la " -"question \"%(question_title)s\"" +msgid "%(points)s points were added for %(username)s's contribution to question %(question_title)s" +msgstr "%(points)s points ont été accordés pour la contribution de %(username)s à la question \"%(question_title)s\"" #: models/repute.py:159 #, python-format -msgid "" -"%(points)s points were subtracted for %(username)s's contribution to " -"question %(question_title)s" -msgstr "" -"%(points)s points ont été retirés pour la contribution de %(username)s' à la " -"question \"%(question_title)s\"" +msgid "%(points)s points were subtracted for %(username)s's contribution to question %(question_title)s" +msgstr "%(points)s points ont été retirés pour la contribution de %(username)s' à la question \"%(question_title)s\"" #: models/tag.py:106 msgid "interesting" @@ -3953,26 +3418,22 @@ msgid "No email" msgstr "Aucun email" #: skins/common/templates/authopenid/authopenid_macros.html:63 -#, fuzzy msgid "Please enter your <span>user name</span>, then sign in" -msgstr "Veuillez saisir votre nom d'utilisateur et un mot de passe" +msgstr "Merci d'entrer votre <span>nom d'utilisateur</span>, puis de vous connecter" #: skins/common/templates/authopenid/authopenid_macros.html:64 #: skins/common/templates/authopenid/signin.html:97 -#, fuzzy msgid "(or select another login method above)" -msgstr "Merci de sélectionner une des options ci-dessus" +msgstr "(ou choisissez une autre méthode de connexion ci-dessus)" #: skins/common/templates/authopenid/authopenid_macros.html:66 #: skins/common/templates/authopenid/signin.html:113 -#, fuzzy msgid "Sign in" -msgstr "connexion/" +msgstr "Connexion" #: skins/common/templates/authopenid/changeemail.html:2 #: skins/common/templates/authopenid/changeemail.html:8 #: skins/common/templates/authopenid/changeemail.html:49 -#, fuzzy msgid "Change Email" msgstr "Changer d'email" @@ -3983,31 +3444,39 @@ msgstr "Sauvegarder votre adresse email" #: skins/common/templates/authopenid/changeemail.html:15 #, python-format msgid "" -"<span class=\\\"strong big\\\">Enter your new email into the box below</" -"span> if \n" +"<span class=\\\"strong big\\\">Enter your new email into the box below</span> if \n" "you'd like to use another email for <strong>update subscriptions</strong>.\n" "<br>Currently you are using <strong>%%(email)s</strong>" msgstr "" +"<span class=\\\"strong big\\\">Entrez votre email dans la zone ci-dessous</span> si \n" +"vous voulez utiliser une autre adresse pour <strong>mettre à jour les inscriptions</strong>.\n" +"<br>Actuellement, vous utilisez <strong>%%(email)s</strong>" #: skins/common/templates/authopenid/changeemail.html:19 #, python-format msgid "" -"<span class='strong big'>Please enter your email address in the box below.</" -"span>\n" +"<span class='strong big'>Please enter your email address in the box below.</span>\n" "Valid email address is required on this Q&A forum. If you like, \n" "you can <strong>receive updates</strong> on interesting questions or entire\n" "forum via email. Also, your email is used to create a unique \n" -"<a href='%%(gravatar_faq_url)s'><strong>gravatar</strong></a> image for " -"your\n" +"<a href='%%(gravatar_faq_url)s'><strong>gravatar</strong></a> image for your\n" "account. Email addresses are never shown or otherwise shared with anybody\n" "else." msgstr "" +"<span class='strong big'>Merci d'entrer votre adresse email dans la zone ci-dessous.</span>\n" +"Une adresse valide est nécessaire pour ce forum Q&R. Si vous voulez, \n" +"vous pouvez <strong>recevoir des mises à jour</strong> sur les questions intéressantes\n" +"ou la totalité du forum par email. De plus votre email est utilisé pour créer un unique \n" +"<a href='%%(gravatar_faq_url)s'><strong>gravatar</strong></a> pour votre\n" +"compte. Les adresses ne sont jamais affichées ou partagées avec quiconque." #: skins/common/templates/authopenid/changeemail.html:38 msgid "" "<strong>Your new Email:</strong> \n" "(will <strong>not</strong> be shown to anyone, must be valid)" msgstr "" +"<strong>Votre nouvel email :</strong> \n" +"(il <strong>ne sera</strong> donné à personne, il doit être valide)" #: skins/common/templates/authopenid/changeemail.html:49 msgid "Save Email" @@ -4032,17 +3501,19 @@ msgstr "Valider l'email" #: skins/common/templates/authopenid/changeemail.html:61 #, python-format msgid "" -"<span class=\\\"strong big\\\">An email with a validation link has been sent " -"to \n" -"%%(email)s.</span> Please <strong>follow the emailed link</strong> with " -"your \n" -"web browser. Email validation is necessary to help insure the proper use " -"of \n" -"email on <span class=\\\"orange\\\">Q&A</span>. If you would like to " -"use \n" +"<span class=\\\"strong big\\\">An email with a validation link has been sent to \n" +"%%(email)s.</span> Please <strong>follow the emailed link</strong> with your \n" +"web browser. Email validation is necessary to help insure the proper use of \n" +"email on <span class=\\\"orange\\\">Q&A</span>. If you would like to use \n" "<strong>another email</strong>, please <a \n" "href='%%(change_email_url)s'><strong>change it again</strong></a>." msgstr "" +"<span class=\\\"strong big\\\">Un email avec un lien de validation a été envoyé à \n" +"%%(email)s.</span> Merci <strong>de suivre ce lien</strong> avec votre navigateur\n" +"web. La validation par email est nécessaire pour s'assurer du bon fonctionnement \n" +"des emails sur <span class=\\\"orange\\\">Q&R</span>. Si vous voulez utiliser \n" +"<strong>une autre adresse</strong>, merci <a \n" +"href='%%(change_email_url)s'><strong>de la changer à nouveau</strong></a>." #: skins/common/templates/authopenid/changeemail.html:70 msgid "Email not changed" @@ -4051,12 +3522,15 @@ msgstr "Email inchangé" #: skins/common/templates/authopenid/changeemail.html:73 #, python-format msgid "" -"<span class=\\\"strong big\\\">Your email address %%(email)s has not been " -"changed.\n" +"<span class=\\\"strong big\\\">Your email address %%(email)s has not been changed.\n" "</span> If you decide to change it later - you can always do it by editing \n" "it in your user profile or by using the <a \n" "href='%%(change_email_url)s'><strong>previous form</strong></a> again." msgstr "" +"<span class=\\\"strong big\\\">Votre adresse email %%(email)s n'a pas été changée.\n" +"</span> Si vous décidez de le changer plus tard - vous pouvez toujours le\n" +"faire le changement dans votre profil utilisateur en utilisant de nouveau le <a \n" +"href='%%(change_email_url)s'><strong>formulaire précédent</strong></a>." #: skins/common/templates/authopenid/changeemail.html:80 msgid "Email changed" @@ -4066,13 +3540,16 @@ msgstr "Adresse email modifiée." #, python-format msgid "" "\n" -"<span class='big strong'>Your email address is now set to %%(email)s.</" -"span> \n" +"<span class='big strong'>Your email address is now set to %%(email)s.</span> \n" "Updates on the questions that you like most will be sent to this address. \n" -"Email notifications are sent once a day or less frequently - only when " -"there \n" +"Email notifications are sent once a day or less frequently - only when there \n" "are any news." msgstr "" +"\n" +"<span class='big strong'>Votre adresse email est maintenant %%(email)s.</span> \n" +"Les mises à jour des questions que vous préférez seront envoyées à cette adresse. \n" +"Les notifications sont envoyées quotidiennement voire moins souvent - uniquement quand\n" +"des nouvelles arrivent." #: skins/common/templates/authopenid/changeemail.html:91 msgid "Email verified" @@ -4080,20 +3557,21 @@ msgstr "Adresse email vérifiée." #: skins/common/templates/authopenid/changeemail.html:94 msgid "" -"<span class=\\\"big strong\\\">Thank you for verifying your email!</span> " -"Now \n" -"you can <strong>ask</strong> and <strong>answer</strong> questions. Also " -"if \n" +"<span class=\\\"big strong\\\">Thank you for verifying your email!</span> Now \n" +"you can <strong>ask</strong> and <strong>answer</strong> questions. Also if \n" "you find a very interesting question you can <strong>subscribe for the \n" -"updates</strong> - then will be notified about changes <strong>once a day</" -"strong>\n" +"updates</strong> - then will be notified about changes <strong>once a day</strong>\n" "or less frequently." msgstr "" +"<span class=\\\"big strong\\\">Merci d'avoir vérifié votre email !</span> Maintenant \n" +"vous pouvez <strong>poser</strong> et <strong>répondre</strong> à des questions. De plus, si \n" +"vous en trouvez une très intéressante vous pouvez vous <strong>inscrire aux\n" +"mises à jour</strong> - dont vous serez notifié <strong>une fois par jour</strong>\n" +"ou moins souvent." #: skins/common/templates/authopenid/changeemail.html:102 -#, fuzzy msgid "Validation email not sent" -msgstr "Valider l'email" +msgstr "Email de validation non envoyé" #: skins/common/templates/authopenid/changeemail.html:105 #, python-format @@ -4103,20 +3581,22 @@ msgid "" "href='%%(change_link)s'>change</a> email used for update subscriptions if \n" "necessary." msgstr "" +"<span class='big strong'>Votre adresse actuelle %%(email)s a été \n" +"validée précédemment</span> donc la nouvelle clef n'a pas été envoyée. Vous pouvez <a \n" +"href='%%(change_link)s'>changer</a> l'adresse utilisée pour les notifications de\n" +"mises à jour, si nécessaire." #: skins/common/templates/authopenid/complete.html:21 -#, fuzzy msgid "Registration" -msgstr "S'inscrire" +msgstr "Inscription" #: skins/common/templates/authopenid/complete.html:23 -#, fuzzy msgid "User registration" -msgstr "S'inscrire" +msgstr "Inscription de l'utilisateur" #: skins/common/templates/authopenid/complete.html:60 msgid "<strong>Receive forum updates by email</strong>" -msgstr "" +msgstr "<strong>Recevoir les mises à jour du forum par email</strong>" #: skins/common/templates/authopenid/complete.html:64 #: skins/common/templates/authopenid/signup_with_password.html:46 @@ -4134,9 +3614,8 @@ msgid "Thank you for registering at our Q&A forum!" msgstr "Merci de vous être inscrit sur notre forum de Questions/Réponses !" #: skins/common/templates/authopenid/confirm_email.txt:3 -#, fuzzy msgid "Your account details are:" -msgstr "Informations détaillées sur votre compte:" +msgstr "Les détails de votre compte :" #: skins/common/templates/authopenid/confirm_email.txt:5 msgid "Username:" @@ -4148,15 +3627,16 @@ msgstr "Mot de passe :" #: skins/common/templates/authopenid/confirm_email.txt:8 msgid "Please sign in here:" -msgstr "Merci de vous authentifiez :" +msgstr "Merci de vous authentifier :" #: skins/common/templates/authopenid/confirm_email.txt:11 #: skins/common/templates/authopenid/email_validation.txt:13 -#, fuzzy msgid "" "Sincerely,\n" "Q&A Forum Administrator" -msgstr "L'administrateur du forum" +msgstr "" +"Sincèrement,\n" +"L'administrateur du forum de Questions/Réponses" #: skins/common/templates/authopenid/email_validation.txt:1 msgid "Greetings from the Q&A forum" @@ -4168,20 +3648,16 @@ msgstr "Pour utiliser ce forum, utilisez le lien ci-dessous :" #: skins/common/templates/authopenid/email_validation.txt:7 msgid "Following the link above will help us verify your email address." -msgstr "" -"En cliquant sur le lien ci-dessus, vous allez nous permettre de vérifier la " -"validité de votre adresse email." +msgstr "En cliquant sur le lien ci-dessus, vous allez nous permettre de vérifier la validité de votre adresse email." #: skins/common/templates/authopenid/email_validation.txt:9 -#, fuzzy msgid "" "If you believe that this message was sent in mistake - \n" "no further action is needed. Just ignore this email, we apologize\n" "for any inconvenience" msgstr "" "Si vous pensez que ce message a été envoyé par erreur - \n" -"aucune action de votre part n'est nécessaire. Ignorez ce mail, et veuillez " -"accepter nos excuses \n" +"aucune action de votre part n'est nécessaire. Ignorez ce mail, et veuillez accepter nos excuses \n" "pour le dérangement que nous avons occasionné" #: skins/common/templates/authopenid/logout.html:3 @@ -4190,134 +3666,111 @@ msgstr "Déconnexion" #: skins/common/templates/authopenid/logout.html:5 msgid "You have successfully logged out" -msgstr "" +msgstr "Vous avez été déconnecté avec succès" #: skins/common/templates/authopenid/logout.html:7 -msgid "" -"However, you still may be logged in to your OpenID provider. Please logout " -"of your provider if you wish to do so." -msgstr "" +msgid "However, you still may be logged in to your OpenID provider. Please logout of your provider if you wish to do so." +msgstr "Cependant, vous êtes peut-être toujours connecté via votre fournisseur OpenID. Merci de vous déconnecter de celui-ci si vous le souhaitez." #: skins/common/templates/authopenid/signin.html:4 msgid "User login" msgstr "Veuillez vous authentifier avec votre \"OpenID\"" #: skins/common/templates/authopenid/signin.html:14 -#, fuzzy, python-format +#, python-format msgid "" "\n" " Your answer to %(title)s %(summary)s will be posted once you log in\n" " " msgstr "" "\n" -"<span class=\"strong big\">Votre réponse à la question </span> <i>\"<strong>" -"%(title)s</strong> %(summary)s...\"</i> <span class=\"strong big\">sera " -"publiée dès que vous vous serez authentifié.</span>" +" Votre réponse à la question %(title)s %(summary)s sera publiée dès que\n" +" vous vous serez connecté." #: skins/common/templates/authopenid/signin.html:21 -#, fuzzy, python-format +#, python-format msgid "" "Your question \n" " %(title)s %(summary)s will be posted once you log in\n" " " msgstr "" "Votre question \n" -" %(title)s %(summary)s sera publiée dès que vous vous serez " -"authentifié\n" -" " +" %(title)s %(summary)s sera publiée dès que vous vous\n" +" serez connecté\n" +" " #: skins/common/templates/authopenid/signin.html:28 -msgid "" -"Choose your favorite service below to sign in using secure OpenID or similar " -"technology. Your external service password always stays confidential and you " -"don't have to rememeber or create another one." -msgstr "" +msgid "Choose your favorite service below to sign in using secure OpenID or similar technology. Your external service password always stays confidential and you don't have to rememeber or create another one." +msgstr "Choisissez votre service de connexion préféré ci-dessous, en utilisant OpenID ou une technologie proche. Votre mot de passe de service externe reste confidentiel et vous n'avez pas à vous en souvenir ou en créer un autre." #: skins/common/templates/authopenid/signin.html:31 -msgid "" -"It's a good idea to make sure that your existing login methods still work, " -"or add a new one. Please click any of the icons below to check/change or add " -"new login methods." -msgstr "" +msgid "It's a good idea to make sure that your existing login methods still work, or add a new one. Please click any of the icons below to check/change or add new login methods." +msgstr "C'est une bonne idée de s'assurer que votre méthode de connexion actuelle fonctionne toujours, ou d'en ajouter une autre. Merci de cliquer sur l'une des icônes ci-dessous pour vérifier, changer ou ajouter de nouvelles méthodes." #: skins/common/templates/authopenid/signin.html:33 -msgid "" -"Please add a more permanent login method by clicking one of the icons below, " -"to avoid logging in via email each time." -msgstr "" +msgid "Please add a more permanent login method by clicking one of the icons below, to avoid logging in via email each time." +msgstr "Merci d'ajouter une méthode de connexion plus permanente en cliquant sur l'une des icônes ci-dessous, pour éviter de vous connecter par email à chaque fois." #: skins/common/templates/authopenid/signin.html:37 -msgid "" -"Click on one of the icons below to add a new login method or re-validate an " -"existing one." -msgstr "" +msgid "Click on one of the icons below to add a new login method or re-validate an existing one." +msgstr "Cliquez sur l'une des icônes ci-dessous pour ajouter une nouvelle méthode de connexion ou en re-valider une existante." #: skins/common/templates/authopenid/signin.html:39 -msgid "" -"You don't have a method to log in right now, please add one or more by " -"clicking any of the icons below." -msgstr "" +msgid "You don't have a method to log in right now, please add one or more by clicking any of the icons below." +msgstr "Vous n'avez pas de méthode pour vous connecter pour le moment, merci d'en ajouter au moins une en cliquant sur une des icônes ci-dessous." #: skins/common/templates/authopenid/signin.html:42 -msgid "" -"Please check your email and visit the enclosed link to re-connect to your " -"account" -msgstr "" +msgid "Please check your email and visit the enclosed link to re-connect to your account" +msgstr "Merci de vérifier votre boîte email et suivre le lien envoyé pour vous reconnecter à votre compte" #: skins/common/templates/authopenid/signin.html:89 -#, fuzzy msgid "or enter your <span>user name and password</span>, then sign in" -msgstr "Veuillez saisir votre nom d'utilisateur et un mot de passe" +msgstr "ou entrez votre <span>nom d'utilisateur et mot de passe</span>, puis connectez-vous" #: skins/common/templates/authopenid/signin.html:93 -#, fuzzy msgid "Please, sign in" -msgstr "Merci de vous authentifiez :" +msgstr "Merci de vous connecter :" #: skins/common/templates/authopenid/signin.html:100 msgid "Login failed, please try again" -msgstr "" +msgstr "Échec de la connexion, merci de réessayer" #: skins/common/templates/authopenid/signin.html:104 -#, fuzzy msgid "Login or email" -msgstr "Aucun email" +msgstr "Nom d'utilisateur ou email" -#: skins/common/templates/authopenid/signin.html:108 utils/forms.py:169 +#: skins/common/templates/authopenid/signin.html:108 +#: utils/forms.py:169 msgid "Password" msgstr "Mot de passe" #: skins/common/templates/authopenid/signin.html:120 msgid "To change your password - please enter the new one twice, then submit" -msgstr "" +msgstr "Pour changer votre mot de passe - merci de saisir deux fois le nouveau, puis valider" #: skins/common/templates/authopenid/signin.html:124 -#, fuzzy msgid "New password" -msgstr "Nouveau mot de passe pris en compte." +msgstr "Nouveau mot de passe" #: skins/common/templates/authopenid/signin.html:133 -#, fuzzy msgid "Please, retype" -msgstr "Merci de resaisir votre mot de passe" +msgstr "Merci de retaper" #: skins/common/templates/authopenid/signin.html:157 msgid "Here are your current login methods" -msgstr "" +msgstr "Voici vos méthodes de connexion actuelles" #: skins/common/templates/authopenid/signin.html:161 -#, fuzzy msgid "provider" -msgstr "Utilisateur certifié" +msgstr "fournisseur" #: skins/common/templates/authopenid/signin.html:162 -#, fuzzy msgid "last used" -msgstr "dernière connexion" +msgstr "dernière utilisation" #: skins/common/templates/authopenid/signin.html:163 msgid "delete, if you like" -msgstr "" +msgstr "supprimer, si vous le souhaitez" #: skins/common/templates/authopenid/signin.html:177 #: skins/common/templates/question/answer_controls.html:13 @@ -4326,46 +3779,40 @@ msgid "delete" msgstr "Supprimer" #: skins/common/templates/authopenid/signin.html:179 -#, fuzzy msgid "cannot be deleted" -msgstr "Compte supprimé." +msgstr "ne peut être supprimé" #: skins/common/templates/authopenid/signin.html:192 -#, fuzzy msgid "Still have trouble signing in?" -msgstr "D'autres questions ?" +msgstr "Toujours des difficultés à se connecter ?" #: skins/common/templates/authopenid/signin.html:197 msgid "Please, enter your email address below and obtain a new key" -msgstr "" +msgstr "Merci d'entrer votre adresse email ci-dessous et d'obtenir une nouvelle clef" #: skins/common/templates/authopenid/signin.html:199 msgid "Please, enter your email address below to recover your account" -msgstr "" +msgstr "Merci d'entrer votre adresse email ci-dessous pour récupérer votre compte" #: skins/common/templates/authopenid/signin.html:202 -#, fuzzy msgid "recover your account via email" -msgstr "Changer le mot de passe de votre compte" +msgstr "récupérez votre compte par email" #: skins/common/templates/authopenid/signin.html:213 msgid "Send a new recovery key" -msgstr "" +msgstr "Envoyer une clef de récupération" #: skins/common/templates/authopenid/signin.html:215 -#, fuzzy msgid "Recover your account via email" -msgstr "Changer le mot de passe de votre compte" +msgstr "Récupérer votre compte par email" #: skins/common/templates/authopenid/signup_with_password.html:10 -#, fuzzy msgid "Please register by clicking on any of the icons below" -msgstr "Merci de sélectionner une des options ci-dessus" +msgstr "Merci de vous enregistrer en cliquant sur l'une des icônes ci-dessous" #: skins/common/templates/authopenid/signup_with_password.html:23 -#, fuzzy msgid "or create a new user name and password here" -msgstr "Créer un nom d'utilisateur et un mot de passe" +msgstr "ou de créer un nouveau compte et mot de passe ici" #: skins/common/templates/authopenid/signup_with_password.html:25 msgid "Create login name and password" @@ -4379,18 +3826,19 @@ msgid "" "simply reuse your external login (e.g. Gmail or AOL) without ever sharing \n" "your login details with anyone and having to remember yet another password." msgstr "" +"<span class='strong big'>Si vous préférez, créez votre compte et mot de passe \n" +"ici. Cependant</span>, souvenez-vous que nous supportons aussi la méthode de connexion \n" +"<strong>OpenID</strong>. Avec <strong>OpenID</strong> vous pouvez facilement réutiliser\n" +"un compte externe (e.g. GMail, AOL, ...) sans jamais partager vos informations de connexion\n" +"ni avoir à vous souvenir d'un nouveau mot de passe" #: skins/common/templates/authopenid/signup_with_password.html:41 msgid "<strong>Receive periodic updates by email</strong>" -msgstr "" +msgstr "<strong>Recevoir les mises à jours périodiques par email</strong>" #: skins/common/templates/authopenid/signup_with_password.html:50 -msgid "" -"Please read and type in the two words below to help us prevent automated " -"account creation." -msgstr "" -"Merci de lire et saisir les deux mots ci-dessous pour nous aider à lutter " -"contre la création automatique de comptes (lutte contre le spam)." +msgid "Please read and type in the two words below to help us prevent automated account creation." +msgstr "Merci de lire et saisir les deux mots ci-dessous pour nous aider à lutter contre la création automatique de comptes (lutte contre le spam)." #: skins/common/templates/authopenid/signup_with_password.html:55 msgid "or" @@ -4401,69 +3849,59 @@ msgid "return to OpenID login" msgstr "retourner à la page d'authentification OpenID" #: skins/common/templates/avatar/add.html:3 -#, fuzzy msgid "add avatar" -msgstr "Qu'est ce que 'Gravatar' ?" +msgstr "ajouter un avatar" #: skins/common/templates/avatar/add.html:5 -#, fuzzy msgid "Change avatar" -msgstr "Modifier les tags" +msgstr "Changer d'avatar" #: skins/common/templates/avatar/add.html:6 #: skins/common/templates/avatar/change.html:7 -#, fuzzy msgid "Your current avatar: " -msgstr "Informations détaillées sur votre compte:" +msgstr "Votre avatar actuel :" #: skins/common/templates/avatar/add.html:9 #: skins/common/templates/avatar/change.html:11 msgid "You haven't uploaded an avatar yet. Please upload one now." -msgstr "" +msgstr "Vous n'avez pas encore envoyé d'avatar. Merci d'en envoyer un maintenant." #: skins/common/templates/avatar/add.html:13 msgid "Upload New Image" -msgstr "" +msgstr "Envoyer une nouvelle image" #: skins/common/templates/avatar/change.html:4 -#, fuzzy msgid "change avatar" -msgstr "Modifications enregistrées." +msgstr "changer d'avatar" #: skins/common/templates/avatar/change.html:17 msgid "Choose new Default" -msgstr "" +msgstr "Choisir un nouvel avatar par défaut" #: skins/common/templates/avatar/change.html:22 -#, fuzzy msgid "Upload" -msgstr "envoyer-sur-le-serveur/" +msgstr "Envoi" #: skins/common/templates/avatar/confirm_delete.html:2 -#, fuzzy msgid "delete avatar" -msgstr "réponse supprimée" +msgstr "supprimer l'avatar" #: skins/common/templates/avatar/confirm_delete.html:4 msgid "Please select the avatars that you would like to delete." -msgstr "" +msgstr "Merci de sélectionner l'avatar que vous voudriez supprimer." #: skins/common/templates/avatar/confirm_delete.html:6 #, python-format -msgid "" -"You have no avatars to delete. Please <a href=\"%(avatar_change_url)s" -"\">upload one</a> now." -msgstr "" +msgid "You have no avatars to delete. Please <a href=\"%(avatar_change_url)s\">upload one</a> now." +msgstr "Vous n'avez pas d'avatar à supprimer. Merci <a href=\"%(avatar_change_url)s\">d'en envoyer un</a> maintenant." #: skins/common/templates/avatar/confirm_delete.html:12 -#, fuzzy msgid "Delete These" -msgstr "réponse supprimée" +msgstr "Supprimer ceci" #: skins/common/templates/question/answer_controls.html:2 -#, fuzzy msgid "swap with question" -msgstr "Répondre à cette question" +msgstr "échanger avec une question" #: skins/common/templates/question/answer_controls.html:7 msgid "permanent link" @@ -4481,26 +3919,21 @@ msgid "undelete" msgstr "restaurer" #: skins/common/templates/question/answer_controls.html:19 -#, fuzzy msgid "remove offensive flag" -msgstr "Voir les drapeaux signalant des messages à contenu abusif" +msgstr "supprimer le drapeau offensif" #: skins/common/templates/question/answer_controls.html:21 #: skins/common/templates/question/question_controls.html:16 -#, fuzzy msgid "remove flag" -msgstr "Voir tous les mots-clés" +msgstr "supprimer le drapeau" #: skins/common/templates/question/answer_controls.html:26 #: skins/common/templates/question/answer_controls.html:35 #: skins/common/templates/question/question_controls.html:14 #: skins/common/templates/question/question_controls.html:20 #: skins/common/templates/question/question_controls.html:27 -msgid "" -"report as offensive (i.e containing spam, advertising, malicious text, etc.)" -msgstr "" -"Signaler un abus (par exemple : spam, publicité déguisée, contenus illégaux " -"ou inappropriés, propos déplacés)" +msgid "report as offensive (i.e containing spam, advertising, malicious text, etc.)" +msgstr "Signaler un abus (par exemple : spam, publicité déguisée, contenus illégaux ou inappropriés, propos déplacés)" #: skins/common/templates/question/answer_controls.html:28 #: skins/common/templates/question/answer_controls.html:37 @@ -4520,21 +3953,16 @@ msgstr "modifier" #: skins/common/templates/question/answer_vote_buttons.html:6 #: skins/default/templates/user_profile/user_stats.html:24 msgid "this answer has been selected as correct" -msgstr "Cette réponse a été sélectionnée comme correct" +msgstr "Cette réponse a été sélectionnée comme correcte" #: skins/common/templates/question/answer_vote_buttons.html:8 -#, fuzzy msgid "mark this answer as correct (click again to undo)" -msgstr "marquer cette réponse comme favorite (cliquez à nouveau pour annuler)" +msgstr "marquer cette réponse comme correcte (cliquez à nouveau pour annuler)" #: skins/common/templates/question/closed_question_info.html:2 -#, fuzzy, python-format -msgid "" -"The question has been closed for the following reason <b>\"%(close_reason)s" -"\"</b> <i>by" -msgstr "" -"Cette question a été close pour la raison suivante : : \"%(close_reason)s\" " -"par" +#, python-format +msgid "The question has been closed for the following reason <b>\"%(close_reason)s\"</b> <i>by" +msgstr "Cette question a été close pour la raison suivante : <b>« %(close_reason)s »</b> <i>par" #: skins/common/templates/question/closed_question_info.html:4 #, python-format @@ -4556,15 +3984,13 @@ msgid "retag" msgstr "requalifier" #: skins/common/templates/widgets/edit_post.html:22 -#, fuzzy msgid ", one of these is required" -msgstr "ce champ est obligatoire" +msgstr ", un de ceux-là est nécessaire" #: skins/common/templates/widgets/edit_post.html:31 #: skins/common/templates/widgets/edit_post.html:36 -#, fuzzy msgid "tags:" -msgstr "Mots-clés (tags)" +msgstr "mots-clés :" #: skins/common/templates/widgets/edit_post.html:32 msgid "(required)" @@ -4578,7 +4004,8 @@ msgstr "Basculer vers l'aperçu avec éditeur temps-réel" #: skins/common/templates/widgets/edit_post.html:60 #: skins/default/templates/answer_edit.html:61 #: skins/default/templates/answer_edit.html:64 -#: skins/default/templates/ask.html:49 skins/default/templates/ask.html:52 +#: skins/default/templates/ask.html:49 +#: skins/default/templates/ask.html:52 #: skins/default/templates/question_edit.html:73 #: skins/default/templates/question_edit.html:76 #: skins/default/templates/question/javascript.html:85 @@ -4589,7 +4016,7 @@ msgstr "Masquer l'aperçu" #: skins/common/templates/widgets/related_tags.html:3 #: skins/default/templates/tags.html:4 msgid "Tags" -msgstr "" +msgstr "mots-clés" #: skins/common/templates/widgets/tag_selector.html:4 msgid "Interesting tags" @@ -4597,9 +4024,8 @@ msgstr "Tags intéressants" #: skins/common/templates/widgets/tag_selector.html:19 #: skins/common/templates/widgets/tag_selector.html:36 -#, fuzzy msgid "add" -msgstr "Ajouter" +msgstr "ajouter" #: skins/common/templates/widgets/tag_selector.html:21 msgid "Ignored tags" @@ -4612,11 +4038,11 @@ msgstr "Filtre des tags" #: skins/default/templates/404.jinja.html:3 #: skins/default/templates/404.jinja.html:10 msgid "Page not found" -msgstr "" +msgstr "Page non trouvée" #: skins/default/templates/404.jinja.html:13 msgid "Sorry, could not find the page you requested." -msgstr "Désolé, la page que vous avez demandé est introuvable." +msgstr "Désolé, la page que vous avez demandée est introuvable." #: skins/default/templates/404.jinja.html:15 msgid "This might have happened for the following reasons:" @@ -4631,12 +4057,8 @@ msgid "url has error - please check it;" msgstr "l'URL comporte une erreur - merci de la vérifier;" #: skins/default/templates/404.jinja.html:19 -msgid "" -"the page you tried to visit is protected or you don't have sufficient " -"points, see" -msgstr "" -"la page que vous avez tenté d'afficher est protégée, ou vous n'avez pas un " -"nombre de points suffisants pour la voir" +msgid "the page you tried to visit is protected or you don't have sufficient points, see" +msgstr "la page que vous avez tenté d'afficher est protégée, ou vous n'avez pas un nombre de points suffisant pour la voir" #: skins/default/templates/404.jinja.html:19 #: skins/default/templates/widgets/footer.html:39 @@ -4645,8 +4067,7 @@ msgstr "FAQ" #: skins/default/templates/404.jinja.html:20 msgid "if you believe this error 404 should not have occured, please" -msgstr "" -"si vous pensez que cette erreur 404 n'aurait pas du se produire, merci de" +msgstr "si vous pensez que cette erreur 404 n'aurait pas dû se produire, merci de" #: skins/default/templates/404.jinja.html:21 msgid "report this problem" @@ -4669,19 +4090,15 @@ msgstr "Voir tous les mots-clés" #: skins/default/templates/500.jinja.html:3 #: skins/default/templates/500.jinja.html:5 msgid "Internal server error" -msgstr "" +msgstr "Erreur interne du serveur" #: skins/default/templates/500.jinja.html:8 msgid "system error log is recorded, error will be fixed as soon as possible" -msgstr "" -"L'erreur a été consigné dans les journaux d'erreurs système, et sera " -"corrigée dès que possible " +msgstr "L'erreur a été consigné dans les journaux d'erreurs système, et sera corrigée dès que possible " #: skins/default/templates/500.jinja.html:9 msgid "please report the error to the site administrators if you wish" -msgstr "" -"Si vous le souhaitez, vous pouvez signaler cette erreur aux administrateurs " -"du site. Merci" +msgstr "Si vous le souhaitez, vous pouvez signaler cette erreur aux administrateurs du site. Merci" #: skins/default/templates/500.jinja.html:12 msgid "see latest questions" @@ -4694,7 +4111,7 @@ msgstr "Voir les mots-clés (tags)" #: skins/default/templates/answer_edit.html:4 #: skins/default/templates/answer_edit.html:10 msgid "Edit answer" -msgstr "Modifier la réopnse" +msgstr "Modifier la réponse" #: skins/default/templates/answer_edit.html:10 #: skins/default/templates/question_edit.html:9 @@ -4710,7 +4127,7 @@ msgstr "Version" #: skins/default/templates/answer_edit.html:17 #: skins/default/templates/question_edit.html:16 msgid "select revision" -msgstr "Version auswählen" +msgstr "Choisir la version" #: skins/default/templates/answer_edit.html:24 #: skins/default/templates/question_edit.html:35 @@ -4727,11 +4144,11 @@ msgstr "Afficher l'aperçu" #: skins/default/templates/ask.html:4 #: skins/default/templates/widgets/ask_button.html:5 #: skins/default/templates/widgets/ask_form.html:43 -#, fuzzy msgid "Ask Your Question" msgstr "Poser votre question" -#: skins/default/templates/badge.html:5 skins/default/templates/badge.html:9 +#: skins/default/templates/badge.html:5 +#: skins/default/templates/badge.html:9 #: skins/default/templates/user_profile/user_recent.html:19 #: skins/default/templates/user_profile/user_stats.html:108 #, python-format @@ -4757,29 +4174,28 @@ msgstr "%(description)s" #: skins/default/templates/badge.html:14 msgid "user received this badge:" msgid_plural "users received this badge:" -msgstr[0] "l'utilisateur a reçu ces badges:" -msgstr[1] "les utilisateurs ont reçu ces badges:" +msgstr[0] "l'utilisateur a reçu ces badges :" +msgstr[1] "les utilisateurs ont reçu ces badges :" -#: skins/default/templates/badges.html:3 skins/default/templates/badges.html:5 +#: skins/default/templates/badges.html:3 +#: skins/default/templates/badges.html:5 msgid "Badges" msgstr "Badges" #: skins/default/templates/badges.html:7 msgid "Community gives you awards for your questions, answers and votes." -msgstr "" -"La communauté récompense vos questions, vos réponses et vos votes en vous " -"distribuant des badges." +msgstr "La communauté récompense vos questions, vos réponses et vos votes en vous distribuant des badges." #: skins/default/templates/badges.html:8 -#, fuzzy, python-format +#, python-format msgid "" "Below is the list of available badges and number \n" " of times each type of badge has been awarded. Have ideas about fun \n" "badges? Please, give us your <a href='%%(feedback_faq_url)s'>feedback</a>\n" msgstr "" -"Ci-dessous figure la liste des badges disponibles et le nombre de vois " -"qu'ils ont été attribués. Vous pouvez nous dire ce que vous en pensez sur " -"%(feedback_faq_url)s.\n" +"Ci-dessous figure la liste des badges disponibles\n" +" et le nombre de fois qu'ils ont été attribués. Si vous avez des idées\n" +"amusantes sur les badges, <a href=\"%%(feedback_faq_url)s\">dites-le nous</a>.\n" #: skins/default/templates/badges.html:36 msgid "Community badges" @@ -4787,32 +4203,31 @@ msgstr "Badges de la communauté" #: skins/default/templates/badges.html:38 msgid "gold badge: the highest honor and is very rare" -msgstr "" +msgstr "badge d'or : le plus haut niveau et très rare" #: skins/default/templates/badges.html:41 msgid "" -"Gold badge is the highest award in this community. To obtain it have to " -"show \n" +"Gold badge is the highest award in this community. To obtain it have to show \n" "profound knowledge and ability in addition to your active participation." msgstr "" +"Le badge d'or est le plus haut niveau atteignable. Pour l'obtenir vous devez\n" +"montrer de grandes connaissances en plus d'une importante participation." #: skins/default/templates/badges.html:47 -msgid "" -"silver badge: occasionally awarded for the very high quality contributions" -msgstr "" +msgid "silver badge: occasionally awarded for the very high quality contributions" +msgstr "badge d'argent : donné occasionnellement pour les contributions de très bonne qualité" #: skins/default/templates/badges.html:51 -msgid "" -"msgid \"silver badge: occasionally awarded for the very high quality " -"contributions" -msgstr "" +msgid "msgid \"silver badge: occasionally awarded for the very high quality contributions" +msgstr "msgid \"badge d'argent : donné occasionnellement pour les contributions de très bonne qualité" #: skins/default/templates/badges.html:54 #: skins/default/templates/badges.html:58 msgid "bronze badge: often given as a special honor" -msgstr "Badge bronze - blah blah" +msgstr "Badge bronze - souvent remis comme une faveur spéciale" -#: skins/default/templates/close.html:3 skins/default/templates/close.html:5 +#: skins/default/templates/close.html:3 +#: skins/default/templates/close.html:5 msgid "Close question" msgstr "Question close" @@ -4831,9 +4246,10 @@ msgstr "OK pour clore" #: skins/default/templates/faq_static.html:3 #: skins/default/templates/faq_static.html:5 #: skins/default/templates/widgets/answer_edit_tips.html:20 -#: skins/default/templates/widgets/question_edit_tips.html:16 views/meta.py:61 +#: skins/default/templates/widgets/question_edit_tips.html:16 +#: views/meta.py:61 msgid "FAQ" -msgstr "" +msgstr "FAQ" #: skins/default/templates/faq_static.html:5 msgid "Frequently Asked Questions " @@ -4844,46 +4260,28 @@ msgid "What kinds of questions can I ask here?" msgstr "Quel genre de questions puis-je poser ici ?" #: skins/default/templates/faq_static.html:7 -msgid "" -"Most importanly - questions should be <strong>relevant</strong> to this " -"community." -msgstr "" -"Surtout, les questions doivent être <strong>pertinentes</strong> et " -"<strong>significatives</strong> pour cette communauté." +msgid "Most importanly - questions should be <strong>relevant</strong> to this community." +msgstr "Surtout, les questions doivent être <strong>pertinentes</strong> et <strong>significatives</strong> pour cette communauté." #: skins/default/templates/faq_static.html:8 -#, fuzzy -msgid "" -"Before you ask - please make sure to search for a similar question. You can " -"search questions by their title or tags." -msgstr "" -"Avant de poser une question, merci d'utiliser notre moteur de recherche afin " -"de vérifier qu'elle n'a pas déjà été posée par quelqu'un d'autre" +msgid "Before you ask - please make sure to search for a similar question. You can search questions by their title or tags." +msgstr "Avant de poser une question - faites une recherche pour une question similaire. Vous pouvez chercher soit par titre soit par mots-clés." #: skins/default/templates/faq_static.html:10 -#, fuzzy msgid "What kinds of questions should be avoided?" msgstr "Quelles questions dois-je éviter de poser ?" #: skins/default/templates/faq_static.html:11 -msgid "" -"Please avoid asking questions that are not relevant to this community, too " -"subjective and argumentative." -msgstr "" -"Evitez de poser des questions qui ne sont pas pertinentes pour cette " -"communauté, ou quisont trop subjectives ou polémiques. " +msgid "Please avoid asking questions that are not relevant to this community, too subjective and argumentative." +msgstr "Evitez de poser des questions qui ne sont pas pertinentes pour cette communauté, ou qui sont trop subjectives ou polémiques. " #: skins/default/templates/faq_static.html:13 msgid "What should I avoid in my answers?" msgstr "Que dois-je éviter dans mes réponses ?" #: skins/default/templates/faq_static.html:14 -msgid "" -"is a <strong>question and answer</strong> site - <strong>it is not a " -"discussion group</strong>. Please avoid holding debates in your answers as " -"they tend to dilute the essense of questions and answers. For the brief " -"discussions please use commenting facility." -msgstr "" +msgid "is a <strong>question and answer</strong> site - <strong>it is not a discussion group</strong>. Please avoid holding debates in your answers as they tend to dilute the essense of questions and answers. For the brief discussions please use commenting facility." +msgstr "est un site <strong>de questions et réponses</strong> - <strong>ce n'est pas un groupe de discussion</strong>. Merci d'éviter d'entretenir des débats dans vos réponses car ils tendent à diluer l'intérêt des questions et réponses. Pour des discussions brèves, merci d'utiliser les outils de commentaires." #: skins/default/templates/faq_static.html:15 msgid "Who moderates this community?" @@ -4898,49 +4296,21 @@ msgid "This website is moderated by the users." msgstr "Ce site est modéré par ses utilisateurs." #: skins/default/templates/faq_static.html:18 -#, fuzzy -msgid "" -"Karma system allows users to earn rights to perform a variety of moderation " -"tasks" -msgstr "" -"Le système de réputation permet aux utilisateurs d'accumuler des points, qui " -"les autorisent ensuite à accéder à divers niveaux et tâches de modération" +msgid "Karma system allows users to earn rights to perform a variety of moderation tasks" +msgstr "Le système de réputation permet aux utilisateurs d'accumuler des points, qui les autorisent ensuite à accéder à divers niveaux et tâches de modération" #: skins/default/templates/faq_static.html:20 -#, fuzzy msgid "How does karma system work?" msgstr "Comment fonctionne le système de réputation ?" #: skins/default/templates/faq_static.html:21 -msgid "" -"When a question or answer is upvoted, the user who posted them will gain " -"some points, which are called \\\"karma points\\\". These points serve as a " -"rough measure of the community trust to him/her. Various moderation tasks " -"are gradually assigned to the users based on those points." -msgstr "" +msgid "When a question or answer is upvoted, the user who posted them will gain some points, which are called \\\"karma points\\\". These points serve as a rough measure of the community trust to him/her. Various moderation tasks are gradually assigned to the users based on those points." +msgstr "Quand une question ou une réponse reçoit un vote positif, l'utilisateur gagne des points appelés « points de karma ». Ces points servent pour mesurer la confiance de la communauté en lui/elle. Différentes tâches de modération sont graduellement données aux utilisateurs, en se basant sur ces points." #: skins/default/templates/faq_static.html:22 #, python-format -msgid "" -"For example, if you ask an interesting question or give a helpful answer, " -"your input will be upvoted. On the other hand if the answer is misleading - " -"it will be downvoted. Each vote in favor will generate <strong>" -"%(REP_GAIN_FOR_RECEIVING_UPVOTE)s</strong> points, each vote against will " -"subtract <strong>%(REP_LOSS_FOR_RECEIVING_DOWNVOTE)s</strong> points. There " -"is a limit of <strong>%(MAX_REP_GAIN_PER_USER_PER_DAY)s</strong> points that " -"can be accumulated for a question or answer per day. The table below " -"explains reputation point requirements for each type of moderation task." -msgstr "" -"Par exemple, si vous posez une question intéressante, ou aidez quelqu'un en " -"répondant à l'une de ses questions, les autres utilisateurs du forum " -"voteront positivement pour vous. En revanche, si vos réponses sont fausses " -"ou malveillantes, vous recevrez des votes négatifs. Chaque vote en votre " -"faveur vous rapportera <strong>%(REP_GAIN_FOR_RECEIVING_UPVOTE)s</strong> " -"points, et chaque vote en votre défaveur vous retirera <strong>" -"%(REP_LOSS_FOR_RECEIVING_DOWNVOTE)s</strong> points. Vous pouvez accumuler " -"au maximum <strong>%(MAX_REP_GAIN_PER_USER_PER_DAY)s</strong> points par " -"jour pour chaque question et chaque réponse. Le tableau ci-dessous indique " -"combien de points sont requis pour chaque tâche de modération. " +msgid "For example, if you ask an interesting question or give a helpful answer, your input will be upvoted. On the other hand if the answer is misleading - it will be downvoted. Each vote in favor will generate <strong>%(REP_GAIN_FOR_RECEIVING_UPVOTE)s</strong> points, each vote against will subtract <strong>%(REP_LOSS_FOR_RECEIVING_DOWNVOTE)s</strong> points. There is a limit of <strong>%(MAX_REP_GAIN_PER_USER_PER_DAY)s</strong> points that can be accumulated for a question or answer per day. The table below explains reputation point requirements for each type of moderation task." +msgstr "Par exemple, si vous posez une question intéressante, ou aidez quelqu'un en répondant à l'une de ses questions, les autres utilisateurs du forum voteront positivement pour vous. En revanche, si vos réponses sont fausses ou malveillantes, vous recevrez des votes négatifs. Chaque vote en votre faveur vous rapportera <strong>%(REP_GAIN_FOR_RECEIVING_UPVOTE)s</strong> points, et chaque vote en votre défaveur vous retirera <strong>%(REP_LOSS_FOR_RECEIVING_DOWNVOTE)s</strong> points. Vous pouvez accumuler au maximum <strong>%(MAX_REP_GAIN_PER_USER_PER_DAY)s</strong> points par jour pour chaque question et chaque réponse. Le tableau ci-dessous indique combien de points sont requis pour chaque tâche de modération. " #: skins/default/templates/faq_static.html:32 #: skins/default/templates/user_profile/user_votes.html:13 @@ -4957,9 +4327,8 @@ msgid "downvote" msgstr "vote négatif" #: skins/default/templates/faq_static.html:43 -#, fuzzy msgid " accept own answer to own questions" -msgstr "Erste Antwort auf eine eigene Frage akzeptiert" +msgstr "accepter ses propres réponses aux questions" #: skins/default/templates/faq_static.html:47 msgid "open and close own questions" @@ -4967,55 +4336,35 @@ msgstr "ouvrir ou fermer ses propres questions" #: skins/default/templates/faq_static.html:51 msgid "retag other's questions" -msgstr "" -"requalifier les questions d'autres utilisateurs (modifier leurs mots-clés)" +msgstr "requalifier les questions d'autres utilisateurs (modifier leurs mots-clés)" #: skins/default/templates/faq_static.html:56 msgid "edit community wiki questions" msgstr "Modifier les questions du \"Wiki communautaire\"." #: skins/default/templates/faq_static.html:61 -#, fuzzy msgid "edit any answer" -msgstr "\"modifier n'importe quelle réponse" +msgstr "éditer n'importe quelle réponse" #: skins/default/templates/faq_static.html:65 -#, fuzzy msgid "delete any comment" -msgstr "\"supprimer n'importe quel commentaire" +msgstr "supprimer n'importe quel commentaire" #: skins/default/templates/faq_static.html:69 msgid "How to change my picture (gravatar) and what is gravatar?" -msgstr "" +msgstr "Comment changer mon image (gravatar) et qu'est-ce qu'un gravatar ?" #: skins/default/templates/faq_static.html:70 -msgid "" -"<p>The picture that appears on the users profiles is called " -"<strong>gravatar</strong> (which means <strong>g</strong>lobally <strong>r</" -"strong>ecognized <strong>avatar</strong>).</p><p>Here is how it works: a " -"<strong>cryptographic key</strong> (unbreakable code) is calculated from " -"your email address. You upload your picture (or your favorite alter ego " -"image) the website <a href='http://gravatar.com'><strong>gravatar.com</" -"strong></a> from where we later retreive your image using the key.</" -"p><p>This way all the websites you trust can show your image next to your " -"posts and your email address remains private.</p><p>Please " -"<strong>personalize your account</strong> with an image - just register at " -"<a href='http://gravatar.com'><strong>gravatar.com</strong></a> (just please " -"be sure to use the same email address that you used to register with us). " -"Default image that looks like a kitchen tile is generated automatically.</p>" -msgstr "" +msgid "<p>The picture that appears on the users profiles is called <strong>gravatar</strong> (which means <strong>g</strong>lobally <strong>r</strong>ecognized <strong>avatar</strong>).</p><p>Here is how it works: a <strong>cryptographic key</strong> (unbreakable code) is calculated from your email address. You upload your picture (or your favorite alter ego image) the website <a href='http://gravatar.com'><strong>gravatar.com</strong></a> from where we later retreive your image using the key.</p><p>This way all the websites you trust can show your image next to your posts and your email address remains private.</p><p>Please <strong>personalize your account</strong> with an image - just register at <a href='http://gravatar.com'><strong>gravatar.com</strong></a> (just please be sure to use the same email address that you used to register with us). Default image that looks like a kitchen tile is generated automatically.</p>" +msgstr "<p>L'image qui apparait sur les profils utilisateurs est appelée <strong>gravatar</strong> (qui signifie <strong>g</strong>lobally <strong>r</strong>ecognized <strong>avatar</strong>).</p><p>Voici le fonctionnement : une <strong>clef cryptographique</strong> est calculée à partir de votre adresse email. Vous envoyez votre image sur le site <a href='http://gravatar.com'><strong>gravatar.com</strong></a> depuis lequel nous récupérons l'image grâce à la clef.</p><p>Ainsi tous les sites web auxquels vous accordez votre confiance affichent votre image à côté de vos messages et votre adresse reste privée.</p><p>Merci <strong>de personnaliser votre compte</strong> avec une image - enregistrez-vous sur <a href='http://gravatar.com'><strong>gravatar.com</strong></a> (assurez-vous d'utiliser la même adresse email que lors de votre inscription ici). L'image par défaut est générée automatiquement.</p>" #: skins/default/templates/faq_static.html:71 msgid "To register, do I need to create new password?" -msgstr "Ais-je besoin de créer un nouveau mot de passe pour m'inscrire ?" +msgstr "Ai-je besoin de créer un nouveau mot de passe pour m'inscrire ?" #: skins/default/templates/faq_static.html:72 -msgid "" -"No, you don't have to. You can login through any service that supports " -"OpenID, e.g. Google, Yahoo, AOL, etc.\"" -msgstr "" -"Non. Ce n'est pas obligatoire. Vous pouvez vous connecter avec n'importe " -"quel service compatible OpenID, ex: Google, Yahoo, AOL, etc..\"" +msgid "No, you don't have to. You can login through any service that supports OpenID, e.g. Google, Yahoo, AOL, etc.\"" +msgstr "Non. Ce n'est pas obligatoire. Vous pouvez vous connecter avec n'importe quel service compatible OpenID, ex: Google, Yahoo, AOL, etc..\"" #: skins/default/templates/faq_static.html:73 msgid "\"Login now!\"" @@ -5023,24 +4372,15 @@ msgstr "\"Je me connecte immédiatement !\"" #: skins/default/templates/faq_static.html:75 msgid "Why other people can edit my questions/answers?" -msgstr "" -"Pourquoi les autres utilisateurs peuvent-ils modifier mes questions ou mes " -"réponses ?" +msgstr "Pourquoi les autres utilisateurs peuvent-ils modifier mes questions ou mes réponses ?" #: skins/default/templates/faq_static.html:76 msgid "Goal of this site is..." msgstr "L'objectif de ce site est simple :" #: skins/default/templates/faq_static.html:76 -msgid "" -"So questions and answers can be edited like wiki pages by experienced users " -"of this site and this improves the overall quality of the knowledge base " -"content." -msgstr "" -"Les questions et les réponses peuvent être modifiées (sur le principe d'un " -"wiki) par les utilisateurs habitués du site, ce qui améliore la qualité " -"globale de la base de connaissances qui est peu à peu constituée par notre " -"communauté." +msgid "So questions and answers can be edited like wiki pages by experienced users of this site and this improves the overall quality of the knowledge base content." +msgstr "Les questions et les réponses peuvent être modifiées (sur le principe d'un wiki) par les utilisateurs habitués du site, ce qui améliore la qualité globale de la base de connaissances qui est peu à peu constituée par notre communauté." #: skins/default/templates/faq_static.html:77 msgid "If this approach is not for you, we respect your choice." @@ -5051,14 +4391,9 @@ msgid "Still have questions?" msgstr "D'autres questions ?" #: skins/default/templates/faq_static.html:80 -#, fuzzy, python-format -msgid "" -"Please <a href='%%(ask_question_url)s'>ask</a> your question, help make our " -"community better!" -msgstr "" -"Merci de <a href='%(ask_question_url)s'>poser vos questions ici</a>; vous " -"nous aiderez ainsi à étoffer notre base de connaissances, dans l'intérêt de " -"toute la communauté." +#, python-format +msgid "Please <a href='%%(ask_question_url)s'>ask</a> your question, help make our community better!" +msgstr "Merci de <a href='%%(ask_question_url)s'>poser vos questions ici</a> ; vous nous aiderez ainsi à étoffer notre base de connaissances, dans l'intérêt de toute la communauté." #: skins/default/templates/feedback.html:3 msgid "Feedback" @@ -5069,36 +4404,33 @@ msgid "Give us your feedback!" msgstr "Envoyez nous vos remarques !" #: skins/default/templates/feedback.html:14 -#, fuzzy, python-format +#, python-format msgid "" "\n" -" <span class='big strong'>Dear %(user_name)s</span>, we look forward " -"to hearing your feedback. \n" +" <span class='big strong'>Dear %(user_name)s</span>, we look forward to hearing your feedback. \n" " Please type and send us your message below.\n" " " msgstr "" "\n" -"<span class='big strong'>Bonjour %(user_name)s</span>, nous sommes " -"impatients de connaître votre avis sur notre module de Questions/Réponses.\n" -"Saisissez vos remarques, critiques ou suggestions ci-dessous." +" <span class='big strong'>Bonjour %(user_name)s</span>, nous sommes impatients de connaître votre avis sur notre module de Questions/Réponses.\n" +" Saisissez vos remarques, critiques ou suggestions ci-dessous.\n" +" " #: skins/default/templates/feedback.html:21 -#, fuzzy msgid "" "\n" -" <span class='big strong'>Dear visitor</span>, we look forward to " -"hearing your feedback.\n" +" <span class='big strong'>Dear visitor</span>, we look forward to hearing your feedback.\n" " Please type and send us your message below.\n" " " msgstr "" "\n" -"<span class='big strong'>Cher visiteur</span>, nous sommes impatients de " -"connaître votre avis sur notre module de Questions/Réponses.\n" -"Saisissez vos remarques, critiques ou suggestions ci-dessous." +" <span class='big strong'>Cher visiteur</span>, nous sommes impatients de connaître votre avis sur notre module de Questions/Réponses.\n" +" Saisissez vos remarques, critiques ou suggestions ci-dessous.\n" +" " #: skins/default/templates/feedback.html:30 msgid "(to hear from us please enter a valid email or check the box below)" -msgstr "" +msgstr "(pour avoir des nouvelles de notre part, merci d'utiliser une adresse email valide ou de cocher la case ci-dessous)" #: skins/default/templates/feedback.html:37 #: skins/default/templates/feedback.html:46 @@ -5107,64 +4439,60 @@ msgstr "(champ obligatoire)" #: skins/default/templates/feedback.html:55 msgid "(Please solve the captcha)" -msgstr "" +msgstr "(Merci de résoudre le captcha)" #: skins/default/templates/feedback.html:63 msgid "Send Feedback" msgstr "Envoyer" #: skins/default/templates/feedback_email.txt:2 -#, fuzzy, python-format +#, python-format msgid "" "\n" "Hello, this is a %(site_title)s forum feedback message.\n" msgstr "" "\n" -"Bonjour, un utilisateur du forum a souhaité faire une remarque sur : " -"%(site_title)s \n" +"Bonjour, un utilisateur du forum a souhaité faire une remarque sur : %(site_title)s \n" -#: skins/default/templates/help.html:2 skins/default/templates/help.html:4 +#: skins/default/templates/help.html:2 +#: skins/default/templates/help.html:4 msgid "Help" -msgstr "" +msgstr "Aide" #: skins/default/templates/help.html:7 -#, fuzzy, python-format +#, python-format msgid "Welcome %(username)s," -msgstr "réponses pour %(username)s" +msgstr "Bienvenue %(username)s," #: skins/default/templates/help.html:9 msgid "Welcome," -msgstr "" +msgstr "Bienvenu," #: skins/default/templates/help.html:13 #, python-format msgid "Thank you for using %(app_name)s, here is how it works." -msgstr "" +msgstr "Merci d'utiliser %(app_name)s, voici le fonctionnement." #: skins/default/templates/help.html:16 -msgid "" -"This site is for asking and answering questions, not for open-ended " -"discussions." -msgstr "" +msgid "This site is for asking and answering questions, not for open-ended discussions." +msgstr "Ce site permet de poser et de répondre à des questions, il ne vise pas des discussions." #: skins/default/templates/help.html:17 -msgid "" -"We encourage everyone to use “question” space for asking and “answer” for " -"answering." -msgstr "" +msgid "We encourage everyone to use “question” space for asking and “answer” for answering." +msgstr "Nous encourageons tout le monde à utiliser l'espace « question » pour demander et « réponse » pour répondre." #: skins/default/templates/help.html:20 msgid "" "Despite that, each question and answer can be commented – \n" " the comments are good for the limited discussions." msgstr "" +"Malgré ça, chaque question et réponse peut être commentée -\n" +" les commentaires sont bons pour des discussions limitées." #: skins/default/templates/help.html:24 #, python-format -msgid "" -"Voting in %(app_name)s helps to select best answers and thank most helpful " -"users." -msgstr "" +msgid "Voting in %(app_name)s helps to select best answers and thank most helpful users." +msgstr "Voter dans %(app_name)s permet de sélectionner les meilleurs réponses et remercier les utilisateurs les plus aidant." #: skins/default/templates/help.html:26 #, python-format @@ -5172,29 +4500,34 @@ msgid "" "Please vote when you find helpful information,\n" " it really helps the %(app_name)s community." msgstr "" +"Merci de voter quand vous trouvez une information utile,\n" +" cela aide grandement la communauté de %(app_name)s." #: skins/default/templates/help.html:29 msgid "" -"Besides, you can @mention users anywhere in the text to point their " -"attention,\n" -" follow users and conversations and report inappropriate content by " -"flagging it." +"Besides, you can @mention users anywhere in the text to point their attention,\n" +" follow users and conversations and report inappropriate content by flagging it." msgstr "" +"Vous pouvez également @mentionner les utilisateurs n'importe où dans le texte pour\n" +" attirer leur attention, suivre les utilisateurs et conversations et rapporter les\n" +" contenus inappropriés en les notifiant." #: skins/default/templates/help.html:32 msgid "Enjoy." -msgstr "" +msgstr "Profitez." #: skins/default/templates/import_data.html:2 #: skins/default/templates/import_data.html:4 msgid "Import StackExchange data" -msgstr "" +msgstr "Importer les données StackExchange" #: skins/default/templates/import_data.html:13 msgid "" "<em>Warning:</em> if your database is not empty, please back it up\n" " before attempting this operation." msgstr "" +"<em>Attention :</em> si votre base de données n'est pas vide, faites\n" +" une sauvegarde avant de continuer." #: skins/default/templates/import_data.html:16 msgid "" @@ -5203,17 +4536,22 @@ msgid "" " Please note that feedback will be printed in plain text.\n" " " msgstr "" +"Envoyez votre archive zip stackexchange, puis attendez que les données\n" +" soient traitées et importées. Ce processus peut prendre plusieurs minutes.\n" +" Les informations suite à l'import seront affichées en texte brut.\n" +" " #: skins/default/templates/import_data.html:25 msgid "Import data" -msgstr "" +msgstr "Importer les données" #: skins/default/templates/import_data.html:27 msgid "" "In the case you experience any difficulties in using this import tool,\n" -" please try importing your data via command line: <code>python manage." -"py load_stackexchange path/to/your-data.zip</code>" +" please try importing your data via command line: <code>python manage.py load_stackexchange path/to/your-data.zip</code>" msgstr "" +"Au cas où vous rencontreriez des difficultés pendant l'utilisation de cet outil d'import,\n" +" merci d'essayer de faire l'import via la ligne de commande : <code>python manage.py load_stackexchange chemin/vers/vos-donnees.zip</code>" #: skins/default/templates/instant_notification.html:1 #, python-format @@ -5224,23 +4562,19 @@ msgstr "<p>Bonjour %(receiving_user_name)s,</p>" #, python-format msgid "" "\n" -"<p>%(update_author_name)s left a <a href=\"%(post_url)s\">new comment</a>:</" -"p>\n" +"<p>%(update_author_name)s left a <a href=\"%(post_url)s\">new comment</a>:</p>\n" msgstr "" "\n" -"<p>%(update_author_name)s a laissé <a href=\"%(post_url)s\">un nouveau " -"commentaire</a>:</p>\n" +"<p>%(update_author_name)s a laissé <a href=\"%(post_url)s\">un nouveau commentaire</a>:</p>\n" #: skins/default/templates/instant_notification.html:8 #, python-format msgid "" "\n" -"<p>%(update_author_name)s left a <a href=\"%(post_url)s\">new comment</a></" -"p>\n" +"<p>%(update_author_name)s left a <a href=\"%(post_url)s\">new comment</a></p>\n" msgstr "" "\n" -"<p>%(update_author_name)s a laissé <a href=\"%(post_url)s\">un nouveau " -"commentaire</a></p>\n" +"<p>%(update_author_name)s a laissé <a href=\"%(post_url)s\">un nouveau commentaire</a></p>\n" #: skins/default/templates/instant_notification.html:13 #, python-format @@ -5287,26 +4621,21 @@ msgstr "" "<a href=\"%(post_url)s\">%(origin_post_title)s</a></p>\n" #: skins/default/templates/instant_notification.html:37 -#, fuzzy, python-format +#, python-format msgid "" "\n" "<div>%(content_preview)s</div>\n" -"<p>Please note - you can easily <a href=\"%(user_subscriptions_url)s" -"\">change</a>\n" -"how often you receive these notifications or unsubscribe. Thank you for your " -"interest in our forum!</p>\n" +"<p>Please note - you can easily <a href=\"%(user_subscriptions_url)s\">change</a>\n" +"how often you receive these notifications or unsubscribe. Thank you for your interest in our forum!</p>\n" msgstr "" "\n" -"<p>Sachez que vous pouvez facilement <a href=\"%(user_subscriptions_url)s" -"\">changer</a>\n" -"la fréquence à laquelle vous recevez les emails de notifications, ou " -"facilement vous désabonner. Merci de l'intérêt que vous protez à notre " -"forum !</p>\n" +"<div>%(content_preview)s</div>\n" +"<p>Sachez que vous pouvez facilement <a href=\"%(user_subscriptions_url)s\">changer</a>\n" +"la fréquence à laquelle vous recevez les emails de notifications, ou facilement vous désabonner. Merci de l'intérêt que vous portez à notre forum !</p>\n" #: skins/default/templates/instant_notification.html:42 msgid "<p>Sincerely,<br/>Forum Administrator</p>" -msgstr "" -"<p>Salutations,<br/>L'administarteur du forum de Questions/Réponses</p>" +msgstr "<p>Salutations,<br/>L'administrateur du forum de Questions/Réponses</p>" #: skins/default/templates/instant_notification_reply_by_email.html:3 msgid "" @@ -5314,39 +4643,43 @@ msgid "" "\n" "======= Reply above this line. ====-=-=\n" msgstr "" +"\n" +"\n" +"======= Répondre au dessus de cette ligne. ====-=-=\n" #: skins/default/templates/instant_notification_reply_by_email.html:8 #, python-format msgid "" "\n" -"You can post an answer or a comment by replying to email notifications. To " -"do that\n" -"you need %(reply_by_email_karma_threshold)s karma, you have " -"%(receiving_user_karma)s karma. \n" +"You can post an answer or a comment by replying to email notifications. To do that\n" +"you need %(reply_by_email_karma_threshold)s karma, you have %(receiving_user_karma)s karma. \n" msgstr "" +"\n" +"Vous pouvez envoyer une réponse ou un commentaire en répondant à la notification par email. Pour le faire\n" +"vous devez avoir au moins %(reply_by_email_karma_threshold)s points de karma, vous avez actuellement %(receiving_user_karma)s karma. \n" #: skins/default/templates/macros.html:5 -#, fuzzy, python-format +#, python-format msgid "Share this question on %(site)s" -msgstr "Partager cette question sur twitter" +msgstr "Partager cette question sur %(site)s" #: skins/default/templates/macros.html:16 #: skins/default/templates/macros.html:436 #, python-format msgid "follow %(alias)s" -msgstr "" +msgstr "suivre %(alias)s" #: skins/default/templates/macros.html:19 #: skins/default/templates/macros.html:439 #, python-format msgid "unfollow %(alias)s" -msgstr "" +msgstr "ne plus suivre %(alias)s" #: skins/default/templates/macros.html:20 #: skins/default/templates/macros.html:440 #, python-format msgid "following %(alias)s" -msgstr "" +msgstr "suivi %(alias)s" #: skins/default/templates/macros.html:33 msgid "current number of votes" @@ -5358,7 +4691,7 @@ msgstr "anonyme" #: skins/default/templates/macros.html:79 msgid "this post is marked as community wiki" -msgstr "" +msgstr "ce message est marqué comme wiki communauté" #: skins/default/templates/macros.html:82 #, python-format @@ -5366,6 +4699,8 @@ msgid "" "This post is a wiki.\n" " Anyone with karma >%(wiki_min_rep)s is welcome to improve it." msgstr "" +"Ce message est un wiki.\n" +" N'importe qui avec au moins %(wiki_min_rep)s points de karma est bienvenu pour l'améliorer." #: skins/default/templates/macros.html:88 msgid "asked" @@ -5392,7 +4727,8 @@ msgstr "Voir les questions marquées par '%(tag)s'." msgid "delete this comment" msgstr "Supprimer ce commentaire" -#: skins/default/templates/macros.html:507 templatetags/extra_tags.py:43 +#: skins/default/templates/macros.html:507 +#: templatetags/extra_tags.py:43 #, python-format msgid "%(username)s gravatar image" msgstr "Image Gravatar de %(username)s" @@ -5418,9 +4754,9 @@ msgstr "page actuelle" #: skins/default/templates/macros.html:552 #: skins/default/templates/macros.html:584 #: skins/default/templates/macros.html:591 -#, fuzzy, python-format +#, python-format msgid "page %(num)s" -msgstr "Page %(num)s" +msgstr "page %(num)s" #: skins/default/templates/macros.html:556 #: skins/default/templates/macros.html:595 @@ -5433,10 +4769,10 @@ msgid "responses for %(username)s" msgstr "réponses pour %(username)s" #: skins/default/templates/macros.html:610 -#, fuzzy, python-format +#, python-format msgid "you have %(response_count)s new response" msgid_plural "you have %(response_count)s new responses" -msgstr[0] "vous avez une nouvelle réponse" +msgstr[0] "vous avez %(response_count)s nouvelle réponse" msgstr[1] "vous avez %(response_count)s nouvelles réponses" #: skins/default/templates/macros.html:613 @@ -5446,9 +4782,9 @@ msgstr "pas de nouvelles réponses pour l'instant" # FIXME #: skins/default/templates/macros.html:628 #: skins/default/templates/macros.html:629 -#, fuzzy, python-format +#, python-format msgid "%(new)s new flagged posts and %(seen)s previous" -msgstr "%(new)s nouveaux messages étiquetés" +msgstr "%(new)s nouveaux messages étiquetés et %(seen)s précédents" # FIXME #: skins/default/templates/macros.html:631 @@ -5469,20 +4805,17 @@ msgid "Questions" msgstr "Questions" #: skins/default/templates/question.html:110 -#, fuzzy msgid "post a comment / <strong>some</strong> more" -msgstr "Voir <strong>1</strong> de plus" +msgstr "envoyer un commentaire / <strong>encore</strong> plus" #: skins/default/templates/question.html:113 -#, fuzzy msgid "see <strong>some</strong> more" -msgstr "Voir <strong>1</strong> de plus" +msgstr "voir <strong>encore</strong> plus" #: skins/default/templates/question.html:117 #: skins/default/templates/question/javascript.html:20 -#, fuzzy msgid "post a comment" -msgstr "ajouter des commentaires" +msgstr "envoyer un commentaire" #: skins/default/templates/question.html:135 #: skins/default/templates/question/content.html:40 @@ -5490,15 +4823,13 @@ msgid "Answer Your Own Question" msgstr "Répondre à votre propre question" #: skins/default/templates/question.html:140 -#, fuzzy msgid "Post Your Answer" -msgstr "Votre réponse" +msgstr "Envoyer votre réponse" #: skins/default/templates/question.html:146 #: skins/default/templates/widgets/ask_form.html:41 -#, fuzzy msgid "Login/Signup to Post" -msgstr "Connectez vous (ou Inscrivez vous) pour poster votre réponse" +msgstr "Connexion pour envoyer" #: skins/default/templates/question_edit.html:4 #: skins/default/templates/question_edit.html:9 @@ -5507,9 +4838,8 @@ msgstr "Modifier une question" #: skins/default/templates/question_retag.html:3 #: skins/default/templates/question_retag.html:5 -#, fuzzy msgid "Retag question" -msgstr "Questions liées" +msgstr "Changer les mots-clés de la question" #: skins/default/templates/question_retag.html:21 msgid "Retag" @@ -5521,18 +4851,18 @@ msgstr "Pourquoi utiliser et modifier les tags ?" #: skins/default/templates/question_retag.html:30 msgid "Tags help to keep the content better organized and searchable" -msgstr "" +msgstr "Les mots-clés aident à conserver le contenu organisé et cherchable" #: skins/default/templates/question_retag.html:32 msgid "tag editors receive special awards from the community" -msgstr "" -"Les éditeurs de tags reçoivent des récompenses de la base de la communauté" +msgstr "Les éditeurs de tags reçoivent des récompenses de la base de la communauté" #: skins/default/templates/question_retag.html:59 msgid "up to 5 tags, less than 20 characters each" msgstr "jusqu'à 5 tags, faisant chacun moins de 20 caractères" -#: skins/default/templates/reopen.html:3 skins/default/templates/reopen.html:5 +#: skins/default/templates/reopen.html:3 +#: skins/default/templates/reopen.html:5 msgid "Reopen question" msgstr "Réouvrir cette question" @@ -5568,9 +4898,10 @@ msgstr "Réouvrir cette question" #: skins/default/templates/reply_by_email_error.html:1 msgid "" "\n" -"<p>The system was unable to process your message successfully, the reason " -"being:<p>\n" +"<p>The system was unable to process your message successfully, the reason being:<p>\n" msgstr "" +"\n" +"<p>Le système a été incapable de traiter votre message avec succès, la raison étant :</p>\n" #: skins/default/templates/revisions.html:4 #: skins/default/templates/revisions.html:7 @@ -5588,34 +4919,31 @@ msgstr "révision %(number)s" #: skins/default/templates/subscribe_for_tags.html:3 #: skins/default/templates/subscribe_for_tags.html:5 -#, fuzzy msgid "Subscribe for tags" -msgstr "utiliser les mots-clés (tags)" +msgstr "S'inscrire pour les mots-clés" #: skins/default/templates/subscribe_for_tags.html:6 -#, fuzzy msgid "Please, subscribe for the following tags:" -msgstr "Cette question a été close pour la raison suivante " +msgstr "Merci de vous inscrire aux mots-clés :" #: skins/default/templates/subscribe_for_tags.html:15 -#, fuzzy msgid "Subscribe" -msgstr "utiliser les mots-clés (tags)" +msgstr "S'inscrire" #: skins/default/templates/tags.html:8 #, python-format msgid "Tags, matching \"%(stag)s\"" -msgstr "" +msgstr "mots-clés, correspondant à « %(stag)s »" #: skins/default/templates/tags.html:10 msgid "Tag list" msgstr "Liste des tags" -#: skins/default/templates/tags.html:14 skins/default/templates/users.html:9 +#: skins/default/templates/tags.html:14 +#: skins/default/templates/users.html:9 #: skins/default/templates/main_page/tab_bar.html:15 -#, fuzzy msgid "Sort by »" -msgstr "Trier par:" +msgstr "Trier par »" #: skins/default/templates/tags.html:19 msgid "sorted alphabetically" @@ -5633,28 +4961,30 @@ msgstr "triée par fréquence d'utilisation des tags" msgid "by popularity" msgstr "par popularité" -#: skins/default/templates/tags.html:31 skins/default/templates/tags.html:56 +#: skins/default/templates/tags.html:31 +#: skins/default/templates/tags.html:56 msgid "Nothing found" msgstr "Aucun résultat" -#: skins/default/templates/users.html:4 skins/default/templates/users.html:6 +#: skins/default/templates/users.html:4 +#: skins/default/templates/users.html:6 msgid "Users" msgstr "Utilisateurs" #: skins/default/templates/users.html:14 msgid "see people with the highest reputation" -msgstr "" +msgstr "voir les utilisateurs avec la plus haute réputation" #: skins/default/templates/users.html:15 #: skins/default/templates/user_profile/user_info.html:25 #: skins/default/templates/user_profile/user_reputation.html:4 #: skins/default/templates/user_profile/user_tabs.html:23 msgid "karma" -msgstr "" +msgstr "karma" #: skins/default/templates/users.html:20 msgid "see people who joined most recently" -msgstr "" +msgstr "voir les utilisateurs nouvellement inscrits" #: skins/default/templates/users.html:21 msgid "recent" @@ -5662,11 +4992,11 @@ msgstr "récent" #: skins/default/templates/users.html:26 msgid "see people who joined the site first" -msgstr "" +msgstr "voir les premiers utilisateurs du site" #: skins/default/templates/users.html:32 msgid "see people sorted by name" -msgstr "" +msgstr "voir les utilisateurs triés par nom" #: skins/default/templates/users.html:33 msgid "by username" @@ -5681,7 +5011,8 @@ msgstr "utilisateurs vérifiant les critères %(suser)s" msgid "Nothing found." msgstr "Aucun résultat." -#: skins/default/templates/main_page/headline.html:4 views/readers.py:135 +#: skins/default/templates/main_page/headline.html:4 +#: views/readers.py:135 #, python-format msgid "%(q_num)s question" msgid_plural "%(q_num)s questions" @@ -5694,9 +5025,8 @@ msgid "with %(author_name)s's contributions" msgstr "avec la contribution de %(author_name)s" #: skins/default/templates/main_page/headline.html:12 -#, fuzzy msgid "Tagged" -msgstr "marquée avec des mots-clés" +msgstr "Marquée avec des mots-clés" #: skins/default/templates/main_page/headline.html:24 msgid "Search tips:" @@ -5710,9 +5040,8 @@ msgstr "Réinitialiser l'auteur" #: skins/default/templates/main_page/headline.html:32 #: skins/default/templates/main_page/nothing_found.html:18 #: skins/default/templates/main_page/nothing_found.html:21 -#, fuzzy msgid " or " -msgstr "ou" +msgstr " ou " #: skins/default/templates/main_page/headline.html:30 msgid "reset tags" @@ -5726,9 +5055,7 @@ msgstr "Recommencer" # FIXME #: skins/default/templates/main_page/headline.html:38 msgid " - to expand, or dig in by adding more tags and revising the query." -msgstr "" -" - pour développer ou restreindre en ajoutant plus de tags et en révisant la " -"requête" +msgstr " - pour développer ou restreindre en ajoutant plus de tags et en révisant la requête" #: skins/default/templates/main_page/headline.html:41 msgid "Search tip:" @@ -5743,14 +5070,12 @@ msgid "There are no unanswered questions here" msgstr "Il n'y a aucune question sans réponse" #: skins/default/templates/main_page/nothing_found.html:7 -#, fuzzy msgid "No questions here. " -msgstr "Aucune question favorite." +msgstr "Pas de question ici." #: skins/default/templates/main_page/nothing_found.html:8 -#, fuzzy msgid "Please follow some questions or follow some users." -msgstr "Merci de commencer (marquer) quelques questions quand vous les visitez" +msgstr "Merci de marquer quelques questions ou suivre quelques utilisateurs." #: skins/default/templates/main_page/nothing_found.html:13 msgid "You can expand your search by " @@ -5787,38 +5112,34 @@ msgstr "S'abonner au flux RSS des questions" #: skins/default/templates/main_page/tab_bar.html:11 msgid "RSS" -msgstr "" +msgstr "RSS" #: skins/default/templates/meta/bottom_scripts.html:7 #, python-format -msgid "" -"Please note: %(app_name)s requires javascript to work properly, please " -"enable javascript in your browser, <a href=\"%(noscript_url)s\">here is how</" -"a>" -msgstr "" +msgid "Please note: %(app_name)s requires javascript to work properly, please enable javascript in your browser, <a href=\"%(noscript_url)s\">here is how</a>" +msgstr "Merci de remarquer : %(app_name)s nécessite JavaScript pour fonctionner correctement, merci d'activer JavaScript dans votre navigateur, <a href=\"%(noscript_url)s\">voir ici comment faire</a>" #: skins/default/templates/meta/editor_data.html:7 -#, fuzzy, python-format +#, python-format msgid "each tag must be shorter that %(max_chars)s character" msgid_plural "each tag must be shorter than %(max_chars)s characters" -msgstr[0] "Chaque mot-clé doit comporter moins de %(max_chars)d caractère" -msgstr[1] "Chaque mot-clé doit comporter moins de %(max_chars)d caractères" +msgstr[0] "Chaque mot-clé doit comporter moins de %(max_chars)s caractère" +msgstr[1] "Chaque mot-clé doit comporter moins de %(max_chars)s caractères" #: skins/default/templates/meta/editor_data.html:9 -#, fuzzy, python-format +#, python-format msgid "please use %(tag_count)s tag" msgid_plural "please use %(tag_count)s tags or less" -msgstr[0] "Veuillez utiliser %(tag_count)d mot-clé, ou moins" -msgstr[1] "Veuillez utiliser %(tag_count)d mots-clés, ou moins" +msgstr[0] "Veuillez utiliser %(tag_count)s mot-clé, ou moins" +msgstr[1] "Veuillez utiliser %(tag_count)s mots-clés, ou moins" #: skins/default/templates/meta/editor_data.html:10 -#, fuzzy, python-format -msgid "" -"please use up to %(tag_count)s tags, less than %(max_chars)s characters each" -msgstr "jusqu'à 5 tags, faisant chacun moins de 20 caractères" +#, python-format +msgid "please use up to %(tag_count)s tags, less than %(max_chars)s characters each" +msgstr "merci d'utiliser un maximum de %(tag_count)s mots-clés, chacun avec moins de %(max_chars)s caractères" #: skins/default/templates/question/answer_tab_bar.html:3 -#, fuzzy, python-format +#, python-format msgid "" "\n" " %(counter)s Answer\n" @@ -5835,9 +5156,8 @@ msgstr[1] "" " %(counter)s réponses :" #: skins/default/templates/question/answer_tab_bar.html:11 -#, fuzzy msgid "Sort by »" -msgstr "Trier par:" +msgstr "Trier par »" #: skins/default/templates/question/answer_tab_bar.html:14 msgid "oldest answers will be shown first" @@ -5852,9 +5172,8 @@ msgid "most voted answers will be shown first" msgstr "Les réponses ayant obtenu le plus de votes seront affichées en premier" #: skins/default/templates/question/new_answer_form.html:16 -#, fuzzy msgid "Login/Signup to Answer" -msgstr "Connectez vous (ou Inscrivez vous) pour poster votre réponse" +msgstr "Connexion pour répondre" #: skins/default/templates/question/new_answer_form.html:24 msgid "Your answer" @@ -5865,154 +5184,114 @@ msgid "Be the first one to answer this question!" msgstr "Soyez le premier à répondre à cette quesion !" #: skins/default/templates/question/new_answer_form.html:32 -msgid "" -"<span class='strong big'>Please start posting your answer anonymously</span> " -"- your answer will be saved within the current session and published after " -"you log in or create a new account. Please try to give a <strong>substantial " -"answer</strong>, for discussions, <strong>please use comments</strong> and " -"<strong>please do remember to vote</strong> (after you log in)!" -msgstr "" +msgid "<span class='strong big'>Please start posting your answer anonymously</span> - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a <strong>substantial answer</strong>, for discussions, <strong>please use comments</strong> and <strong>please do remember to vote</strong> (after you log in)!" +msgstr "<span class='strong big'>Merci de commencer à répondre de manière anonyme</span> - votre réponse sera sauvegardée dans la session courante et publiée une fois que vous vous connecterez ou que vous créerez un compte. Merci d'essayer de donner une <strong>réponse complète</strong>, pour les discussions, <strong>merci d'utiliser les commentaires</strong> et <strong>pensez à voter</strong> (après vous être connecté) !" #: skins/default/templates/question/new_answer_form.html:36 -msgid "" -"<span class='big strong'>You are welcome to answer your own question</span>, " -"but please make sure to give an <strong>answer</strong>. Remember that you " -"can always <strong>revise your original question</strong>. Please " -"<strong>use comments for discussions</strong> and <strong>please don't " -"forget to vote :)</strong> for the answers that you liked (or perhaps did " -"not like)!" -msgstr "" +msgid "<span class='big strong'>You are welcome to answer your own question</span>, but please make sure to give an <strong>answer</strong>. Remember that you can always <strong>revise your original question</strong>. Please <strong>use comments for discussions</strong> and <strong>please don't forget to vote :)</strong> for the answers that you liked (or perhaps did not like)!" +msgstr "<span class='big strong'>Vous pouvez répondre vous-même à vos questions</span>, mais assurez-vous de donner une <strong>réponse</strong>. Souvenez-vous que vous pouvez <strong>revoir votre question d'origine</strong>. Merci <strong>d'utiliser les commentaires pour les discussions</strong> et <strong>n'oubliez-pas de voter :)</strong> pour les réponses que vous avez apprécié (ou peut-être pas) !" #: skins/default/templates/question/new_answer_form.html:38 -msgid "" -"<span class='big strong'>Please try to give a substantial answer</span>. If " -"you wanted to comment on the question or answer, just <strong>use the " -"commenting tool</strong>. Please remember that you can always <strong>revise " -"your answers</strong> - no need to answer the same question twice. Also, " -"please <strong>don't forget to vote</strong> - it really helps to select the " -"best questions and answers!" -msgstr "" +msgid "<span class='big strong'>Please try to give a substantial answer</span>. If you wanted to comment on the question or answer, just <strong>use the commenting tool</strong>. Please remember that you can always <strong>revise your answers</strong> - no need to answer the same question twice. Also, please <strong>don't forget to vote</strong> - it really helps to select the best questions and answers!" +msgstr "<span class='big strong'>Merci d'essayer de donner une réponse complète</span>. Si vous vouliez commenter la question ou la réponse, <strong>utilisez l'outil de commentaire</strong>. Merci de vous souvenir que vous pouvez toujours <strong>revoir vos réponses</strong> - il n'est pas nécessaire de répondre deux fois à la même question. De plus, <strong>n'oubliez pas de voter</strong> - c'est très utile pour la sélection des meilleures questions et réponses !" #: skins/default/templates/question/sharing_prompt_phrase.html:2 #, python-format -msgid "" -"Know someone who can answer? Share a <a href=\"%(question_url)s\">link</a> " -"to this question via" -msgstr "" +msgid "Know someone who can answer? Share a <a href=\"%(question_url)s\">link</a> to this question via" +msgstr "Vous connaissez quelqu'un qui a la réponse ? Partagez un <a href=\"%(question_url)s\">lien</a> vers cette question question via" #: skins/default/templates/question/sharing_prompt_phrase.html:8 -#, fuzzy msgid " or" -msgstr "ou" +msgstr " ou" #: skins/default/templates/question/sharing_prompt_phrase.html:10 msgid "email" msgstr "email" #: skins/default/templates/question/sidebar.html:6 -#, fuzzy msgid "Question tools" -msgstr "Tags de la question" +msgstr "Outils de question" # FIXME #: skins/default/templates/question/sidebar.html:9 -#, fuzzy msgid "click to unfollow this question" -msgstr "Cliquez ici pour voir les questions ayant obtenu le plus de réponses" +msgstr "cliquez pour ne plus suivre cette question" #: skins/default/templates/question/sidebar.html:10 -#, fuzzy msgid "Following" -msgstr "Toutes les questions" +msgstr "Suivi" #: skins/default/templates/question/sidebar.html:11 -#, fuzzy msgid "Unfollow" -msgstr "Toutes les questions" +msgstr "Ne plus suivre" # FIXME #: skins/default/templates/question/sidebar.html:15 -#, fuzzy msgid "click to follow this question" -msgstr "Cliquez ici pour voir les questions ayant obtenu le plus de réponses" +msgstr "cliquez pour suivre cette question" #: skins/default/templates/question/sidebar.html:16 -#, fuzzy msgid "Follow" -msgstr "Toutes les questions" +msgstr "Suivre" #: skins/default/templates/question/sidebar.html:23 #, python-format msgid "%(count)s follower" msgid_plural "%(count)s followers" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(count)s suiveur" +msgstr[1] "%(count)s suiveurs" #: skins/default/templates/question/sidebar.html:29 -#, fuzzy msgid "email the updates" -msgstr "Modification(s) d'email(s) annulée(s)" +msgstr "Envoi des mises à jour" #: skins/default/templates/question/sidebar.html:32 -msgid "" -"<strong>Here</strong> (once you log in) you will be able to sign up for the " -"periodic email updates about this question." -msgstr "" +msgid "<strong>Here</strong> (once you log in) you will be able to sign up for the periodic email updates about this question." +msgstr "<strong>Ici</strong> (une fois connecté) vous pourrez configurer les notifications périodiques par email à propos de cette question." #: skins/default/templates/question/sidebar.html:37 -#, fuzzy msgid "subscribe to this question rss feed" -msgstr "S'abonner au flux RSS des questions" +msgstr "S'abonner au flux RSS de cette question" #: skins/default/templates/question/sidebar.html:38 -#, fuzzy msgid "subscribe to rss feed" -msgstr "S'abonner au flux RSS des questions" +msgstr "S'abonner au flux RSS" #: skins/default/templates/question/sidebar.html:46 msgid "Stats" -msgstr "" +msgstr "Statistiques" #: skins/default/templates/question/sidebar.html:48 -#, fuzzy msgid "Asked" -msgstr "posée" +msgstr "Posée" #: skins/default/templates/question/sidebar.html:51 msgid "Seen" -msgstr "" +msgstr "Vu" #: skins/default/templates/question/sidebar.html:51 msgid "times" msgstr "fois" #: skins/default/templates/question/sidebar.html:54 -#, fuzzy msgid "Last updated" -msgstr "dernière mise à jour" +msgstr "Dernière mise à jour" #: skins/default/templates/question/sidebar.html:62 msgid "Related questions" msgstr "Questions liées" #: skins/default/templates/question/subscribe_by_email_prompt.html:5 -#, fuzzy msgid "Email me when there are any new answers" -msgstr "" -"<strong>Me notifier les nouvelles réponses par email</strong> une fois par " -"semaine" +msgstr "Me notifier les nouvelles réponses par email" #: skins/default/templates/question/subscribe_by_email_prompt.html:11 msgid "once you sign in you will be able to subscribe for any updates here" -msgstr "" -"vous pourrez vous abonner aux mails de notifications de mise à jour dès que " -"vous vous serez connecté" +msgstr "vous pourrez vous abonner aux mails de notifications de mise à jour dès que vous vous serez connecté" #: skins/default/templates/question/subscribe_by_email_prompt.html:12 -msgid "" -"<span class='strong'>Here</span> (once you log in) you will be able to sign " -"up for the periodic email updates about this question." -msgstr "" +msgid "<span class='strong'>Here</span> (once you log in) you will be able to sign up for the periodic email updates about this question." +msgstr "<span class='strong'>Ici</span> (une fois connecté) vous pourrez configurer les notifications périodiques par email à propos de cette question." # FIXME ou bien "Annulation des emails de notification de mises à jour" ??? #: skins/default/templates/user_profile/user.html:12 @@ -6036,7 +5315,7 @@ msgstr "changer d'image" #: skins/default/templates/user_profile/user_edit.html:25 #: skins/default/templates/user_profile/user_info.html:19 msgid "remove" -msgstr "" +msgstr "supprimer" #: skins/default/templates/user_profile/user_edit.html:32 msgid "Registered user" @@ -6047,9 +5326,8 @@ msgid "Screen Name" msgstr "Pseudo" #: skins/default/templates/user_profile/user_edit.html:59 -#, fuzzy msgid "(cannot be changed)" -msgstr "Compte supprimé." +msgstr "(ne peut être changé)" #: skins/default/templates/user_profile/user_edit.html:101 #: skins/default/templates/user_profile/user_email_subscriptions.html:22 @@ -6066,111 +5344,91 @@ msgid "Email subscription settings" msgstr "Paramètres d'abonnement aux emails" #: skins/default/templates/user_profile/user_email_subscriptions.html:9 -msgid "" -"<span class='big strong'>Adjust frequency of email updates.</span> Receive " -"updates on interesting questions by email, <strong><br/>help the community</" -"strong> by answering questions of your colleagues. If you do not wish to " -"receive emails - select 'no email' on all items below.<br/>Updates are only " -"sent when there is any new activity on selected items." -msgstr "" +msgid "<span class='big strong'>Adjust frequency of email updates.</span> Receive updates on interesting questions by email, <strong><br/>help the community</strong> by answering questions of your colleagues. If you do not wish to receive emails - select 'no email' on all items below.<br/>Updates are only sent when there is any new activity on selected items." +msgstr "<span class='big strong'>Ajuster la fréquence des notifications par email.</span> Recevez des notifications sur les questions intéressante par email, <strong><br/>aidez la communauté</strong> en répondant à vos collègues. Si vous ne souhaitez pas recevoir d'emails - sélectionner « pas d'email » dans tous les éléments ci-dessous.<br/>Les notifications sont uniquement envoyées quand il y a de l'activité sur les éléments sélectionnés." #: skins/default/templates/user_profile/user_email_subscriptions.html:23 -#, fuzzy msgid "Stop Email" -msgstr "" -"<strong>Votre adresse email</strong> (ne sera<strong>pas</strong> visible " -"des autres utilisateurs; et doit être valide)" +msgstr "Arrêter les emails" #: skins/default/templates/user_profile/user_favorites.html:4 #: skins/default/templates/user_profile/user_tabs.html:27 -#, fuzzy msgid "followed questions" -msgstr "Toutes les questions" +msgstr "questions suivies" #: skins/default/templates/user_profile/user_inbox.html:18 #: skins/default/templates/user_profile/user_tabs.html:12 msgid "inbox" -msgstr "" +msgstr "boîte de réception" #: skins/default/templates/user_profile/user_inbox.html:34 -#, fuzzy msgid "Sections:" -msgstr "questions" +msgstr "Sections :" #: skins/default/templates/user_profile/user_inbox.html:38 #, python-format msgid "forum responses (%(re_count)s)" -msgstr "" +msgstr "réponses du forum (%(re_count)s)" #: skins/default/templates/user_profile/user_inbox.html:43 -#, fuzzy, python-format +#, python-format msgid "flagged items (%(flag_count)s)" -msgstr "Veuillez utiliser %(tag_count)d mot-clé, ou moins" +msgstr "éléments annotés (%(flag_count)s)" #: skins/default/templates/user_profile/user_inbox.html:49 #: skins/default/templates/user_profile/user_inbox.html:61 -#, fuzzy msgid "select:" -msgstr "Supprimer" +msgstr "sélectionner :" #: skins/default/templates/user_profile/user_inbox.html:51 #: skins/default/templates/user_profile/user_inbox.html:63 -#, fuzzy msgid "seen" -msgstr "dernière connexion" +msgstr "vu" #: skins/default/templates/user_profile/user_inbox.html:52 #: skins/default/templates/user_profile/user_inbox.html:64 -#, fuzzy msgid "new" -msgstr "date (↓)" +msgstr "nouveau" #: skins/default/templates/user_profile/user_inbox.html:53 #: skins/default/templates/user_profile/user_inbox.html:65 -#, fuzzy msgid "none" -msgstr "bronze" +msgstr "aucun" #: skins/default/templates/user_profile/user_inbox.html:54 -#, fuzzy msgid "mark as seen" -msgstr "dernière connexion" +msgstr "marquer comme vu" # FIXME ou "ayant reçu une récompense" #: skins/default/templates/user_profile/user_inbox.html:55 -#, fuzzy msgid "mark as new" -msgstr "marquée comme meilleure réponse" +msgstr "marquée comme nouveau" #: skins/default/templates/user_profile/user_inbox.html:56 msgid "dismiss" -msgstr "" +msgstr "passer outre" #: skins/default/templates/user_profile/user_inbox.html:66 -#, fuzzy msgid "remove flags" -msgstr "Voir tous les mots-clés" +msgstr "supprimer les drapeaux" #: skins/default/templates/user_profile/user_inbox.html:68 -#, fuzzy msgid "delete post" -msgstr "Supprimer" +msgstr "supprimer le message" #: skins/default/templates/user_profile/user_info.html:36 msgid "update profile" msgstr "Mettre à jour le profil" #: skins/default/templates/user_profile/user_info.html:40 -#, fuzzy msgid "manage login methods" -msgstr "Bitte einloggen" +msgstr "gérer les méthodes de connexion" #: skins/default/templates/user_profile/user_info.html:53 msgid "real name" msgstr "nom réél" #: skins/default/templates/user_profile/user_info.html:58 -#, fuzzy msgid "member since" msgstr "membre depuis" @@ -6179,7 +5437,6 @@ msgid "last seen" msgstr "dernière connexion" #: skins/default/templates/user_profile/user_info.html:69 -#, fuzzy msgid "website" msgstr "Site web" @@ -6249,13 +5506,8 @@ msgid "Send message to %(username)s" msgstr "Envoyer un message à %(username)s" #: skins/default/templates/user_profile/user_moderate.html:44 -msgid "" -"An email will be sent to the user with 'reply-to' field set to your email " -"address. Please make sure that your address is entered correctly." -msgstr "" -"Un email sera envoyé à cet utilisateur avec le champ 'reply-to' pré-" -"renseigné avec votre adresse email, afin qu'il puisse vous répondre " -"directement. Merci de vérifier que votre adresse email est correcte." +msgid "An email will be sent to the user with 'reply-to' field set to your email address. Please make sure that your address is entered correctly." +msgstr "Un email sera envoyé à cet utilisateur avec le champ 'reply-to' pré-renseigné avec votre adresse email, afin qu'il puisse vous répondre directement. Merci de vérifier que votre adresse email est correcte." #: skins/default/templates/user_profile/user_moderate.html:46 msgid "Message sent" @@ -6266,62 +5518,53 @@ msgid "Send message" msgstr "Envoyer le message" #: skins/default/templates/user_profile/user_moderate.html:74 -msgid "" -"Administrators have privileges of normal users, but in addition they can " -"assign/revoke any status to any user, and are exempt from the reputation " -"limits." -msgstr "" +msgid "Administrators have privileges of normal users, but in addition they can assign/revoke any status to any user, and are exempt from the reputation limits." +msgstr "Les administrateurs ont les privilèges des utilisateurs normaux, mais en plus ils peuvent assigner ou révoquer n'importe quel statut de n'importe quel utilisateur, et ne sont pas soumis aux limites de réputation." #: skins/default/templates/user_profile/user_moderate.html:77 -msgid "" -"Moderators have the same privileges as administrators, but cannot add or " -"remove user status of 'moderator' or 'administrator'." -msgstr "" +msgid "Moderators have the same privileges as administrators, but cannot add or remove user status of 'moderator' or 'administrator'." +msgstr "Les modérateurs ont les mêmes droits que les administrateurs, mais ne peuvent ajouter ou supprimer les statuts « modérateur » ou « administrateur » à un utilisateur" #: skins/default/templates/user_profile/user_moderate.html:80 msgid "'Approved' status means the same as regular user." -msgstr "" +msgstr "Le statut « approuvé » signifie la même chose qu'un utilisateur normal." #: skins/default/templates/user_profile/user_moderate.html:83 -#, fuzzy msgid "Suspended users can only edit or delete their own posts." -msgstr "Les utilisateurs suspendus ne peuvent pas étiqueter les messages" +msgstr "Les utilisateurs suspendus ne peuvent pas éditer ou supprimer leurs propres messages" #: skins/default/templates/user_profile/user_moderate.html:86 -msgid "" -"Blocked users can only login and send feedback to the site administrators." -msgstr "" +msgid "Blocked users can only login and send feedback to the site administrators." +msgstr "Les utilisateurs bloqués peuvent seulement se connecter et envoyer des retours aux administrateurs." #: skins/default/templates/user_profile/user_network.html:5 #: skins/default/templates/user_profile/user_tabs.html:18 msgid "network" -msgstr "" +msgstr "réseau" #: skins/default/templates/user_profile/user_network.html:10 #, python-format msgid "Followed by %(count)s person" msgid_plural "Followed by %(count)s people" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Suivi par %(count)s personne" +msgstr[1] "Suivi par %(count)s personnes" #: skins/default/templates/user_profile/user_network.html:14 #, python-format msgid "Following %(count)s person" msgid_plural "Following %(count)s people" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Suivi de %(count)s personne" +msgstr[1] "Suivi de %(count)s personnes" #: skins/default/templates/user_profile/user_network.html:19 -msgid "" -"Your network is empty. Would you like to follow someone? - Just visit their " -"profiles and click \"follow\"" -msgstr "" +msgid "Your network is empty. Would you like to follow someone? - Just visit their profiles and click \"follow\"" +msgstr "Votre réseau est vide. Voulez-vous ajouter quelqu'un à suivre ? - Visitez son profil et cliquez sur « suivre »" # FIXME ou bien "Annulation des emails de notification de mises à jour" ??? #: skins/default/templates/user_profile/user_network.html:21 -#, fuzzy, python-format +#, python-format msgid "%(username)s's network is empty" -msgstr "Profil de l'utilisateur %(username)s" +msgstr "Le réseau de %(username)s est vide" # TODO demander au développeur de faire 2 entrées distinctes. Une contiendra "date (↑)" et l'autre "date" #: skins/default/templates/user_profile/user_recent.html:4 @@ -6333,7 +5576,7 @@ msgstr "actualité (↓)" #: skins/default/templates/user_profile/user_recent.html:24 #: skins/default/templates/user_profile/user_recent.html:28 msgid "source" -msgstr "" +msgstr "source" #: skins/default/templates/user_profile/user_reputation.html:11 msgid "Your karma change log." @@ -6357,7 +5600,6 @@ msgstr[0] "<span class=\"count\">%(counter)s</span> Question" msgstr[1] "<span class=\"count\">%(counter)s</span> Questions" #: skins/default/templates/user_profile/user_stats.html:16 -#, fuzzy msgid "Answer" msgid_plural "Answers" msgstr[0] "réponse" @@ -6414,33 +5656,32 @@ msgstr[1] "<span class=\"count\">%(counter)s</span> Badges" # FIXME #: skins/default/templates/user_profile/user_stats.html:120 -#, fuzzy msgid "Answer to:" -msgstr "Conseils pour répondre" +msgstr "Répondre à :" #: skins/default/templates/user_profile/user_tabs.html:5 msgid "User profile" msgstr "Profil utilisateur" -#: skins/default/templates/user_profile/user_tabs.html:10 views/users.py:638 +#: skins/default/templates/user_profile/user_tabs.html:10 +#: views/users.py:638 msgid "comments and answers to others questions" msgstr "Commentaires et réponses à d'autres questions" #: skins/default/templates/user_profile/user_tabs.html:16 msgid "followers and followed users" -msgstr "" +msgstr "utilisateurs suiveurs et suivis" #: skins/default/templates/user_profile/user_tabs.html:21 -#, fuzzy msgid "Graph of user karma" -msgstr "Statistiques sur la réputation de cet utilisateur" +msgstr "Graphique du karma de l'utilisateur" #: skins/default/templates/user_profile/user_tabs.html:25 -#, fuzzy msgid "questions that user is following" -msgstr "questions favorites de cet utilisateur" +msgstr "questions que cet utilisateur suit" -#: skins/default/templates/user_profile/user_tabs.html:34 views/users.py:679 +#: skins/default/templates/user_profile/user_tabs.html:34 +#: views/users.py:679 msgid "user vote record" msgstr "Trace des votes de cet utilisateur" @@ -6449,34 +5690,31 @@ msgstr "Trace des votes de cet utilisateur" msgid "votes" msgstr "votes" -#: skins/default/templates/user_profile/user_tabs.html:40 views/users.py:769 +#: skins/default/templates/user_profile/user_tabs.html:40 +#: views/users.py:769 msgid "email subscription settings" msgstr "Paramètres d'abonnement aux emails" -#: skins/default/templates/user_profile/user_tabs.html:46 views/users.py:205 +#: skins/default/templates/user_profile/user_tabs.html:46 +#: views/users.py:205 msgid "moderate this user" msgstr "Modérer cet utilisateur" #: skins/default/templates/widgets/answer_edit_tips.html:3 #: skins/default/templates/widgets/question_edit_tips.html:3 msgid "Tips" -msgstr "" +msgstr "Astuces" #: skins/default/templates/widgets/answer_edit_tips.html:6 -#, fuzzy msgid "give an answer interesting to this community" -msgstr "" -"Rédiger vos réponses afin qu'elles soient pertinentes pour la communauté." +msgstr "Rédiger vos réponses afin qu'elles soient pertinentes pour la communauté." #: skins/default/templates/widgets/answer_edit_tips.html:9 msgid "try to give an answer, rather than engage into a discussion" -msgstr "" -"Contentez-vous de donner une réponse, plutôt que de vous engagez dans une " -"discussion." +msgstr "Contentez-vous de donner une réponse, plutôt que de vous engager dans une discussion." #: skins/default/templates/widgets/answer_edit_tips.html:12 #: skins/default/templates/widgets/question_edit_tips.html:8 -#, fuzzy msgid "provide enough details" msgstr "Merci de fournir suffisamment de détails." @@ -6492,9 +5730,8 @@ msgstr "lisez notre FAQ (Foire aux questions)" #: skins/default/templates/widgets/answer_edit_tips.html:27 #: skins/default/templates/widgets/question_edit_tips.html:22 -#, fuzzy msgid "Markdown basics" -msgstr "Aide sur les balises \"Markdown\"" +msgstr "Aide sur les balises Markdown" #: skins/default/templates/widgets/answer_edit_tips.html:31 #: skins/default/templates/widgets/question_edit_tips.html:26 @@ -6545,34 +5782,16 @@ msgstr "en savoir plus sur les balises \"Markdown\"" #: skins/default/templates/widgets/ask_form.html:6 msgid "login to post question info" -msgstr "" -"<span class=\"strong big\">Formulez votre question à l'aide du formulaire ci-" -"dessous (un court titre résumant la question, puis la question à proprement " -"parler, aussi détaillée que vous le souhaitez...)</span>. A l'étape " -"suivante, vous devrez saisir votre email et votre nom (ou un pseudo si vous " -"souhaitez rester anonyme...). Ces éléments sont nécessaires pour bénéficier " -"des fonctionnalités de notre module de questions/réponses, qui repose sur un " -"principe communautaire." +msgstr "<span class=\"strong big\">Formulez votre question à l'aide du formulaire ci-dessous (un court titre résumant la question, puis la question à proprement parler, aussi détaillée que vous le souhaitez...)</span>. A l'étape suivante, vous devrez saisir votre email et votre nom (ou un pseudo si vous souhaitez rester anonyme...). Ces éléments sont nécessaires pour bénéficier des fonctionnalités de notre module de questions/réponses, qui repose sur un principe communautaire." #: skins/default/templates/widgets/ask_form.html:7 -msgid "" -"<span class=\\\"strong big\\\">You are welcome to start submitting your " -"question anonymously</span>. When you submit the post, you will be " -"redirected to the login/signup page. Your question will be saved in the " -"current session and will be published after you log in. Login/signup process " -"is very simple. Login takes about 30 seconds, initial signup takes a minute " -"or less." -msgstr "" +msgid "<span class=\\\"strong big\\\">You are welcome to start submitting your question anonymously</span>. When you submit the post, you will be redirected to the login/signup page. Your question will be saved in the current session and will be published after you log in. Login/signup process is very simple. Login takes about 30 seconds, initial signup takes a minute or less." +msgstr "<span class=\\\"strong big\\\">Vous pouvez envoyer des questions de manière anonyme</span>. Quand vous enverrez le message, vous serez redirigé vers la page de connexion et d'inscription. Votre question sera sauvegardée dans la session courante et sera publiée après connexion. Le processus de connexion ou d'inscription est très simple. La connexion prend 30 secondes, l'inscription initiale prend moins d'une minute." #: skins/default/templates/widgets/ask_form.html:11 #, python-format -msgid "" -"<span class='strong big'>Looks like your email address, %%(email)s has not " -"yet been validated.</span> To post messages you must verify your email, " -"please see <a href='%%(email_validation_faq_url)s'>more details here</a>." -"<br>You can submit your question now and validate email after that. Your " -"question will saved as pending meanwhile." -msgstr "" +msgid "<span class='strong big'>Looks like your email address, %%(email)s has not yet been validated.</span> To post messages you must verify your email, please see <a href='%%(email_validation_faq_url)s'>more details here</a>.<br>You can submit your question now and validate email after that. Your question will saved as pending meanwhile." +msgstr "<span class='strong big'>Il semblerait que votre adresse email, %%(email)s n'ait pas encore été validée.</span> Pour envoyer des messages vou devez vérifier votre adresse email, merci de voir <a href='%%(email_validation_faq_url)s'>ici pour plus de détails</a>.<br>Vous pouvez envoyer votre question maintenant et valider votre adresse email après. La question sera sauvegardée pendant ce temps-là." #: skins/default/templates/widgets/contributors.html:3 msgid "Contributors" @@ -6581,16 +5800,16 @@ msgstr "Contributeurs" #: skins/default/templates/widgets/footer.html:33 #, python-format msgid "Content on this site is licensed under a %(license)s" -msgstr "" +msgstr "Le contenu de ce site est mis à disposition sous les termes de la licence %(license)s" #: skins/default/templates/widgets/footer.html:38 msgid "about" -msgstr "Qui sommes nous ?" +msgstr "Qui sommes-nous ?" #: skins/default/templates/widgets/footer.html:40 #: skins/default/templates/widgets/user_navigation.html:17 msgid "help" -msgstr "" +msgstr "aide" #: skins/default/templates/widgets/footer.html:42 msgid "privacy policy" @@ -6605,9 +5824,9 @@ msgid "back to home page" msgstr "Retour à l'accueil" #: skins/default/templates/widgets/logo.html:4 -#, fuzzy, python-format +#, python-format msgid "%(site)s logo" -msgstr "Logo du site de Questions/Réponses" +msgstr "Logo de %(site)s" #: skins/default/templates/widgets/meta_nav.html:10 msgid "users" @@ -6618,10 +5837,8 @@ msgid "badges" msgstr "Réputation" #: skins/default/templates/widgets/question_edit_tips.html:5 -#, fuzzy msgid "ask a question interesting to this community" -msgstr "" -"Rédiger vos réponses afin qu'elles soient pertinentes pour la communauté." +msgstr "Rédiger vos réponses afin qu'elles soient pertinentes pour la communauté." #: skins/default/templates/widgets/question_summary.html:12 msgid "view" @@ -6643,7 +5860,7 @@ msgstr[1] "votes" #: skins/default/templates/widgets/scope_nav.html:6 msgid "ALL" -msgstr "" +msgstr "TOUS" #: skins/default/templates/widgets/scope_nav.html:8 msgid "see unanswered questions" @@ -6651,70 +5868,65 @@ msgstr "Voir les questions sans réponses" #: skins/default/templates/widgets/scope_nav.html:8 msgid "UNANSWERED" -msgstr "" +msgstr "NON RÉPONDU" #: skins/default/templates/widgets/scope_nav.html:11 -#, fuzzy msgid "see your followed questions" -msgstr "Voir vos questions favorites" +msgstr "Voir vos questions suivies" #: skins/default/templates/widgets/scope_nav.html:11 msgid "FOLLOWED" -msgstr "" +msgstr "SUIVI" #: skins/default/templates/widgets/scope_nav.html:14 -#, fuzzy msgid "Please ask your question here" -msgstr "Veuillez saisir votre question !" +msgstr "Merci de poser votre question ici" #: skins/default/templates/widgets/user_long_score_and_badge_summary.html:3 msgid "karma:" -msgstr "" +msgstr "karma :" #: skins/default/templates/widgets/user_long_score_and_badge_summary.html:7 msgid "badges:" msgstr "badges :" #: skins/default/templates/widgets/user_navigation.html:9 -#, fuzzy msgid "sign out" -msgstr "deconnexion/" +msgstr "déconnexion" #: skins/default/templates/widgets/user_navigation.html:12 -#, fuzzy msgid "Hi, there! Please sign in" -msgstr "Merci de vous authentifiez :" +msgstr "Bonjour ! Merci de vous connecter" #: skins/default/templates/widgets/user_navigation.html:15 msgid "settings" msgstr "paramètres" #: templatetags/extra_filters_jinja.py:279 -#, fuzzy msgid "no" -msgstr "bronze" +msgstr "non" -#: utils/decorators.py:90 views/commands.py:73 views/commands.py:93 +#: utils/decorators.py:90 +#: views/commands.py:73 +#: views/commands.py:93 msgid "Oops, apologies - there was some error" -msgstr "" +msgstr "Oops, toutes nos excuses - il y a eu une erreur" #: utils/decorators.py:109 -#, fuzzy msgid "Please login to post" -msgstr "Bitte einloggen" +msgstr "Merci de vous connecter pour envoyer un message" #: utils/decorators.py:205 msgid "Spam was detected on your post, sorry for if this is a mistake" -msgstr "" +msgstr "De la pollution (spam) a été détectée dans votre message, désolé si c'est une erreur" #: utils/forms.py:33 msgid "this field is required" msgstr "ce champ est obligatoire" #: utils/forms.py:60 -#, fuzzy msgid "Choose a screen name" -msgstr "choisissez un nom d'utilisateur" +msgstr "Choisissez un nom d'utilisateur" #: utils/forms.py:69 msgid "user name is required" @@ -6734,24 +5946,19 @@ msgstr "désolé, aucun utilisateur ne porte ce nom" #: utils/forms.py:73 msgid "sorry, we have a serious error - user name is taken by several users" -msgstr "" -"désolé, ce nom d'utilisateur est utilisé par plusieurs utilisateurs. C'est " -"un problème grave, qui peut témoigner de la présence d'un bug dans " -"l'application. Merci de prévenir l'administrateur du site. " +msgstr "désolé, ce nom d'utilisateur est utilisé par plusieurs utilisateurs. C'est un problème grave, qui peut témoigner de la présence d'un bug dans l'application. Merci de prévenir l'administrateur du site. " #: utils/forms.py:74 msgid "user name can only consist of letters, empty space and underscore" -msgstr "" -"un nom d'utilisateur doit comporter uniquement des lettres, des espaces et " -"des caractères de soulignement (ou <i>underscores</i> : '_')" +msgstr "un nom d'utilisateur doit comporter uniquement des lettres, des espaces et des caractères de soulignement (ou <i>underscores</i> : '_')" #: utils/forms.py:75 msgid "please use at least some alphabetic characters in the user name" -msgstr "" +msgstr "merci d'utiliser au moins quelques caractères alphabétiques dans le nom d'utilisateur" #: utils/forms.py:138 msgid "Your email <i>(never shared)</i>" -msgstr "" +msgstr "Votre email <i>(jamais partagé)</i>" #: utils/forms.py:139 msgid "email address is required" @@ -6763,9 +5970,7 @@ msgstr "Veuillez entrer une adresse email valide" #: utils/forms.py:141 msgid "this email is already used by someone else, please choose another" -msgstr "" -"Cette adresse email est déjà utilisée par un autre utilisateur; veuillez en " -"saisir une autre" +msgstr "Cette adresse email est déjà utilisée par un autre utilisateur; veuillez en saisir une autre" #: utils/forms.py:170 msgid "password is required" @@ -6773,7 +5978,7 @@ msgstr "le mot de passe est obligatoire" #: utils/forms.py:173 msgid "Password <i>(please retype)</i>" -msgstr "" +msgstr "Mot de passe <i>(retaper s'il vous plait)</i>" #: utils/forms.py:174 msgid "please, retype your password" @@ -6815,53 +6020,51 @@ msgid "" "<p>Note that tags may consist of more than one word, and tags\n" "may be separated by a semicolon or a comma</p>\n" msgstr "" +"<p>Pour demander par email, merci de :</p>\n" +"<ul>\n" +" <li>Mettre dans le sujet : [Tag1; Tag2] Titre de la question</li>\n" +" <li>Taper le détail de la question dans le corps du message</li>\n" +"</ul>\n" +"<p>Remarquez que les mots-clés sont composés d'un ou plusieurs mots\n" +"et qu'ils peuvent être séparés par une virgule ou un point-virgule.</p>\n" #: utils/mail.py:167 #, python-format -msgid "" -"<p>Sorry, there was an error posting your question please contact the " -"%(site)s administrator</p>" -msgstr "" +msgid "<p>Sorry, there was an error posting your question please contact the %(site)s administrator</p>" +msgstr "<p>Désolé, une erreur est survenue pendant l'envoi de votre question, merci de contacter les administrateurs de %(site)s </p>" #: utils/mail.py:173 #, python-format -msgid "" -"<p>Sorry, in order to post questions on %(site)s by email, please <a href=" -"\"%(url)s\">register first</a></p>" -msgstr "" +msgid "<p>Sorry, in order to post questions on %(site)s by email, please <a href=\"%(url)s\">register first</a></p>" +msgstr "<p>Désolé, afin d'envoyer vos questions sur %(site)s par email, merci de vous <a href=\"%(url)s\">inscrire d'abord</a></p>" #: utils/mail.py:181 -msgid "" -"<p>Sorry, your question could not be posted due to insufficient privileges " -"of your user account</p>" -msgstr "" +msgid "<p>Sorry, your question could not be posted due to insufficient privileges of your user account</p>" +msgstr "<p>Désolé, votre question ne peut pas être envoyée à cause d'un manque de droits sur votre compte utilisateur</p>" #: views/avatar_views.py:99 msgid "Successfully uploaded a new avatar." -msgstr "" +msgstr "Nouvel avatar envoyé avec succès." #: views/avatar_views.py:140 msgid "Successfully updated your avatar." -msgstr "" +msgstr "Mise à jour de l'avatar réalisée avec succès." #: views/avatar_views.py:180 msgid "Successfully deleted the requested avatars." -msgstr "" +msgstr "Suppression des avatars demandés réalisée avec succès." #: views/commands.py:83 -#, fuzzy msgid "Sorry, but anonymous users cannot access the inbox" -msgstr "" -"Désolé, mais les utilisateurs anonymes ne peuvent aps accepter les réponses" +msgstr "Désolé, mais les utilisateurs anonymes ne peuvent pas accéder à la boîte de réception" #: views/commands.py:112 -#, fuzzy msgid "Sorry, anonymous users cannot vote" -msgstr "les utilisateurs anonymes ne peuvent pas voter" +msgstr "Désolé, mais les utilisateurs anonymes ne peuvent pas voter" #: views/commands.py:129 msgid "Sorry you ran out of votes for today" -msgstr "Désolé, vous avez épuisé votre crédit de votes pour ajourd'hui" +msgstr "Désolé, vous avez épuisé votre crédit de votes pour aujourd'hui" #: views/commands.py:135 #, python-format @@ -6874,55 +6077,44 @@ msgstr "Désolé, il semble y avoir un problème..." #: views/commands.py:229 msgid "Sorry, but anonymous users cannot accept answers" -msgstr "" -"Désolé, mais les utilisateurs anonymes ne peuvent aps accepter les réponses" +msgstr "Désolé, mais les utilisateurs anonymes ne peuvent aps accepter les réponses" #: views/commands.py:339 -#, fuzzy, python-format -msgid "" -"Your subscription is saved, but email address %(email)s needs to be " -"validated, please see <a href=\"%(details_url)s\">more details here</a>" -msgstr "" -"Votre abonnement a été enegistré, mais nous devons valider votre adresse " -"email %(email)s ; <a href='%(details_url)s'>Cliquez ici pour en savoir plus</" -"a>." +#, python-format +msgid "Your subscription is saved, but email address %(email)s needs to be validated, please see <a href=\"%(details_url)s\">more details here</a>" +msgstr "Votre abonnement a été enregistré, mais nous devons valider votre adresse email %(email)s ; <a href=\"%(details_url)s\">cliquez ici pour en savoir plus</a>." #: views/commands.py:348 msgid "email update frequency has been set to daily" -msgstr "" -"La fréquence d'envoi des emails de notification de mises à jour est " -"désormais \"quotidienne\"." +msgstr "La fréquence d'envoi des emails de notification de mises à jour est désormais \"quotidienne\"." #: views/commands.py:464 #, python-format msgid "Tag subscription was canceled (<a href=\"%(url)s\">undo</a>)." -msgstr "" +msgstr "Suivi des mots-clés annulé (<a href=\"%(url)s\">annuler</a>)." #: views/commands.py:473 #, python-format msgid "Please sign in to subscribe for: %(tags)s" -msgstr "" +msgstr "Merci de vous connecter pour suivre : %(tags)s" #: views/commands.py:600 -#, fuzzy msgid "Please sign in to vote" -msgstr "Merci de vous authentifiez :" +msgstr "Merci de vous connecter pour voter" #: views/commands.py:620 -#, fuzzy msgid "Please sign in to delete/restore posts" -msgstr "Merci de vous authentifiez :" +msgstr "Merci de vous connecter pour supprimer ou restaurer des messages" #: views/meta.py:37 -#, fuzzy, python-format +#, python-format msgid "About %(site)s" -msgstr "A propos de %(site_name)s" +msgstr "À propos de %(site)s" # FIXME à vérifier #: views/meta.py:92 msgid "Q&A forum feedback" -msgstr "" -"Vos remarques, critiques et suggestions sur notre forum de questions/réponses" +msgstr "Vos remarques, critiques et suggestions sur notre forum de questions/réponses" #: views/meta.py:93 msgid "Thanks for the feedback!" @@ -6930,28 +6122,22 @@ msgstr "Merci pour vos remarques !" #: views/meta.py:102 msgid "We look forward to hearing your feedback! Please, give it next time :)" -msgstr "" -"Nous aimerions bien savoir ce que vous pensez de notre service de questions/" -"réponses ! Lors de votre prochaine visite, n'hésitez pas à nous envoyer vos " -"remarques, critiques et suggestions ;o)" +msgstr "Nous aimerions bien savoir ce que vous pensez de notre service de questions/réponses ! Lors de votre prochaine visite, n'hésitez pas à nous envoyer vos remarques, critiques et suggestions ;o)" #: views/meta.py:106 msgid "Privacy policy" msgstr "Politique en matière de respect de la vie privée" #: views/readers.py:133 -#, fuzzy, python-format +#, python-format msgid "%(q_num)s question, tagged" msgid_plural "%(q_num)s questions, tagged" -msgstr[0] "%(q_num)s question" -msgstr[1] "%(q_num)s questions" +msgstr[0] "%(q_num)s question notée" +msgstr[1] "%(q_num)s questions notées" #: views/readers.py:388 -#, fuzzy -msgid "" -"Sorry, the comment you are looking for has been deleted and is no longer " -"accessible" -msgstr "Désolé, cette question a été supprimée, et n'est plus accessible." +msgid "Sorry, the comment you are looking for has been deleted and is no longer accessible" +msgstr "Désolé, ce commentaire a été supprimé, et n'est plus accessible." # FIXME #: views/users.py:206 @@ -6965,7 +6151,7 @@ msgstr "profil de l'utilisateur" #: views/users.py:382 msgid "user profile overview" -msgstr "infOs générales sur le profil de l'utilisateur" +msgstr "infos générales sur le profil de l'utilisateur" #: views/users.py:551 msgid "recent user activity" @@ -6985,12 +6171,11 @@ msgstr "profil - votes" #: views/users.py:701 msgid "user karma" -msgstr "" +msgstr "karma de l'utilisateur" #: views/users.py:702 -#, fuzzy msgid "Profile - User's Karma" -msgstr "profil - réputation de l'utilisateur" +msgstr "Profil - Karma de l'utilisateur" #: views/users.py:720 msgid "users favorite questions" @@ -7000,7 +6185,8 @@ msgstr "questions favorites des utilisateurs" msgid "profile - favorite questions" msgstr "profil - questions favorites" -#: views/users.py:741 views/users.py:745 +#: views/users.py:741 +#: views/users.py:745 msgid "changes saved" msgstr "Modifications enregistrées." @@ -7014,8 +6200,7 @@ msgstr "profil - abonnements emails" #: views/writers.py:60 msgid "Sorry, anonymous users cannot upload files" -msgstr "" -"Désolé, les utilisateurs anonymes ne peuvent pas transférer de fichiers" +msgstr "Désolé, les utilisateurs anonymes ne peuvent pas transférer de fichiers" #: views/writers.py:73 #, python-format @@ -7025,59 +6210,37 @@ msgstr "Les types de fichiers autoprisés sont '%(file_types)s'" #: views/writers.py:84 #, python-format msgid "maximum upload file size is %(file_size)sK" -msgstr "" -"La taille maximale autorisée pour un fichier est de %(file_size)s Kilo-octets" +msgstr "La taille maximale autorisée pour un fichier est de %(file_size)s Kilo-octets" #: views/writers.py:92 msgid "Error uploading file. Please contact the site administrator. Thank you." -msgstr "" -"Une erreur est survenue lors du transfert du fichier sur notre serveur. " -"Merci de contacter l'administrateur." +msgstr "Une erreur est survenue lors du transfert du fichier sur notre serveur. Merci de contacter l'administrateur." #: views/writers.py:189 -msgid "" -"<span class=\"strong big\">You are welcome to start submitting your question " -"anonymously</span>. When you submit the post, you will be redirected to the " -"login/signup page. Your question will be saved in the current session and " -"will be published after you log in. Login/signup process is very simple. " -"Login takes about 30 seconds, initial signup takes a minute or less." -msgstr "" +msgid "<span class=\"strong big\">You are welcome to start submitting your question anonymously</span>. When you submit the post, you will be redirected to the login/signup page. Your question will be saved in the current session and will be published after you log in. Login/signup process is very simple. Login takes about 30 seconds, initial signup takes a minute or less." +msgstr "<span class=\"strong big\">Vous pouvez envoyer des questions de manière anonyme</span>. Quand vous enverrez le message, vous serez redirigé vers la page de connexion et d'inscription. Votre question sera sauvegardée dans la session courante et sera publiée après connexion. Le processus de connexion ou d'inscription est très simple. La connexion prend 30 secondes, l'inscription initiale prend moins d'une minute." #: views/writers.py:466 -#, fuzzy msgid "Please log in to answer questions" -msgstr "Voir les questions sans réponses" +msgstr "Merci de vous connecter pour répondre à des questions" #: views/writers.py:572 #, python-format -msgid "" -"Sorry, you appear to be logged out and cannot post comments. Please <a href=" -"\"%(sign_in_url)s\">sign in</a>." -msgstr "" -"Désolé, vous ne pouvez pas publier de commentaires car vous semblez être " -"déconnecté. Merci d'essayer de vous <a href=\"%(sign_in_url)s\">reconnecter</" -"a>." +msgid "Sorry, you appear to be logged out and cannot post comments. Please <a href=\"%(sign_in_url)s\">sign in</a>." +msgstr "Désolé, vous ne pouvez pas publier de commentaires car vous semblez être déconnecté. Merci d'essayer de vous <a href=\"%(sign_in_url)s\">reconnecter</a>." #: views/writers.py:589 -#, fuzzy msgid "Sorry, anonymous users cannot edit comments" -msgstr "" -"Désolé, les utilisateurs anonymes ne peuvent pas transférer de fichiers" +msgstr "Désolé, les utilisateurs anonymes ne peuvent pas éditer des commentaires" #: views/writers.py:619 #, python-format -msgid "" -"Sorry, you appear to be logged out and cannot delete comments. Please <a " -"href=\"%(sign_in_url)s\">sign in</a>." -msgstr "" -"Désolé, vous ne pouvez pas supprimer de commentaires car vous semblez être " -"déconnecté. Merci d'essayer de vous <a href=\"%(sign_in_url)s\">reconnecter</" -"a>." +msgid "Sorry, you appear to be logged out and cannot delete comments. Please <a href=\"%(sign_in_url)s\">sign in</a>." +msgstr "Désolé, vous ne pouvez pas supprimer de commentaires car vous semblez être déconnecté. Merci d'essayer de vous <a href=\"%(sign_in_url)s\">reconnecter</a>." #: views/writers.py:640 msgid "sorry, we seem to have some technical difficulties" -msgstr "" -"Désolé, nous rencontrons apparemment des difficultés d'ordre techniques..." +msgstr "Désolé, nous rencontrons apparemment des difficultés d'ordre technique..." #~ msgid "use-these-chars-in-tags" #~ msgstr "utiliser-ces-caracteres-dans-les-mots-cles" @@ -7106,6 +6269,7 @@ msgstr "" #~ msgstr "Nom d'utilisateur incorrect." #~ msgid "%(name)s, this is an update message header for %(num)d question" + #~ msgid_plural "" #~ "%(name)s, this is an update message header for %(num)d questions" #~ msgstr[0] "" @@ -7198,6 +6362,7 @@ msgstr "" #, fuzzy #~ msgid "need > %(min_rep)d point to remove flag" + #~ msgid_plural "need > %(min_rep)d points to remove flag" #~ msgstr[0] "" #~ "Il faut au moins %(min_rep)s points de réputation pour pouvoir étiqueter " @@ -7209,54 +6374,42 @@ msgstr "" # FIXME ou "ayant reçu un vote négatif" #~ msgid "cannot revoke old vote" #~ msgstr "impossible de révoquer un ancien vote" - #~ msgid "%(author)s modified the question" #~ msgstr "%(author)s a modifié la question" - #~ msgid "%(people)s posted %(new_answer_count)s new answers" #~ msgstr "" #~ "Les utilisateurs %(people)s ont posté %(new_answer_count)s nouvelles " #~ "réponses" - #~ msgid "%(people)s commented the question" #~ msgstr "%(people)s a/ont commenté cette question" - #~ msgid "%(people)s commented answers" #~ msgstr "%(people)s a/ont commenté des réponses" - #~ msgid "%(people)s commented an answer" #~ msgstr "%(people)s a/ont commenté une réponse" - # FIXME #~ msgid "change %(email)s info" #~ msgstr "Informations concernant le changement d'email : %(email)s" - # FIXME #~ msgid "here is why email is required, see %(gravatar_faq_url)s" #~ msgstr "" #~ "voici pourquoi les emails sont obligatoires; pour plus d'infos, consultez " #~ "la page %(gravatar_faq_url)s" - #~ msgid "Your new Email" #~ msgstr "" #~ "<strong>Votre nouvelle adresse email:</strong> (ne sera<strong>pas</" #~ "strong> visible des autres utilisateurs; et doit être valide)" - # FIXME #~ msgid "validate %(email)s info or go to %(change_email_url)s" #~ msgstr "" #~ "valider l'information %(email)s ou visitez la page : %(change_email_url)s" - #~ msgid "old %(email)s kept, if you like go to %(change_email_url)s" #~ msgstr "" #~ "ancien email %(email)s conservé; si vous souhaitez le changer rendez-vous " #~ "à l'url %(change_email_url)s" - #~ msgid "your current %(email)s can be used for this" #~ msgstr "" #~ "<span class='big strong'>Votre adresse email actuelle %(email)s peut être " #~ "utilisée pour cela.</span>" - #~ msgid "thanks for verifying email" #~ msgstr "" #~ "<span class=\"big strong\">Merci d'avoir validé votre email.</span> Vous " @@ -7265,23 +6418,19 @@ msgstr "" #~ "abonnez pour recevoir périodiquement par email des questions qui vous " #~ "intéressent</strong>, ou être informé(e) des réponses et commentairesà " #~ "vos questions." - # FIXME #~ msgid "email key not sent" #~ msgstr "l'email comportant la clé de validation n'a pas été envoyé" - #~ msgid "email key not sent %(email)s change email here %(change_link)s" #~ msgstr "" #~ "<span class='big strong'>Ihre momentane E-Mail-Adresse %(email)s wurde " #~ "bereits bestätigt</span>. Es wurde keine neue Bestätigungsmail versandt. " -#~ "Sie können Ihre E-Mail-Adresse bei Bedarf <a " -#~ "href='%(change_link)s'>ändern</a>." - +#~ "Sie können Ihre E-Mail-Adresse bei Bedarf <a href='%(change_link)" +#~ "s'>ändern</a>." #~ msgid "register new %(provider)s account info, see %(gravatar_faq_url)s" #~ msgstr "" #~ "Créez vous un compte chez %(provider)s; pour plus d'infos, <a " #~ "href='%(gravatar_faq_url)s'>cliquez ici</a>" - #~ msgid "" #~ "%(username)s already exists, choose another name for \n" #~ " %(provider)s. Email is required too, see " @@ -7292,38 +6441,30 @@ msgstr "" #~ "pour \n" #~ " %(provider)s. Un email est aussi obligatoire, " #~ "cf. %(gravatar_faq_url)s" - #~ msgid "" #~ "register new external %(provider)s account info, see %(gravatar_faq_url)s" #~ msgstr "" #~ "Informations concernant la création d'un nouveau compte chez un " #~ "fournisseur d'identité externe %(provider)s; pour plus d'infos <a " #~ "href='%(gravatar_faq_url)s'>cliquez ici</a>" - #~ msgid "register new Facebook connect account info, see %(gravatar_faq_url)s" #~ msgstr "" #~ "Informations concernant la création d'un nouveau compte Facebook pour se " #~ "connecter; pour plus d'infos visitez la page %(gravatar_faq_url)s" - #~ msgid "This account already exists, please use another." #~ msgstr "Ce compte existe déjà; merci d'en utiliser un autre." - # FIXME #~ msgid "Screen name label" #~ msgstr "Pseudo" - # FIXME #~ msgid "Email address label" #~ msgstr "Adresse email" - #~ msgid "Tag filter tool will be your right panel, once you log in." #~ msgstr "" #~ "L'outil de filtrage des mots-clés sera affiché à droite de l'écran, une " #~ "fos que vous serez authentifié" - #~ msgid "create account" #~ msgstr "Créer un compte" - #~ msgid "" #~ "If you beleive that this message was sent in mistake - \n" #~ "no further action is needed. Just ingore this email, we apologize\n" @@ -7333,16 +6474,12 @@ msgstr "" #~ "aucune action de votre part n'est nécessaire. Ignorez ce mail, et " #~ "veuillez accepter nos excuses \n" #~ "pour le dérangement que nous avons occasionné" - #~ msgid "Login" #~ msgstr "Connexion" - #~ msgid "Why use OpenID?" #~ msgstr "Pourquoi utiliser OpenID ?" - #~ msgid "with openid it is easier" #~ msgstr "OpenID est le nouveau standard d’identification sur Internet." - #~ msgid "reuse openid" #~ msgstr "" #~ "Le principe d'OpenID est simple : au lieu de créer un nouveau compte sur " @@ -7351,32 +6488,24 @@ msgstr "" #~ "Orange...).Vous pourrez ensuite le réutiliser pour vous authentifier sur " #~ "d'autres sites \"compatibles OpenID\", sans avoir besoin de remplir le " #~ "formulaire d'inscription de ces sites..." - #~ msgid "openid is widely adopted" #~ msgstr "" #~ "OpenID à été adopté par tous les grands portails (Google, Yahoo, " #~ "Facebook, Microsoft, Flicker, ...) ce qui porte le nombre de comptes " #~ "OpenID à plus de 300 millions. Plus de 10000 sites sont compatibles " #~ "OpenID. " - #~ msgid "openid is supported open standard" #~ msgstr "OpenId est basé sur un standard international et libre." - #~ msgid "Find out more" #~ msgstr "En savoir plus." - #~ msgid "Get OpenID" #~ msgstr "Obtenir un compte OpenID" - #~ msgid "Traditional signup info" #~ msgstr "Informations sur la méthode classique de connexion" - #~ msgid "Create Account" #~ msgstr "Créer un compte" - #~ msgid "answer permanent link" #~ msgstr "lien permanent vers une réponse" - #, fuzzy #~ msgid "remove all flags" #~ msgstr "Voir tous les mots-clés" @@ -7462,6 +6591,7 @@ msgstr "" #~ msgstr "Ajouter un commentaire" #~ msgid "see <strong>%(counter)s</strong> more comment" + #~ msgid_plural "" #~ "see <strong>%(counter)s</strong> more comments\n" #~ " " @@ -7542,6 +6672,7 @@ msgstr "" #~ msgstr "Site internet" #~ msgid "<span class=\"count\">%(counter)s</span> Answer" + #~ msgid_plural "<span class=\"count\">%(counter)s</span> Answers" #~ msgstr[0] "<span class=\"count\">%(counter)s</span> Réponse" #~ msgstr[1] "<span class=\"count\">%(counter)s</span> Réponses" @@ -7555,17 +6686,13 @@ msgstr "" # FIXME #~ msgid "casted votes" #~ msgstr "abgegebene Stimmen" - # FIXME #~ msgid "answer tips" #~ msgstr "Conseils pour répondre" - #~ msgid "please try to provide details" #~ msgstr "Fournissez un maximum de détails." - #~ msgid "ask a question" #~ msgstr "Poser une question" - #~ msgid "" #~ "must have valid %(email)s to post, \n" #~ " see %(email_validation_faq_url)s\n" @@ -7576,42 +6703,30 @@ msgstr "" #~ "E-Mail-Adresse bestätigen. <a href='%(email_validation_faq_url)s'>Mehr " #~ "infos hier</a>.<br>Sie können Ihren Beitrag speichern und die Bestätigung " #~ "danach durchführen - Ihr Beitrag wird bis dahin gespeichert." - #~ msgid "Login/signup to post your question" #~ msgstr "Vous devez vous authentifier pour publier votre question " - #~ msgid "question tips" #~ msgstr "conseils pour poser une question" - #~ msgid "please ask a relevant question" #~ msgstr "Merci de poser une question pertinente." - #~ msgid "logout" #~ msgstr "Déconnexion" - #~ msgid "login" #~ msgstr "Connexion" - #~ msgid "no items in counter" #~ msgstr "0" - #~ msgid "your email address" #~ msgstr "Votre adresse email <i>(ne sera pas communiquée)</i>" - #~ msgid "choose password" #~ msgstr "choisissez un mot de passe" - #~ msgid "retype password" #~ msgstr "Saisissez à nouveau le mot de passe <i>(pour confirmation)</i>" - #~ msgid "%(badge_count)d %(badge_level)s badge" #~ msgid_plural "%(badge_count)d %(badge_level)s badges" #~ msgstr[0] "%(badge_count)d %(badge_level)s badge" #~ msgstr[1] "%(badge_count)d %(badge_level)s badges" - #~ msgid "user reputation in the community" #~ msgstr "réputation de l'utilisateur au sein de la communauté" - #, fuzzy #~ msgid "Please log in to ask questions" #~ msgstr "N'hésitez pas à poser des questions !" @@ -7640,6 +6755,7 @@ msgstr "" #~ "\n" #~ " %(counter)s Answer:\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " %(counter)s Answers:\n" @@ -7674,18 +6790,14 @@ msgstr "" # ##FIXME "Questions: [toutes] [ouvertes]" POUR GAGNER DE LA PLACE !!! #~ msgid "In:" #~ msgstr "Questions:" - #~ msgid "Email (not shared with anyone):" #~ msgstr "Votre email (ne sera pas communiqué):" - #~ msgid "Keys to connect the site with external services like Facebook, etc." #~ msgstr "" #~ "Clés pour connecter le site avec des services externes comme Facebook, " #~ "etc." - #~ msgid "Minimum reputation required to perform actions" #~ msgstr "Réputation minimale requise pour réaliser les actions" - #, fuzzy #~ msgid "Site modes" #~ msgstr "Sites" @@ -7813,13 +6925,12 @@ msgstr "" # FIXME #~ msgid "unmark-tag/" #~ msgstr "retirer-un-tag/" - #~ msgid "favorites" #~ msgstr "favorites" - # FIXME coquille dans phrase anglaise ??? #, fuzzy #~ msgid "this questions was selected as favorite %(cnt)s time" + #~ msgid_plural "this questions was selected as favorite %(cnt)s times" #~ msgstr[0] "Cette question a été sélectionnée favorite" #~ msgstr[1] "Cette question a été sélectionnée favorite" @@ -7861,10 +6972,8 @@ msgstr "" #~ msgstr "" #~ "Informations concernant la procédure de validation d'email avec " #~ "%(send_email_key_url)s %(gravatar_faq_url)s" - #~ msgid "." #~ msgstr "." - #~ msgid "" #~ "As a registered user you can login with your OpenID, log out of the site " #~ "or permanently remove your account." @@ -7872,18 +6981,16 @@ msgstr "" #~ "Etant un utilisateur enregistré, vous pouvez vous connecter avec votre " #~ "OpenID, vous déconnecter du site, ou supprimer définitivement votre " #~ "compte." - #~ msgid "Logout now" #~ msgstr "Se déconnecter" - #~ msgid "see questions tagged '%(tag_name)s'" #~ msgstr "Voir les questions marquées par '%(tag_name)s' " - #, fuzzy #~ msgid "" #~ "\n" #~ " %(q_num)s question\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " %(q_num)s questions\n" @@ -7927,6 +7034,7 @@ msgstr "" #~ "\n" #~ " vote\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " votes\n" @@ -7945,6 +7053,7 @@ msgstr "" #~ "\n" #~ " answer \n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " answers \n" @@ -7960,6 +7069,7 @@ msgstr "" #~ "\n" #~ " view\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " views\n" @@ -7975,6 +7085,7 @@ msgstr "" #~ "\n" #~ " vote\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " votes\n" @@ -7990,6 +7101,7 @@ msgstr "" #~ "\n" #~ " answer \n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " answers \n" @@ -8005,6 +7117,7 @@ msgstr "" #~ "\n" #~ " view\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " views\n" @@ -8333,89 +7446,61 @@ msgstr "" #~ msgstr "" #~ "Le mot de passe de '%s' a été modifié. Vous pouvez maintenant vous " #~ "connecter." - #~ msgid "email update message subject" #~ msgstr "Sujet du message de modification d'email" - #~ msgid "sorry, system error" #~ msgstr "Désolé, erreur interne" - #~ msgid "Account functions" #~ msgstr "Gérer votre compte" - #~ msgid "Change email " #~ msgstr "Changer d'adresse email" - #~ msgid "Add or update the email address associated with your account." #~ msgstr "Ajouter ou modifier l'adresse email associée à votre compte" - #~ msgid "Change OpenID" #~ msgstr "Changer d'OpenID" - #~ msgid "Change openid associated to your account" #~ msgstr "Changer l'OpenID associé à votre compte" - #~ msgid "Erase your username and all your data from website" #~ msgstr "" #~ "Supprimer votre nom d'utilisateur et toutes vos données de notre site" - #~ msgid "toggle preview" #~ msgstr "basculer vers l'aperçu" - #~ msgid "reading channel" #~ msgstr "Canal de lecture" - #~ msgid "[author]" #~ msgstr "[auteur]" - #~ msgid "[publisher]" #~ msgstr "[auteur de la publication]" - #~ msgid "[publication date]" #~ msgstr "[date de publication]" - #~ msgid "[price]" #~ msgstr "[prix]" - #~ msgid "currency unit" #~ msgstr "devise" - #~ msgid "[pages]" #~ msgstr "[pages]" - #~ msgid "pages abbreviation" #~ msgstr "abbréviation des pages" - #~ msgid "[tags]" #~ msgstr "[mots-cles]" - #~ msgid "author blog" #~ msgstr "blog de l'auteur" - #~ msgid "book directory" #~ msgstr "bibliothèque" - #~ msgid "buy online" #~ msgstr "acheter en ligne" - #~ msgid "reader questions" #~ msgstr "questions des lecteurs" - #~ msgid "ask the author" #~ msgstr "Demander à l'auteur" - #~ msgid "this question was selected as favorite" #~ msgstr "Cette question a été sélectionnée comme favorite" - #~ msgid "number of times" #~ msgstr "fois" - #~ msgid "the answer has been accepted to be correct" #~ msgstr "La réponse a été jugée correcte" - #~ msgid "subscribe to book RSS feed" #~ msgstr "S'abonner au flux RSS" - #~ msgid "" #~ "\n" #~ "<p>%(update_author_name)s left a <a href=\"%(post_url)s\">new comment</" @@ -8426,115 +7511,84 @@ msgstr "" #~ "<p>%(update_author_name)s a laissé <a href=\"%(post_url)s\">nouveau " #~ "commentaire</a>\n" #~ " concernant une réponse à la question \"%(origin_post_title)s\"</p>\n" - #~ msgid "%(rev_count)s revision" #~ msgid_plural "%(rev_count)s revisions" #~ msgstr[0] "%(rev_count)s revision" #~ msgstr[1] "%(rev_count)s revisions" - #~ msgid "tags help us keep Questions organized" #~ msgstr "Les tags nous permettent de classer et organiser les questions." - #~ msgid "less answers" #~ msgstr "nb réponses (↑)" - # FIXME #~ msgid "click to see coldest questions" #~ msgstr "" #~ "Cliquez ici pour voir les questions ayant obtenu le moins de réponses" - #~ msgid "more answers" #~ msgstr "nb réponses (↓)" - #~ msgid "unpopular" #~ msgstr "popularité (↑)" - #~ msgid "popular" #~ msgstr "popularité (↓)" - #~ msgid "responses" #~ msgstr "réponses" - #~ msgid "Account: change OpenID URL" #~ msgstr "Compte : changer d'URL OpenID" - #~ msgid "" #~ "This is where you can change your OpenID URL. Make sure you remember it!" #~ msgstr "Cette page vous permet de changer d'URL OpenID. Ne l'oubliez pas !" - #~ msgid "Please correct errors below:" #~ msgstr "Merci de bien vouloir corriger les erreurs ci-dessous :" - #~ msgid "" #~ "This is where you can change your password. Make sure you remember it!" #~ msgstr "" #~ "Cette page vous permet de changer de mot de passe. Ne l'oubliez pas !" - # FIXME #~ msgid "Connect your OpenID with this site" #~ msgstr "Associer votre OpenID avec ce site" - #~ msgid "Sorry, looks like we have some errors:" #~ msgstr "Désolé, il semble qu'une erreur se soit produite :" - #~ msgid "Existing account" #~ msgstr "Ce compte existe déjà" - #~ msgid "password" #~ msgstr "Mot de passe" - #~ msgid "Forgot your password?" #~ msgstr "Mot de passe oublié ?" - #~ msgid "Account: delete account" #~ msgstr "Compte : compte supprimé" - #~ msgid "" #~ "Note: After deleting your account, anyone will be able to register this " #~ "username." #~ msgstr "" #~ "Attention : après avoir supprimé votre compte, n'importe qui pourra créer " #~ "un compte en reprenant le nom d'utilisateur que vous utilisiez..." - #~ msgid "Check confirm box, if you want delete your account." #~ msgstr "" #~ "Merci de cocher la case pour confirmer que vous voulez vraiment supprimer " #~ "votre compte." - #~ msgid "I am sure I want to delete my account." #~ msgstr "Oui, je souhaite supprimer définitivement mon compte." - #~ msgid "Password/OpenID URL" #~ msgstr "Mot de passe / URL OpenID" - #~ msgid "(required for your security)" #~ msgstr "(obligatoire, pour des raisons de sécurité)" - #~ msgid "Delete account permanently" #~ msgstr "Supprimer définitivement ce compte" - #~ msgid "Traditional login information" #~ msgstr "Information sur la méthode classique de connexion" - #~ msgid "" #~ "how to login with password through external login website or use " #~ "%(feedback_url)s" #~ msgstr "" #~ "comment se connecter avec un mot de passe à travers le site web d'un " #~ "fournisseur d'identité externe ou utiliser %(feedback_url)s" - #~ msgid "Send new password" #~ msgstr "Un nouveau mot de passe a été envoyé" - #~ msgid "password recovery information" #~ msgstr "Informations concernant la récupération du mot de passe" - #~ msgid "Reset password" #~ msgstr "Réinitialiser le mot de passe" - #~ msgid "return to login" #~ msgstr "Retour à la page de connexion" - #~ msgid "" #~ "Someone has requested to reset your password on %(site_url)s.\n" #~ "If it were not you, it is safe to ignore this email." @@ -8543,14 +7597,12 @@ msgstr "" #~ "forum de Questions/Réponses %(site_url)s.\n" #~ "Si vous n'êtes pas à l'origine de cette demande, ignorez ce message, et " #~ "votre mot de passe ne sera pas modifié..." - #~ msgid "" #~ "email explanation how to use new %(password)s for %(username)s\n" #~ "with the %(key_link)s" #~ msgstr "" #~ "Email expliquant comment utiliser le nouveau mot de passe %(password)s " #~ "pour l'utilisateur %(username)s avec la clé %(key_link)s" - #~ msgid "Click to sign in through any of these services." #~ msgstr "" #~ "<span class='grey'>OpenID est un nouveau système d’authentification " @@ -8565,7 +7617,6 @@ msgstr "" #~ "sur tout autre site \"compatible OpenID\" </span><p><span class=\"big" #~ "\">Si vous possédez un compte \"OpenID\" chez l'un des fournisseurs ci-" #~ "dessous, cliquez sur son logo pour vous authentifier : </span>" - # msgid "Click to sign in through any of these services." # msgstr "" # "<p><span class=\"big strong\">Please select your favorite login method @@ -8582,68 +7633,50 @@ msgstr "" #~ "Saisissez <span id=\"enter_your_what\">l'identifiant que vous utilisez " #~ "habituellement chez ce fournisseur de services</span><span class='grey'>" #~ "(à condition qu'il soit compatible OpenID)</span>" - #~ msgid "" #~ "Enter your <a class=\"openid_logo\" href=\"http://openid.net\">OpenID</a> " #~ "web address" #~ msgstr "" #~ "ou bien saisissez directement votre adresse web OpenID <a class=" #~ "\"openid_logo\" href=\"http://openid.net\">OpenID</a> " - #~ msgid "Enter your login name and password" #~ msgstr "Saisissez votre nom d'utilisateur AskBot et votre mot de passe" - #~ msgid "Create account" #~ msgstr "Créer un compte" - #~ msgid "Connect to %(settings.APP_SHORT_NAME)s with Facebook!" #~ msgstr "Se connecter à %(settings.APP_SHORT_NAME)s avec FaceBook !" - # TODO vérifier que %s contient le username et pas le nouveau mot de passe ! #~ msgid "Your question and all of it's answers have been deleted" #~ msgstr "" #~ "Votre question a été supprimée, ainsi que toutes les réponses associées." - #~ msgid "Your question has been deleted" #~ msgstr "Votre question a été supprimée." - #~ msgid "The question and all of it's answers have been deleted" #~ msgstr "La question et toutes ses réponses ont été supprimées." - #~ msgid "The question has been deleted" #~ msgstr "La question a été supprimée." - #~ msgid "question" #~ msgstr "question" - #~ msgid "Automatically accept user's contributions for the email updates" #~ msgstr "" #~ "Accepter automatiquement les contributions d'utilisateurs pour les mises " #~ "à jour par email" - #~ msgid "unanswered/" #~ msgstr "nonrepondu/" - #~ msgid "nimda/" #~ msgstr "nimda/" - #~ msgid "Première révision de mots-clés" #~ msgstr "Erste Tag-Bearbeitung" - #~ msgid "open any closed question" #~ msgstr "(ré)ouvrir n'importe quelle question cloturée" - #~ msgid "books" #~ msgstr "Livres" - #~ msgid "general message about privacy" #~ msgstr "" #~ "Datenschutz und die Privatsphäre unserer Nutzer sind uns wichtig. Auf " #~ "dieser Seite wird erklärt, wie wir mit Ihren Daten umgehen." - #~ msgid "Site Visitors" #~ msgstr "Visiteurs du site" - #~ msgid "what technical information is collected about visitors" #~ msgstr "" #~ "Information über das Anschauen von Fragen, Fragen-Versionen und " @@ -8651,44 +7684,35 @@ msgstr "" #~ "Nutzer aufgezeichnet, um eine korrekte Zählung der Zugriffe sowie einen " #~ "intakten Datenbestand zu gewährleisten und um zuvor angeforderte " #~ "Benachrichtigungsmails an die Nutzer zu versenden." - #~ msgid "Personal Information" #~ msgstr "Informations personnelles" - #~ msgid "details on personal information policies" #~ msgstr "" #~ "Benutzer können auf freiwilliger Basis Informationen über sich in ihrem " #~ "Benutzerprofil ablegen. Diese Daten werden nicht ohne die ausdrückliche " #~ "Zustimmung des Nutzers öffentlich angezeigt." - #~ msgid "details on sharing data with third parties" #~ msgstr "" #~ "Keine Daten, die nicht mit Zustimmung des Benutzers öffentlich auf der " #~ "Internetseite angezeigt werden, werden mit Dritten geteilt. " - #~ msgid "Policy Changes" #~ msgstr "Changement de politique" - #~ msgid "how privacy policies can be changed" #~ msgstr "" #~ "Unsere Datenschutzrichtlinien können sich im Laufe der Zeit verfeinern " #~ "und verändern. Registrierte Benutzer werden in diesen Fällen automatisch " #~ "benachrichtigt." - #~ msgid "Found by tags" #~ msgstr "Trouvés (par tags)" - #~ msgid "Search results" #~ msgstr "Résultats de la recherche" - #~ msgid "Found by title" #~ msgstr "Trouvés (par titre)" - #~ msgid "Unanswered questions" #~ msgstr "Questions sans réponses" - #, fuzzy #~ msgid " %(q_num)s question found" + #~ msgid_plural "%(q_num)s questions found" #~ msgstr[0] "%(q_num)s question trouvée" #~ msgstr[1] "%(q_num)s questions trouvées" @@ -8727,11 +7751,13 @@ msgstr "" #~ msgstr "livres/" #~ msgid " One question found" + #~ msgid_plural "%(q_num)s questions found" #~ msgstr[0] "Eine Frage gefunden" #~ msgstr[1] "%(q_num)s Fragen gefunden" #~ msgid "one revision" + #~ msgid_plural "%(rev_count)s revisions" #~ msgstr[0] "1 Version" #~ msgstr[1] "%(rev_count)s Versionen" @@ -8744,6 +7770,7 @@ msgstr "" #~ " see <strong>one</strong> " #~ "more \n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " see <strong>%(counter)s</" @@ -8761,6 +7788,7 @@ msgstr "" #~ " see <strong>one</strong> more " #~ "comment\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " see <strong>%(counter)s</" @@ -8778,6 +7806,7 @@ msgstr "" #~ " see <strong>one</" #~ "strong> more \n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " see <strong>" @@ -8795,6 +7824,7 @@ msgstr "" #~ " see <strong>one</" #~ "strong> more comment\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " see <strong>" @@ -8811,6 +7841,7 @@ msgstr "" #~ "\n" #~ " (one comment)\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " the answer has been commented %(comment_count)s " @@ -8827,6 +7858,7 @@ msgstr "" #~ "\n" #~ " <span class=\"count\">1</span> Vote\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " <span class=\"count\">%(cnt)s</span> Votes\n" @@ -8842,6 +7874,7 @@ msgstr "" #~ "\n" #~ " <span class=\"count\">1</span> Tag\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " <span class=\"count\">%(counter)s</span> Tags\n" @@ -8870,6 +7903,7 @@ msgstr "" #~ msgid "" #~ " have total %(q_num)s questions containing %(searchtitle)s in full text " + #~ msgid_plural "" #~ " have total %(q_num)s questions containing %(searchtitle)s in full text " #~ msgstr[0] "" @@ -8880,6 +7914,7 @@ msgstr "" #~ "<strong><span class=\"darkred\">%(searchtitle)s</span></strong></p>" #~ msgid " have total %(q_num)s questions containing %(searchtitle)s " + #~ msgid_plural " have total %(q_num)s questions containing %(searchtitle)s " #~ msgstr[0] "" #~ "<div class=\"questions-count\">%(q_num)s</div><p>question with title " @@ -8891,6 +7926,7 @@ msgstr "" #~ "strong></p>" #~ msgid " have total %(q_num)s unanswered questions " + #~ msgid_plural " have total %(q_num)s unanswered questions " #~ msgstr[0] "" #~ "<div class=\"questions-count\">%(q_num)s</div>questions <strong>without " @@ -8915,6 +7951,7 @@ msgstr "" #~ "\n" #~ " have total %(q_num)s unanswered questions\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " have total %(q_num)s unanswered questions\n" @@ -8932,6 +7969,7 @@ msgstr "" #~ "\n" #~ " have total %(q_num)s questions\n" #~ " " + #~ msgid_plural "" #~ "\n" #~ " have total %(q_num)s questions\n" diff --git a/askbot/locale/fr/LC_MESSAGES/djangojs.mo b/askbot/locale/fr/LC_MESSAGES/djangojs.mo Binary files differindex 2cadf96d..79489007 100644 --- a/askbot/locale/fr/LC_MESSAGES/djangojs.mo +++ b/askbot/locale/fr/LC_MESSAGES/djangojs.mo diff --git a/askbot/locale/fr/LC_MESSAGES/djangojs.po b/askbot/locale/fr/LC_MESSAGES/djangojs.po index 2664d5b0..1c65f20a 100644 --- a/askbot/locale/fr/LC_MESSAGES/djangojs.po +++ b/askbot/locale/fr/LC_MESSAGES/djangojs.po @@ -5,19 +5,21 @@ # Translators: # Christophe kryskool <christophe.chauvet@gmail.com>, 2011. # Mademoiselle Geek <mademoisellegeek42@gmail.com>, 2011. +# Alexandre Lissy <alexandrelissy@free.fr>, 2012. +# msgid "" msgstr "" "Project-Id-Version: askbot\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-04-18 18:54-0500\n" -"PO-Revision-Date: 2011-12-17 21:30+0000\n" -"Last-Translator: Mademoiselle Geek <mademoisellegeek42@gmail.com>\n" -"Language-Team: French (http://www.transifex.net/projects/p/askbot/team/fr/)\n" +"PO-Revision-Date: 2012-06-14 15:40+0200\n" +"Last-Translator: Alexandre Lissy <alexandrelissy@free.fr>\n" +"Language-Team: français <>\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" +"Content-Transfer-Encoding: 8bits\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" "X-Generator: Translate Toolkit 1.9.0\n" #: skins/common/media/jquery-openid/jquery.openid.js:73 @@ -111,7 +113,6 @@ msgstr "" "publicité, des remarques malveillantes, etc.?" #: skins/common/media/js/post.js:331 -#, fuzzy msgid "please confirm removal of offensive flag" msgstr "" "êtes-vous sûr que ce message est offensant, contient du spam, de la " @@ -172,24 +173,22 @@ msgstr[0] "Supprimer cet avertissement?" msgstr[1] "Supprimer ces avertissements?" #: skins/common/media/js/user.js:65 -#, fuzzy msgid "Close this entry?" msgid_plural "Close these entries?" -msgstr[0] "supprimer ce commentaire" -msgstr[1] "supprimer ce commentaire" +msgstr[0] "Fermer cette entrée ?" +msgstr[1] "Fermer ces entrées ?" #: skins/common/media/js/user.js:72 msgid "Remove all flags on this entry?" msgid_plural "Remove all flags on these entries?" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Supprimer tous les drapeaux sur cette entrée ?" +msgstr[1] "Supprimer tous les drapeaux sur ces entrées ?" #: skins/common/media/js/user.js:79 -#, fuzzy msgid "Delete this entry?" msgid_plural "Delete these entries?" -msgstr[0] "supprimer ce commentaire" -msgstr[1] "supprimer ce commentaire" +msgstr[0] "Supprimer cette entrée ?" +msgstr[1] "Supprimer ces entrées ?" #: skins/common/media/js/user.js:159 msgid "Please <a href=\"%(signin_url)s\">signin</a> to follow %(username)s" diff --git a/askbot/locale/ru/LC_MESSAGES/django.po b/askbot/locale/ru/LC_MESSAGES/django.po index 0a8b54f0..30744f83 100644 --- a/askbot/locale/ru/LC_MESSAGES/django.po +++ b/askbot/locale/ru/LC_MESSAGES/django.po @@ -4844,7 +4844,7 @@ msgstr "редактировать" #: skins/default/templates/macros.html:525 #: skins/default/templates/question.html:511 msgid "add comment" -msgstr "добавить комментарий" +msgstr "добавить" #: skins/default/templates/macros.html:518 #, python-format @@ -8403,7 +8403,7 @@ msgstr "Сорт. по »" #: skins/default/templates/main_page/headline.html:12 msgid "Tagged" -msgstr "Отмечено" +msgstr "В тегах" #: skins/default/templates/main_page/nothing_found.html:8 msgid "Please follow some questions or follow some users." diff --git a/askbot/utils/mail.py b/askbot/mail/__init__.py index aa4df320..7ff8a2d3 100644 --- a/askbot/utils/mail.py +++ b/askbot/mail/__init__.py @@ -10,6 +10,7 @@ from django.core.exceptions import PermissionDenied from django.forms import ValidationError from django.utils.translation import ugettext_lazy as _ from django.utils.translation import string_concat +from django.template import Context from askbot import exceptions from askbot import const from askbot.conf import settings as askbot_settings @@ -144,21 +145,31 @@ def mail_moderators( if raise_on_failure == True: raise exceptions.EmailNotSent(unicode(error)) -ASK_BY_EMAIL_USAGE = _( -"""<p>To ask by email, please:</p> -<ul> - <li>Format the subject line as: [Tag1; Tag2] Question title</li> - <li>Type details of your question into the email body</li> -</ul> -<p>Note that tags may consist of more than one word, and tags -may be separated by a semicolon or a comma</p> -""" +INSTRUCTIONS_PREAMBLE = _('<p>To ask by email, please:</p>') +QUESTION_TITLE_INSTRUCTION = _( + '<li>Type title in the subject line</li>' +) +QUESTION_DETAILS_INSTRUCTION = _( + '<li>Type details of your question into the email body</li>' +) +OPTIONAL_TAGS_INSTRUCTION = _( +"""<li>The beginning of the subject line can contain tags, +<em>enclosed in the square brackets</em> like so: [Tag1; Tag2]</li>""" +) +REQUIRED_TAGS_INSTRUCTION = _( +"""<li>In the beginning of the subject add at least one tag +<em>enclosed in the brackets</em> like so: [Tag1; Tag2].</li>""" +) +TAGS_INSTRUCTION_FOOTNOTE = _( +"""<p>Note that a tag may consist of more than one word, to separate +the tags, use a semicolon or a comma, for example, [One tag; Other tag]</p>""" ) -def bounce_email(email, subject, reason = None, body_text = None): +def bounce_email( + email, subject, reason = None, body_text = None, reply_to = None +): """sends a bounce email at address ``email``, with the subject line ``subject``, accepts several reasons for the bounce: - * ``'problem_posting'``, ``unknown_user`` and ``permission_denied`` * ``body_text`` in an optional parameter that allows to append extra text to the message @@ -168,7 +179,28 @@ def bounce_email(email, subject, reason = None, body_text = None): '<p>Sorry, there was an error posting your question ' 'please contact the %(site)s administrator</p>' ) % {'site': askbot_settings.APP_SHORT_NAME} - error_message = string_concat(error_message, ASK_BY_EMAIL_USAGE) + + if askbot_settings.TAGS_ARE_REQUIRED: + error_message = string_concat( + INSTRUCTIONS_PREAMBLE, + '<ul>', + QUESTION_TITLE_INSTRUCTION, + REQUIRED_TAGS_INSTRUCTION, + QUESTION_DETAILS_INSTRUCTION, + '</ul>', + TAGS_INSTRUCTION_FOOTNOTE + ) + else: + error_message = string_concat( + INSTRUCTIONS_PREAMBLE, + '<ul>', + QUESTION_TITLE_INSTRUCTION, + QUESTION_DETAILS_INSTRUCTION, + OPTIONAL_TAGS_INSTRUCTION, + '</ul>', + TAGS_INSTRUCTION_FOOTNOTE + ) + elif reason == 'unknown_user': error_message = _( '<p>Sorry, in order to post questions on %(site)s ' @@ -177,25 +209,30 @@ def bounce_email(email, subject, reason = None, body_text = None): 'site': askbot_settings.APP_SHORT_NAME, 'url': url_utils.get_login_url() } - elif reason == 'permission_denied': + elif reason == 'permission_denied' and body_text is None: error_message = _( '<p>Sorry, your question could not be posted ' 'due to insufficient privileges of your user account</p>' ) + elif body_text: + error_message = body_text else: raise ValueError('unknown reason to bounce an email: "%s"' % reason) - if body_text != None: - error_message = string_concat(error_message, body_text) #print 'sending email' #print email #print subject #print error_message + headers = {} + if reply_to: + headers['Reply-To'] = reply_to + send_mail( recipient_list = (email,), subject_line = 'Re: ' + subject, - body_text = error_message + body_text = error_message, + headers = headers ) def extract_reply(text): @@ -221,7 +258,29 @@ def process_attachment(attachment): markdown_link = '!' + markdown_link return markdown_link, file_storage -def process_parts(parts): +def extract_user_signature(text, reply_code): + """extracts email signature as text trailing + the reply code""" + if reply_code in text: + #extract the signature + tail = list() + for line in reversed(text.splitlines()): + #scan backwards from the end until the magic line + if reply_code in line: + break + tail.insert(0, line) + + #strip off the leading quoted lines, there could be one or two + #also strip empty lines + while tail and (tail[0].startswith('>') or tail[0].strip() == ''): + tail.pop(0) + + return '\n'.join(tail) + else: + return '' + + +def process_parts(parts, reply_code = None): """Process parts will upload the attachments and parse out the body, if body is multipart. Secondly - links to attachments will be added to the body of the question. @@ -237,7 +296,7 @@ def process_parts(parts): stored_files.append(stored_file) attachments_markdown += '\n\n' + markdown elif part_type == 'body': - body_markdown += '\n\n' + content + body_markdown += '\n\n' + content.strip('\n\t ') elif part_type == 'inline': markdown, stored_file = process_attachment(content) stored_files.append(stored_file) @@ -245,25 +304,32 @@ def process_parts(parts): #if the response separator is present - #split the body with it, and discard the "so and so wrote:" part + if reply_code: + signature = extract_user_signature(body_markdown, reply_code) + else: + signature = None body_markdown = extract_reply(body_markdown) body_markdown += attachments_markdown - return body_markdown.strip(), stored_files + return body_markdown.strip(), stored_files, signature -def process_emailed_question(from_address, subject, parts, tags = None): +def process_emailed_question( + from_address, subject, body_text, stored_files, tags = None +): """posts question received by email or bounces the message""" #a bunch of imports here, to avoid potential circular import issues from askbot.forms import AskByEmailForm - from askbot.models import User + from askbot.models import ReplyAddress, User + from askbot.mail import messages + reply_to = None try: #todo: delete uploaded files when posting by email fails!!! - body, stored_files = process_parts(parts) data = { 'sender': from_address, 'subject': subject, - 'body_text': body + 'body_text': body_text } form = AskByEmailForm(data) if form.is_valid(): @@ -271,6 +337,18 @@ def process_emailed_question(from_address, subject, parts, tags = None): user = User.objects.get( email__iexact = email_address ) + + if user.can_post_by_email() == False: + raise PermissionDenied(messages.insufficient_reputation(user)) + + if user.email_isvalid == False: + reply_to = ReplyAddress.objects.create_new( + user = user, + reply_action = 'validate_email' + ).as_email_address() + message = messages.ask_for_signature(user, footer_code = reply_to) + raise PermissionDenied(message) + tagnames = form.cleaned_data['tagnames'] title = form.cleaned_data['title'] body_text = form.cleaned_data['body_text'] @@ -279,10 +357,15 @@ def process_emailed_question(from_address, subject, parts, tags = None): if tags: tagnames += ' ' + ' '.join(tags) + stripped_body_text = user.strip_email_signature(body_text) + if stripped_body_text == body_text and user.email_signature: + #todo: send an email asking to update the signature + raise ValueError('email signature changed') + user.post_question( title = title, tags = tagnames.strip(), - body_text = body_text, + body_text = stripped_body_text, by_email = True, email_address = from_address ) @@ -298,7 +381,8 @@ def process_emailed_question(from_address, subject, parts, tags = None): email_address, subject, reason = 'permission_denied', - body_text = unicode(error) + body_text = unicode(error), + reply_to = reply_to ) except ValidationError: if from_address: diff --git a/askbot/mail/lamson_handlers.py b/askbot/mail/lamson_handlers.py new file mode 100644 index 00000000..f7053414 --- /dev/null +++ b/askbot/mail/lamson_handlers.py @@ -0,0 +1,317 @@ +import re +import functools +from django.core.files.uploadedfile import SimpleUploadedFile +from django.conf import settings as django_settings +from django.template import Context +from django.utils.translation import ugettext as _ +from lamson.routing import route, stateless +from lamson.server import Relay +from askbot.models import ReplyAddress, Tag +from askbot import mail +from askbot.conf import settings as askbot_settings +from askbot.skins.loaders import get_template + + +#we might end up needing to use something like this +#to distinguish the reply text from the quoted original message +""" +def _strip_message_qoute(message_text): + import re + result = message_text + pattern = "(?P<qoute>" + \ + "On ([a-zA-Z0-9, :/<>@\.\"\[\]]* wrote:.*)|" + \ + "From: [\w@ \.]* \[mailto:[\w\.]*@[\w\.]*\].*|" + \ + "From: [\w@ \.]*(\n|\r\n)+Sent: [\*\w@ \.,:/]*(\n|\r\n)+To:.*(\n|\r\n)+.*|" + \ + "[- ]*Forwarded by [\w@ \.,:/]*.*|" + \ + "From: [\w@ \.<>\-]*(\n|\r\n)To: [\w@ \.<>\-]*(\n|\r\n)Date: [\w@ \.<>\-:,]*\n.*|" + \ + "From: [\w@ \.<>\-]*(\n|\r\n)To: [\w@ \.<>\-]*(\n|\r\n)Sent: [\*\w@ \.,:/]*(\n|\r\n).*|" + \ + "From: [\w@ \.<>\-]*(\n|\r\n)To: [\w@ \.<>\-]*(\n|\r\n)Subject:.*|" + \ + "(-| )*Original Message(-| )*.*)" + groups = re.search(pattern, email_text, re.IGNORECASE + re.DOTALL) + qoute = None + if not groups is None: + if groups.groupdict().has_key("qoute"): + qoute = groups.groupdict()["qoute"] + if qoute: + result = reslut.split(qoute)[0] + #if the last line contains an email message remove that one too + lines = result.splitlines(True) + if re.search(r'[\w\.]*@[\w\.]*\].*', lines[-1]): + result = '\n'.join(lines[:-1]) + return result +""" + +def get_disposition(part): + """return list of part's content dispositions + or an empty list + """ + dispositions = part.content_encoding.get('Content-Disposition', None) + if dispositions: + return dispositions[0] + else: + return list() + +def get_attachment_info(part): + return part.content_encoding['Content-Disposition'][1] + +def is_attachment(part): + """True if part content disposition is + attachment""" + return get_disposition(part) == 'attachment' + +def is_inline_attachment(part): + """True if part content disposition is + inline""" + return get_disposition(part) == 'inline' + +def format_attachment(part): + """takes message part and turns it into SimpleUploadedFile object""" + att_info = get_attachment_info(part) + name = att_info.get('filename', None) + content_type = get_content_type(part) + return SimpleUploadedFile(name, part.body, content_type) + +def get_content_type(part): + """return content type of the message part""" + return part.content_encoding.get('Content-Type', (None,))[0] + +def is_body(part): + """True, if part is plain text and is not attachment""" + if get_content_type(part) == 'text/plain': + if not is_attachment(part): + return True + return False + +def get_part_type(part): + if is_body(part): + return 'body' + elif is_attachment(part): + return 'attachment' + elif is_inline_attachment(part): + return 'inline' + +def get_parts(message): + """returns list of tuples (<part_type>, <formatted_part>), + where <part-type> is one of 'body', 'attachment', 'inline' + and <formatted-part> - will be in the directly usable form: + * if it is 'body' - then it will be unicode text + * for attachment - it will be django's SimpleUploadedFile instance + + There may be multiple 'body' parts as well as others + usually the body is split when there are inline attachments present. + """ + + parts = list() + + simple_body = '' + if message.body(): + simple_body = message.body() + parts.append(('body', simple_body)) + + for part in message.walk(): + part_type = get_part_type(part) + if part_type == 'body': + part_content = part.body + if part_content == simple_body: + continue#avoid duplication + elif part_type in ('attachment', 'inline'): + part_content = format_attachment(part) + else: + continue + parts.append((part_type, part_content)) + return parts + +def process_reply(func): + @functools.wraps(func) + def wrapped(message, host = None, address = None): + """processes forwarding rules, and run the handler + in the case of error, send a bounce email + """ + try: + for rule in django_settings.LAMSON_FORWARD: + if re.match(rule['pattern'], message.base['to']): + relay = Relay(host=rule['host'], + port=rule['port'], debug=1) + relay.deliver(message) + return + except AttributeError: + pass + + error = None + try: + reply_address = ReplyAddress.objects.get( + address = address, + allowed_from_email = message.From + ) + + #here is the business part of this function + func( + from_address = message.From, + subject_line = message['Subject'], + parts = get_parts(message), + reply_address_object = reply_address + ) + + except ReplyAddress.DoesNotExist: + error = _("You were replying to an email address\ + unknown to the system or you were replying from a different address from the one where you\ + received the notification.") + except Exception, e: + import sys + sys.stderr.write(str(e)) + import traceback + sys.stderr.write(traceback.format_exc()) + + if error is not None: + template = get_template('email/reply_by_email_error.html') + body_text = template.render(Context({'error':error})) + mail.send_mail( + subject_line = "Error posting your reply", + body_text = body_text, + recipient_list = [message.From], + ) + + return wrapped + +@route('(addr)@(host)', addr = '.+') +@stateless +def ASK(message, host = None, addr = None): + """lamson handler for asking by email, + to the forum in general and to a specific group""" + + #we need to exclude some other emails by prefix + if addr.startswith('reply-'): + return + if addr.startswith('welcome-'): + return + + parts = get_parts(message) + from_address = message.From + #why lamson does not give it normally? + subject = message['Subject'].strip('\n\t ') + body_text, stored_files, unused = mail.process_parts(parts) + if addr == 'ask': + mail.process_emailed_question( + from_address, subject, body_text, stored_files + ) + else: + if askbot_settings.GROUP_EMAIL_ADDRESSES_ENABLED == False: + return + try: + group_tag = Tag.group_tags.get( + deleted = False, + name__iexact = addr + ) + mail.process_emailed_question( + from_address, subject, body_text, stored_files, + tags = [group_tag.name, ] + ) + except Tag.DoesNotExist: + #do nothing because this handler will match all emails + return + except Tag.MultipleObjectsReturned: + return + +@route('welcome-(address)@(host)', address='.+') +@stateless +@process_reply +def VALIDATE_EMAIL( + parts = None, + reply_address_object = None, + from_address = None, + **kwargs +): + """process the validation email and save + the email signature + todo: go a step further and + """ + reply_code = reply_address_object.address + try: + content, stored_files, signature = mail.process_parts(parts, reply_code) + user = reply_address_object.user + if signature and signature != user.email_signature: + user.email_signature = signature + user.email_isvalid = True + user.save() + + data = { + 'site_name': askbot_settings.APP_SHORT_NAME, + 'site_url': askbot_settings.APP_URL, + 'ask_address': 'ask@' + askbot_settings.REPLY_BY_EMAIL_HOSTNAME + } + template = get_template('email/re_welcome_lamson_on.html') + + mail.send_mail( + subject_line = _('Re: Welcome to %(site_name)s') % data, + body_text = template.render(Context(data)), + recipient_list = [from_address,] + ) + except ValueError: + raise ValueError( + _( + 'Please reply to the welcome email ' + 'without editing it' + ) + ) + +@route('reply-(address)@(host)', address='.+') +@stateless +@process_reply +def PROCESS( + parts = None, + reply_address_object = None, + subject_line = None, + from_address = None, + **kwargs +): + """handler to process the emailed message + and make a post to askbot based on the contents of + the email, including the text body and the file attachments""" + #1) get actual email content + # todo: factor this out into the process_reply decorator + reply_code = reply_address_object.address + body_text, stored_files, signature = mail.process_parts(parts, reply_code) + + #2) process body text and email signature + user = reply_address_object.user + if signature:#if there, then it was stripped + if signature != user.email_signature: + user.email_signature = signature + else:#try to strip signature + stripped_body_text = user.strip_email_signature(body_text) + #todo: add test cases for emails without the signature + if stripped_body_text == body_text and user.email_signature: + #todo: send an email asking to update the signature + raise ValueError('email signature changed or unknown') + body_text = stripped_body_text + + #3) validate email address and save user + user.email_isvalid = True + user.save()#todo: actually, saving is not necessary, if nothing changed + + #4) actually make an edit in the forum + robj = reply_address_object + add_post_actions = ('post_comment', 'post_answer', 'auto_answer_or_comment') + if robj.reply_action == 'replace_content': + robj.edit_post(body_text, title = subject_line) + elif robj.reply_action == 'append_content': + robj.edit_post(body_text)#in this case we don't touch the title + elif robj.reply_action in add_post_actions: + if robj.was_used: + robj.edit_post(body_text, edit_response = True) + else: + robj.create_reply(body_text) + elif robj.reply_action == 'validate_email': + #todo: this is copy-paste - factor it out to askbot.mail.messages + data = { + 'site_name': askbot_settings.APP_SHORT_NAME, + 'site_url': askbot_settings.APP_URL, + 'ask_address': 'ask@' + askbot_settings.REPLY_BY_EMAIL_HOSTNAME + } + template = get_template('email/re_welcome_lamson_on.html') + + mail.send_mail( + subject_line = _('Re: %s') % subject_line, + body_text = template.render(Context(data)), + recipient_list = [from_address,] + ) diff --git a/askbot/mail/messages.py b/askbot/mail/messages.py new file mode 100644 index 00000000..24091971 --- /dev/null +++ b/askbot/mail/messages.py @@ -0,0 +1,52 @@ +"""functions in this module return body text +of email messages for various occasions +""" +import functools +from django.template import Context +from askbot.conf import settings as askbot_settings +from askbot.skins.loaders import get_template +from askbot.utils import html as html_utils + +def message(template = None): + """a decorator that creates a function + which returns formatted message using the + template and data""" + def decorate(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + template_object = get_template(template) + data = func(*args, **kwargs) + return template_object.render(Context(data)) + return wrapped + return decorate + +@message(template = 'email/ask_for_signature.html') +def ask_for_signature(user, footer_code = None): + """tells that we don't have user's signature + and because of that he/she cannot make posts + the message will ask to make a simple response + """ + return { + 'username': user.username, + 'site_name': askbot_settings.APP_SHORT_NAME, + 'footer_code': footer_code + } + +@message(template = 'email/insufficient_rep_to_post_by_email.html') +def insufficient_reputation(user): + """tells user that he does not have + enough rep and suggests to ask on the web + """ + min_rep = askbot_settings.MIN_REP_TO_POST_BY_EMAIL + min_upvotes = 1 + \ + (min_rep/askbot_settings.REP_GAIN_FOR_RECEIVING_UPVOTE) + site_link = html_utils.site_link( + 'ask', + askbot_settings.APP_SHORT_NAME + ) + return { + 'username': user.username, + 'site_name': askbot_settings.APP_SHORT_NAME, + 'site_link': site_link, + 'min_upvotes': min_upvotes + } diff --git a/askbot/management/commands/askbot_add_test_content.py b/askbot/management/commands/askbot_add_test_content.py index 6b14df59..ace629d0 100644 --- a/askbot/management/commands/askbot_add_test_content.py +++ b/askbot/management/commands/askbot_add_test_content.py @@ -14,21 +14,23 @@ NUM_COMMENTS = 20 # karma. This can be calculated dynamically - max of MIN_REP_TO_... settings INITIAL_REPUTATION = 500 +BAD_STUFF = "<script>alert('hohoho')</script>" + # Defining template inputs. -USERNAME_TEMPLATE = "test_user_%s" +USERNAME_TEMPLATE = BAD_STUFF + "test_user_%s" PASSWORD_TEMPLATE = "test_password_%s" EMAIL_TEMPLATE = "test_user_%s@askbot.org" -TITLE_TEMPLATE = "Test question title No.%s" -TAGS_TEMPLATE = ["tag-%s-0", "tag-%s-1"] # len(TAGS_TEMPLATE) tags per question +TITLE_TEMPLATE = "Test question title No.%s" + BAD_STUFF +TAGS_TEMPLATE = [BAD_STUFF + "tag-%s-0", BAD_STUFF + "tag-%s-1"] # len(TAGS_TEMPLATE) tags per question -CONTENT_TEMPLATE = """Lorem lean startup ipsum product market fit customer +CONTENT_TEMPLATE = BAD_STUFF + """Lorem lean startup ipsum product market fit customer development acquihire technical cofounder. User engagement **A/B** testing *shrink* a market venture capital pitch.""" -ANSWER_TEMPLATE = """Accelerator photo sharing business school drop out ramen +ANSWER_TEMPLATE = BAD_STUFF + """Accelerator photo sharing business school drop out ramen hustle crush it revenue traction platforms.""" -COMMENT_TEMPLATE = """Main differentiators business model micro economics +COMMENT_TEMPLATE = BAD_STUFF + """Main differentiators business model micro economics marketplace equity augmented reality human computer""" diff --git a/askbot/management/commands/fix_answer_counts.py b/askbot/management/commands/fix_answer_counts.py index 959e37b6..9f22422e 100644 --- a/askbot/management/commands/fix_answer_counts.py +++ b/askbot/management/commands/fix_answer_counts.py @@ -23,6 +23,5 @@ class Command(NoArgsCommand): """function that handles the command job """ self.remove_save_signals() - questions = models.Question.objects.all() - for question in questions: - question.thread.update_answer_count() + for thread in models.Thread.objects.all(): + thread.update_answer_count() diff --git a/askbot/management/commands/fix_question_tags.py b/askbot/management/commands/fix_question_tags.py index 9858e397..d575e651 100644 --- a/askbot/management/commands/fix_question_tags.py +++ b/askbot/management/commands/fix_question_tags.py @@ -39,33 +39,33 @@ class Command(NoArgsCommand): transaction.commit() #go through questions and fix tag records on each - questions = models.Question.objects.all() + threads = models.Thread.objects.all() checked_count = 0 found_count = 0 - total_count = questions.count() + total_count = threads.count() print "Searching for questions with inconsistent tag records:", - for question in questions: - tags = question.thread.tags.all() - denorm_tag_set = set(question.get_tag_names()) - norm_tag_set = set(question.thread.tags.values_list('name', flat=True)) + for thread in threads: + tags = thread.tags.all() + denorm_tag_set = set(thread.get_tag_names()) + norm_tag_set = set(thread.tags.values_list('name', flat=True)) if norm_tag_set != denorm_tag_set: - if question.last_edited_by: - user = question.last_edited_by - timestamp = question.last_edited_at + if thread.last_edited_by: + user = thread.last_edited_by + timestamp = thread.last_edited_at else: - user = question.author - timestamp = question.added_at + user = thread.author + timestamp = thread.added_at - tagnames = forms.TagNamesField().clean(question.tagnames) + tagnames = forms.TagNamesField().clean(thread.tagnames) - question.thread.update_tags( + thread.update_tags( tagnames = tagnames, user = user, timestamp = timestamp ) - question.thread.tagnames = tagnames - question.thread.save() + thread.tagnames = tagnames + thread.save() found_count += 1 transaction.commit() diff --git a/askbot/management/commands/fix_revisionless_posts.py b/askbot/management/commands/fix_revisionless_posts.py index 92c03425..9535bef3 100644 --- a/askbot/management/commands/fix_revisionless_posts.py +++ b/askbot/management/commands/fix_revisionless_posts.py @@ -7,11 +7,11 @@ from django.db.models import signals, Count from askbot import models from askbot import const -def fix_revisionless_posts(post_class, post_name): +def fix_revisionless_posts(post_class): posts = post_class.objects.annotate( rev_count = Count('revisions') ).filter(rev_count = 0) - print 'have %d corrupted %ss' % (len(posts), post_name) + print 'have %d corrupted posts' % len(posts) for post in posts: post.add_revision( author = post.author, @@ -39,5 +39,4 @@ class Command(NoArgsCommand): """function that handles the command job """ self.remove_save_signals() - fix_revisionless_posts(models.Question, 'question') - fix_revisionless_posts(models.Answer, 'answer') + fix_revisionless_posts(models.Post) diff --git a/askbot/management/commands/post_emailed_questions.py b/askbot/management/commands/post_emailed_questions.py index cc77b57f..acddcf43 100644 --- a/askbot/management/commands/post_emailed_questions.py +++ b/askbot/management/commands/post_emailed_questions.py @@ -22,7 +22,7 @@ import base64 from django.conf import settings as django_settings from django.core.management.base import NoArgsCommand, CommandError from askbot.conf import settings as askbot_settings -from askbot.utils import mail +from askbot import mail class CannotParseEmail(Exception): """This exception will bounce the email""" diff --git a/askbot/management/commands/send_accept_answer_reminders.py b/askbot/management/commands/send_accept_answer_reminders.py index 47358a99..3a20ba27 100644 --- a/askbot/management/commands/send_accept_answer_reminders.py +++ b/askbot/management/commands/send_accept_answer_reminders.py @@ -6,7 +6,7 @@ from askbot import const from askbot.conf import settings as askbot_settings from django.utils.translation import ugettext as _ from django.utils.translation import ungettext -from askbot.utils import mail +from askbot import mail from askbot.utils.classes import ReminderSchedule DEBUG_THIS_COMMAND = False diff --git a/askbot/management/commands/send_email.py b/askbot/management/commands/send_email.py index 2493c51b..ff36b19f 100644 --- a/askbot/management/commands/send_email.py +++ b/askbot/management/commands/send_email.py @@ -1,4 +1,4 @@ -from askbot.utils.mail import send_mail +from askbot.mail import send_mail from django.core.exceptions import ValidationError from django.core.management.base import BaseCommand, CommandError from django.core.validators import validate_email diff --git a/askbot/management/commands/send_email_alerts.py b/askbot/management/commands/send_email_alerts.py index b7624e21..e890452d 100644 --- a/askbot/management/commands/send_email_alerts.py +++ b/askbot/management/commands/send_email_alerts.py @@ -12,7 +12,7 @@ from askbot.conf import settings as askbot_settings from django.utils.datastructures import SortedDict from django.contrib.contenttypes.models import ContentType from askbot import const -from askbot.utils import mail +from askbot import mail from askbot.utils.slug import slugify DEBUG_THIS_COMMAND = False diff --git a/askbot/management/commands/send_respondable_welcome_email.py b/askbot/management/commands/send_respondable_welcome_email.py new file mode 100644 index 00000000..c386b531 --- /dev/null +++ b/askbot/management/commands/send_respondable_welcome_email.py @@ -0,0 +1,12 @@ +"""Management command that sends a respondable +welcome email to all users. +User's responses will be used to validate +the email addresses and extract the email signatures. +""" +from django.core.management.base import NoArgsCommand +from askbot.models import User, send_welcome_email + +class Command(NoArgsCommand): + def handle_noargs(self): + for user in User.objects.filter(email_isvalid = False): + send_welcome_email(user) diff --git a/askbot/management/commands/send_unanswered_question_reminders.py b/askbot/management/commands/send_unanswered_question_reminders.py index 424e45cc..82b6ecd8 100644 --- a/askbot/management/commands/send_unanswered_question_reminders.py +++ b/askbot/management/commands/send_unanswered_question_reminders.py @@ -3,7 +3,7 @@ from askbot import models from askbot import const from askbot.conf import settings as askbot_settings from django.utils.translation import ungettext -from askbot.utils import mail +from askbot import mail from askbot.utils.classes import ReminderSchedule from askbot.models.question import Thread diff --git a/askbot/migrations/0123_setup_postgres_user_search.py b/askbot/migrations/0123_setup_postgres_user_search.py new file mode 100644 index 00000000..152fbde4 --- /dev/null +++ b/askbot/migrations/0123_setup_postgres_user_search.py @@ -0,0 +1,29 @@ +# encoding: utf-8 +import askbot +from askbot.search import postgresql +import os +from south.v2 import DataMigration + +class Migration(DataMigration): + """this migration is the same as 22 and 106 + just ran again to update the postgres search setup + """ + + def forwards(self, orm): + "Write your forwards methods here." + + db_engine_name = askbot.get_database_engine_name() + if 'postgresql_psycopg2' in db_engine_name: + script_path = os.path.join( + askbot.get_install_directory(), + 'search', + 'postgresql', + 'user_profile_search_051312.plsql' + ) + postgresql.setup_full_text_search(script_path) + + def backwards(self, orm): + "Write your backwards methods here." + pass + + models = {}#we don't need orm for this migration diff --git a/askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py b/askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py new file mode 100644 index 00000000..ecb66552 --- /dev/null +++ b/askbot/migrations/0124_auto__add_field_post_is_private__add_field_replyaddress_reply_action.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Post.is_private' + db.add_column('askbot_post', 'is_private', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + # Adding field 'ReplyAddress.reply_action' + db.add_column('askbot_replyaddress', 'reply_action', + self.gf('django.db.models.fields.CharField')(default='auto_answer_or_comment', max_length=32), + keep_default=False) + + # Changing field 'ReplyAddress.post' + db.alter_column('askbot_replyaddress', 'post_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['askbot.Post'])) + + try: + # Adding field 'User.interesting_tags' + db.add_column(u'auth_user', 'email_signature', self.gf('django.db.models.fields.TextField')(blank=True, default = ''), keep_default=False) + except: + pass + + def backwards(self, orm): + db.delete_column('askbot_post', 'is_private') + db.delete_column('askbot_replyaddress', 'reply_action') + db.delete_column('auth_user', 'email_signature') + db.alter_column('askbot_replyaddress', 'post_id', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['askbot.Post'])) + + models = { + 'askbot.activity': { + 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"}, + 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True'}), + 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'symmetrical': 'False', 'through': "orm['askbot.ActivityAuditStatus']", 'to': "orm['auth.User']"}), + 'summary': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.activityauditstatus': { + 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'}, + 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.anonymousanswer': { + 'Meta': {'object_name': 'AnonymousAnswer'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Post']"}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.anonymousquestion': { + 'Meta': {'object_name': 'AnonymousQuestion'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.award': { + 'Meta': {'object_name': 'Award', 'db_table': "u'award'"}, + 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"}) + }, + 'askbot.badgedata': { + 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'}, + 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"}) + }, + 'askbot.favoritequestion': { + 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + 'askbot.groupmembership': { + 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupMembership'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_memberships'", 'to': "orm['askbot.Tag']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'group_memberships'", 'to': "orm['auth.User']"}) + }, + 'askbot.groupprofile': { + 'Meta': {'object_name': 'GroupProfile'}, + 'group_tag': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group_profile'", 'unique': 'True', 'to': "orm['askbot.Tag']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_open': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), + 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'preapproved_email_domains': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'preapproved_emails': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}) + }, + 'askbot.markedtag': { + 'Meta': {'object_name': 'MarkedTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"}) + }, + 'askbot.post': { + 'Meta': {'object_name': 'Post'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'posts'", 'null': 'True', 'blank': 'True', 'to': "orm['askbot.Thread']"}), + 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'askbot.postflagreason': { + 'Meta': {'object_name': 'PostFlagReason'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'details': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_reject_reasons'", 'to': "orm['askbot.Post']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'askbot.postrevision': { + 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'approved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'approved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}), + 'by_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'revision_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'}) + }, + 'askbot.questionview': { + 'Meta': {'object_name': 'QuestionView'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"}) + }, + 'askbot.replyaddress': { + 'Meta': {'object_name': 'ReplyAddress'}, + 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}), + 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'to': "orm['askbot.Post']"}), + 'reply_action': ('django.db.models.fields.CharField', [], {'default': "'auto_answer_or_comment'", 'max_length': '32'}), + 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.repute': { + 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.tag': { + 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'tag_wiki': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'described_tag'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.thread': { + 'Meta': {'object_name': 'Thread'}, + 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}), + 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}), + 'vote': ('django.db.models.fields.SmallIntegerField', [], {}), + 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/migrations/0125_add_show_tags_field_to_user.py b/askbot/migrations/0125_add_show_tags_field_to_user.py new file mode 100644 index 00000000..cb7ab2bd --- /dev/null +++ b/askbot/migrations/0125_add_show_tags_field_to_user.py @@ -0,0 +1,296 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model show_marked_tags fields to the model auth_user + try: + db.add_column( + u'auth_user', + 'show_marked_tags', + self.gf( + 'django.db.models.fields.BooleanField' + )(default=True, blank=True) + ) + except: + pass + + + def backwards(self, orm): + + # Deleting show_marked_tags fields + db.delete_column(u'auth_user', 'show_marked_tags') + + models = { + 'askbot.activity': { + 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"}, + 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True'}), + 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'symmetrical': 'False', 'through': "orm['askbot.ActivityAuditStatus']", 'to': "orm['auth.User']"}), + 'summary': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.activityauditstatus': { + 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'}, + 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.anonymousanswer': { + 'Meta': {'object_name': 'AnonymousAnswer'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Post']"}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.anonymousquestion': { + 'Meta': {'object_name': 'AnonymousQuestion'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.award': { + 'Meta': {'object_name': 'Award', 'db_table': "u'award'"}, + 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"}) + }, + 'askbot.badgedata': { + 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'}, + 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"}) + }, + 'askbot.favoritequestion': { + 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + 'askbot.markedtag': { + 'Meta': {'object_name': 'MarkedTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"}) + }, + 'askbot.post': { + 'Meta': {'object_name': 'Post'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['askbot.Thread']"}), + 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'askbot.postrevision': { + 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'revision_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'}) + }, + 'askbot.questionview': { + 'Meta': {'object_name': 'QuestionView'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"}) + }, + 'askbot.replyaddress': { + 'Meta': {'object_name': 'ReplyAddress'}, + 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}), + 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'to': "orm['askbot.Post']"}), + 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.repute': { + 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.tag': { + 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.thread': { + 'Meta': {'object_name': 'Thread'}, + 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}), + 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}), + 'score': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}), + 'vote': ('django.db.models.fields.SmallIntegerField', [], {}), + 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 27bc98c2..c4c11cb4 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -1,9 +1,10 @@ from askbot import startup_procedures startup_procedures.run() -import logging -import hashlib +import collections import datetime +import hashlib +import logging import urllib from django.core.urlresolvers import reverse, NoReverseMatch from django.db.models import signals as django_signals @@ -12,6 +13,7 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.contrib.auth.models import User from django.utils.safestring import mark_safe +from django.utils.html import escape from django.db import models from django.conf import settings as django_settings from django.contrib.contenttypes.models import ContentType @@ -37,9 +39,10 @@ from askbot.models.repute import Award, Repute, Vote from askbot import auth from askbot.utils.decorators import auto_now_timestamp from askbot.utils.slug import slugify +from askbot.utils.html import sanitize_html from askbot.utils.diff import textDiff as htmldiff from askbot.utils.url_utils import strip_path -from askbot.utils import mail +from askbot import mail def get_model(model_name): """a shortcut for getting model for an askbot app""" @@ -52,6 +55,26 @@ def get_admins_and_moderators(): models.Q(is_superuser=True) | models.Q(status='m') ) +def get_users_by_text_query(search_query): + """Runs text search in user names and profile. + For postgres, search also runs against user group names. + """ + import askbot + if 'postgresql_psycopg2' in askbot.get_database_engine_name(): + from askbot.search import postgresql + return postgresql.run_full_text_search(User.objects.all(), search_query) + else: + return User.objects.filter( + models.Q(username__icontains=search_query) | + models.Q(about__icontains=search_query) + ) + #if askbot.get_database_engine_name().endswith('mysql') \ + # and mysql.supports_full_text_search(): + # return User.objects.filter( + # models.Q(username__search = search_query) | + # models.Q(about__search = search_query) + # ) + User.add_to_class( 'status', models.CharField( @@ -61,7 +84,7 @@ User.add_to_class( ) ) -User.add_to_class('email_isvalid', models.BooleanField(default=False)) +User.add_to_class('email_isvalid', models.BooleanField(default=False)) #@UndefinedVariable User.add_to_class('email_key', models.CharField(max_length=32, null=True)) #hardcoded initial reputaion of 1, no setting for this one User.add_to_class('reputation', @@ -100,6 +123,9 @@ User.add_to_class('about', models.TextField(blank=True)) User.add_to_class('interesting_tags', models.TextField(blank = True)) User.add_to_class('ignored_tags', models.TextField(blank = True)) User.add_to_class('subscribed_tags', models.TextField(blank = True)) +User.add_to_class('email_signature', models.TextField(blank = True)) +User.add_to_class('show_marked_tags', models.BooleanField(default = True)) + User.add_to_class( 'email_tag_filter_strategy', models.SmallIntegerField( @@ -183,6 +209,16 @@ def user_update_avatar_type(self): self.avatar_type = _check_gravatar(self.gravatar) self.save() +def user_strip_email_signature(self, text): + """strips email signature from the end of the text""" + if self.email_signature.strip() == '': + return text + + text = '\n'.join(text.splitlines())#normalize the line endings + while text.endswith(self.email_signature): + text = text[0:-len(self.email_signature)] + return text + def _check_gravatar(gravatar): gravatar_url = "http://www.gravatar.com/avatar/%s?d=404" % gravatar code = urllib.urlopen(gravatar_url).getcode() @@ -205,6 +241,42 @@ def user_get_old_vote_for_post(self, post): except Vote.MultipleObjectsReturned: raise AssertionError +def user_get_marked_tags(self, reason): + """reason is a type of mark: good, bad or subscribed""" + assert(reason in ('good', 'bad', 'subscribed')) + if reason == 'subscribed': + if askbot_settings.SUBSCRIBED_TAG_SELECTOR_ENABLED == False: + return Tag.objects.none() + + return Tag.objects.filter( + user_selections__user = self, + user_selections__reason = reason + ) + +MARKED_TAG_PROPERTY_MAP = { + 'good': 'interesting_tags', + 'bad': 'ignored_tags', + 'subscribed': 'subscribed_tags' +} +def user_get_marked_tag_names(self, reason): + """returns list of marked tag names for a give + reason: good, bad, or subscribed + will add wildcard tags as well, if used + """ + if reason == 'subscribed': + if askbot_settings.SUBSCRIBED_TAG_SELECTOR_ENABLED == False: + return list() + + tags = self.get_marked_tags(reason) + tag_names = list(tags.values_list('name', flat = True)) + + if askbot_settings.USE_WILDCARD_TAGS: + attr_name = MARKED_TAG_PROPERTY_MAP[reason] + wildcard_tags = getattr(self, attr_name).split() + tag_names.extend(wildcard_tags) + + return tag_names + def user_has_affinity_to_question(self, question = None, affinity_type = None): """returns True if number of tag overlap of the user tag selection with the question is 0 and False otherwise @@ -265,7 +337,7 @@ def user_can_have_strong_url(self): followed by the search engine crawlers""" return (self.reputation >= askbot_settings.MIN_REP_TO_HAVE_STRONG_URL) -def user_can_reply_by_email(self): +def user_can_post_by_email(self): """True, if reply by email is enabled and user has sufficient reputatiton""" return askbot_settings.REPLY_BY_EMAIL and \ @@ -1220,6 +1292,11 @@ def user_delete_comment( timestamp = None ): self.assert_can_delete_comment(comment = comment) + #todo: we want to do this + #comment.deleted = True + #comment.deleted_by = self + #comment.deleted_at = timestamp + #comment.save() comment.delete() comment.thread.invalidate_cached_data() @@ -1471,18 +1548,18 @@ def user_edit_post(self, @auto_now_timestamp def user_edit_question( - self, - question = None, - title = None, - body_text = None, - revision_comment = None, - tags = None, - wiki = False, - edit_anonymously = False, - timestamp = None, - force = False,#if True - bypass the assert - by_email = False - ): + self, + question = None, + title = None, + body_text = None, + revision_comment = None, + tags = None, + wiki = False, + edit_anonymously = False, + timestamp = None, + force = False,#if True - bypass the assert + by_email = False + ): if force == False: self.assert_can_edit_question(question) @@ -2019,10 +2096,39 @@ def user_get_absolute_url(self): def get_profile_link(self): profile_link = u'<a href="%s">%s</a>' \ - % (self.get_profile_url(),self.username) + % (self.get_profile_url(), escape(self.username)) return mark_safe(profile_link) +def user_get_groups_membership_info(self, groups): + """returts a defaultdict with values that are + dictionaries with the following keys and values: + * key: can_join, value: True if user can join group + * key: is_member, value: True if user is member of group + + ``groups`` is a group tag query set + """ + groups = groups.select_related('group_profile') + + group_ids = groups.values_list('id', flat = True) + memberships = GroupMembership.objects.filter( + user__id = self.id, + group__id__in = group_ids + ) + + info = collections.defaultdict( + lambda: {'can_join': False, 'is_member': False} + ) + for membership in memberships: + info[membership.group_id]['is_member'] = True + + for group in groups: + info[group.id]['can_join'] = group.group_profile.can_accept_user(self) + + return info + + + def user_get_karma_summary(self): """returns human readable sentence about status of user's karma""" @@ -2233,12 +2339,18 @@ def user_approve_post_revision(user, post_revision, timestamp = None): post = post_revision.post post.approved = True post.save() + if post_revision.post.post_type == 'question': thread = post.thread thread.approved = True thread.save() post.thread.invalidate_cached_data() + #send the signal of published revision + signals.post_revision_published.send( + None, revision = post_revision, was_approved = True + ) + @auto_now_timestamp def flag_post(user, post, timestamp=None, cancel=False, cancel_all = False, force = False): if cancel_all: @@ -2404,6 +2516,10 @@ User.add_to_class('get_absolute_url', user_get_absolute_url) User.add_to_class('get_avatar_url', user_get_avatar_url) User.add_to_class('get_default_avatar_url', user_get_default_avatar_url) User.add_to_class('get_gravatar_url', user_get_gravatar_url) +User.add_to_class('get_marked_tags', user_get_marked_tags) +User.add_to_class('get_marked_tag_names', user_get_marked_tag_names) +User.add_to_class('strip_email_signature', user_strip_email_signature) +User.add_to_class('get_groups_membership_info', user_get_groups_membership_info) User.add_to_class('get_anonymous_name', user_get_anonymous_name) User.add_to_class('update_avatar_type', user_update_avatar_type) User.add_to_class('post_question', user_post_question) @@ -2445,7 +2561,7 @@ User.add_to_class('is_following_question', user_is_following_question) User.add_to_class('mark_tags', user_mark_tags) User.add_to_class('update_response_counts', user_update_response_counts) User.add_to_class('can_have_strong_url', user_can_have_strong_url) -User.add_to_class('can_reply_by_email', user_can_reply_by_email) +User.add_to_class('can_post_by_email', user_can_post_by_email) User.add_to_class('can_post_comment', user_can_post_comment) User.add_to_class('is_administrator', user_is_administrator) User.add_to_class('is_administrator_or_moderator', user_is_administrator_or_moderator) @@ -2517,11 +2633,13 @@ User.add_to_class( user_assert_can_approve_post_revision ) -#todo: move this to askbot/utils ?? +#todo: move this to askbot/mail ? def format_instant_notification_email( to_user = None, from_user = None, post = None, + reply_address = None, + alt_reply_address = None, update_type = None, template = None, ): @@ -2568,16 +2686,16 @@ def format_instant_notification_email( revisions = post.revisions.all()[:2] assert(len(revisions) == 2) content_preview = htmldiff( - revisions[1].html, - revisions[0].html, - ins_start = '<b><u style="background-color:#cfc">', - ins_end = '</u></b>', - del_start = '<del style="color:#600;background-color:#fcc">', - del_end = '</del>' - ) + sanitize_html(revisions[1].html), + sanitize_html(revisions[0].html), + ins_start = '<b><u style="background-color:#cfc">', + ins_end = '</u></b>', + del_start = '<del style="color:#600;background-color:#fcc">', + del_end = '</del>' + ) #todo: remove hardcoded style else: - content_preview = post.format_for_email() + content_preview = post.format_for_email(is_leaf_post = True) #add indented summaries for the parent posts content_preview += post.format_for_email_as_parent_thread_summary() @@ -2611,13 +2729,21 @@ def format_instant_notification_email( 'post_link': '<a href="%s">%s</a>' % (post_url, _(post.post_type)) } - can_reply = to_user.can_reply_by_email() + can_reply = to_user.can_post_by_email() if can_reply: - reply_separator = const.REPLY_SEPARATOR_TEMPLATE % { - 'user_action': user_action, - 'instruction': _('To reply, PLEASE WRITE ABOVE THIS LINE.') - } + reply_separator = const.SIMPLE_REPLY_SEPARATOR_TEMPLATE % \ + _('To reply, PLEASE WRITE ABOVE THIS LINE.') + if post.post_type == 'question' and alt_reply_address: + data = { + 'addr': alt_reply_address, + 'subject': urllib.quote( + ('Re: ' + post.thread.title).encode('utf-8') + ) + } + reply_separator += '<p>' + \ + const.REPLY_WITH_COMMENT_TEMPLATE % data + reply_separator += '</p>' else: reply_separator = user_action @@ -2635,7 +2761,56 @@ def format_instant_notification_email( 'reply_separator': reply_separator } subject_line = _('"%(title)s"') % {'title': origin_post.thread.title} - return subject_line, template.render(Context(update_data)) + + content = template.render(Context(update_data)) + if can_reply: + content += '<p style="font-size:8px;color:#aaa">' + \ + reply_address + '</p>' + + return subject_line, content + +def get_reply_to_addresses(user, post): + """Returns one or two email addresses that can be + used by a given `user` to reply to the `post` + the first address - always a real email address, + the second address is not ``None`` only for "question" posts. + + When the user is notified of a new question - + i.e. `post` is a "quesiton", he/she + will need to choose - whether to give a question or a comment, + thus we return the second address - for the comment reply. + + When the post is a "question", the first email address + is for posting an "answer", and when post is either + "comment" or "answer", the address will be for posting + a "comment". + """ + #these variables will contain return values + primary_addr = django_settings.DEFAULT_FROM_EMAIL + secondary_addr = None + if user.can_post_by_email(): + if user.reputation >= askbot_settings.MIN_REP_TO_POST_BY_EMAIL: + + reply_args = { + 'post': post, + 'user': user, + 'reply_action': 'post_comment' + } + if post.post_type in ('answer', 'comment'): + reply_args['reply_action'] = 'post_comment' + elif post.post_type == 'question': + reply_args['reply_action'] = 'post_answer' + + primary_addr = ReplyAddress.objects.create_new( + **reply_args + ).as_email_address() + + if post.post_type == 'question': + reply_args['reply_action'] = 'post_comment' + secondary_addr = ReplyAddress.objects.create_new( + **reply_args + ).as_email_address() + return primary_addr, secondary_addr #todo: action def send_instant_notifications_about_activity_in_post( @@ -2659,33 +2834,31 @@ def send_instant_notifications_about_activity_in_post( if update_activity.activity_type not in acceptable_types: return + #calculate some variables used in the loop below from askbot.skins.loaders import get_template update_type_map = const.RESPONSE_ACTIVITY_TYPE_MAP_FOR_TEMPLATES update_type = update_type_map[update_activity.activity_type] - origin_post = post.get_origin_post() + headers = mail.thread_headers( + post, + origin_post, + update_activity.activity_type + ) + #send email for all recipients for user in recipients: - + reply_address, alt_reply_address = get_reply_to_addresses(user, post) + subject_line, body_text = format_instant_notification_email( to_user = user, from_user = update_activity.user, post = post, + reply_address = reply_address, + alt_reply_address = alt_reply_address, update_type = update_type, template = get_template('instant_notification.html') ) - #todo: this could be packaged as an "action" - a bundle - #of executive function with the activity log recording - #TODO check user reputation - headers = mail.thread_headers(post, origin_post, update_activity.activity_type) - if askbot_settings.REPLY_BY_EMAIL: - reply_address = "noreply" - if user.reputation >= askbot_settings.MIN_REP_TO_POST_BY_EMAIL: - reply_address = ReplyAddress.objects.create_new(post, user).address - reply_to = 'reply-%s@%s' % (reply_address, askbot_settings.REPLY_BY_EMAIL_HOSTNAME) - headers.update({'Reply-To': reply_to}) - else: - reply_to = django_settings.DEFAULT_FROM_EMAIL + headers['Reply-To'] = reply_address mail.send_mail( subject_line = subject_line, body_text = body_text, @@ -2695,6 +2868,18 @@ def send_instant_notifications_about_activity_in_post( headers = headers ) +def notify_author_of_published_revision( + revision = None, was_approved = None, **kwargs +): + """notifies author about approved post revision, + assumes that we have the very first revision + """ + #only email about first revision + if revision.should_notify_author_about_publishing(was_approved): + from askbot.tasks import notify_author_of_published_revision_celery_task + notify_author_of_published_revision_celery_task.delay(revision) + + #todo: move to utils def calculate_gravatar_hash(instance, **kwargs): """Calculates a User's gravatar hash from their email address.""" @@ -2740,14 +2925,6 @@ def record_post_update_activity( created = created, diff = diff, ) - #non-celery version - #tasks.record_post_update( - # post = post, - # newly_mentioned_users = newly_mentioned_users, - # updated_by = updated_by, - # timestamp = timestamp, - # created = created, - #) def record_award_event(instance, created, **kwargs): @@ -2980,6 +3157,57 @@ def record_user_full_updated(instance, **kwargs): ) activity.save() +def send_respondable_email_validation_message( + user = None, subject_line = None, data = None, template_name = None +): + """sends email validation message to the user + + We validate email by getting user's reply + to the validation message by email, which also gives + an opportunity to extract user's email signature. + """ + reply_address = ReplyAddress.objects.create_new( + user = user, + reply_action = 'validate_email' + ) + data['email_code'] = reply_address.address + + from askbot.skins.loaders import get_template + template = get_template(template_name) + body_text = template.render(Context(data)) + + reply_to_address = 'welcome-%s@%s' % ( + reply_address.address, + askbot_settings.REPLY_BY_EMAIL_HOSTNAME + ) + + mail.send_mail( + subject_line = subject_line, + body_text = body_text, + recipient_list = [user.email, ], + activity_type = const.TYPE_ACTIVITY_VALIDATION_EMAIL_SENT, + headers = {'Reply-To': reply_to_address} + ) + + +def send_welcome_email(user, **kwargs): + """sends welcome email to the newly created user + + todo: second branch should send email with a simple + clickable link. + """ + if askbot_settings.REPLY_BY_EMAIL:#with this on we also collect signature + data = { + 'site_name': askbot_settings.APP_SHORT_NAME + } + send_respondable_email_validation_message( + user = user, + subject_line = _('Welcome to %(site_name)s') % data, + data = data, + template_name = 'email/welcome_lamson_on.html' + ) + + def complete_pending_tag_subscriptions(sender, request, *args, **kwargs): """save pending tag subscriptions saved in the session""" if 'subscribe_for_tags' in request.session: @@ -3041,22 +3269,10 @@ def make_admin_if_first_user(instance, **kwargs): instance.set_admin_status() cache.cache.set('admin-created', True) -def place_post_revision_on_moderation_queue(instance, **kwargs): - """`instance` is post revision, because we must - be able to moderate all the revisions, if necessary, - in order to avoid people getting the post past the moderation - then make some evil edit. - """ - if instance.needs_moderation(): - instance.place_on_moderation_queue() #signal for User model save changes django_signals.pre_save.connect(make_admin_if_first_user, sender=User) django_signals.pre_save.connect(calculate_gravatar_hash, sender=User) -django_signals.post_save.connect( - place_post_revision_on_moderation_queue, - sender=PostRevision -) django_signals.post_save.connect(add_missing_subscriptions, sender=User) django_signals.post_save.connect(record_award_event, sender=Award) django_signals.post_save.connect(notify_award_message, sender=Award) @@ -3076,10 +3292,15 @@ signals.delete_question_or_answer.connect(record_delete_question, sender=Post) signals.flag_offensive.connect(record_flag_offensive, sender=Post) signals.remove_flag_offensive.connect(remove_flag_offensive, sender=Post) signals.tags_updated.connect(record_update_tags) +signals.user_registered.connect(send_welcome_email) signals.user_updated.connect(record_user_full_updated, sender=User) signals.user_logged_in.connect(complete_pending_tag_subscriptions)#todo: add this to fake onlogin middleware signals.user_logged_in.connect(post_anonymous_askbot_content) signals.post_updated.connect(record_post_update_activity) + +#probably we cannot use post-save here the point of this is +#to tell when the revision becomes publicly visible, not when it is saved +signals.post_revision_published.connect(notify_author_of_published_revision) signals.site_visited.connect(record_user_visit) #set up a possibility for the users to follow others diff --git a/askbot/models/post.py b/askbot/models/post.py index a0fdc4ff..c08b940b 100644 --- a/askbot/models/post.py +++ b/askbot/models/post.py @@ -332,6 +332,11 @@ class Post(models.Model): #the reason is that the title and tags belong to thread, #but the question body to Post is_anonymous = models.BooleanField(default=False) + #When is_private == True + #the post is visible only to the some privileged users. + #The privilege may be defined through groups to which + #the thread belongs or in some other way. + is_private = models.BooleanField(default=False) objects = PostManager() @@ -340,7 +345,7 @@ class Post(models.Model): db_table = 'askbot_post' - def parse_post_text(post): + def parse_post_text(self): """typically post has a field to store raw source text in comment it is called .comment, in Question and Answer it is called .text @@ -356,18 +361,18 @@ class Post(models.Model): removed_mentions - list of mention <Activity> objects - for removed ones """ - if post.post_type in ('question', 'answer', 'tag_wiki', 'reject_reason'): + if self.post_type in ('question', 'answer', 'tag_wiki', 'reject_reason'): _urlize = False _use_markdown = True _escape_html = False #markdow does the escaping - elif post.is_comment(): + elif self.is_comment(): _urlize = True _use_markdown = True _escape_html = True else: raise NotImplementedError - text = post.text + text = self.text if _escape_html: text = cgi.escape(text) @@ -379,12 +384,12 @@ class Post(models.Model): text = sanitize_html(markup.get_parser().convert(text)) #todo, add markdown parser call conditional on - #post.use_markdown flag + #self.use_markdown flag post_html = text mentioned_authors = list() removed_mentions = list() if '@' in text: - op = post.get_origin_post() + op = self.get_origin_post() anticipated_authors = op.get_author_list( include_comments = True, recursive = True @@ -394,9 +399,8 @@ class Post(models.Model): extra_authors = set() for name_seed in extra_name_seeds: - extra_authors.update(User.objects.filter( - username__istartswith = name_seed - ) + extra_authors.update( + User.objects.filter(username__istartswith = name_seed) ) #it is important to preserve order here so that authors of post @@ -411,10 +415,10 @@ class Post(models.Model): #find mentions that were removed and identify any previously #entered mentions so that we can send alerts on only new ones from askbot.models.user import Activity - if post.pk is not None: + if self.pk is not None: #only look for previous mentions if post was already saved before prev_mention_qs = Activity.objects.get_mentions( - mentioned_in = post + mentioned_in = self ) new_set = set(mentioned_authors) for prev_mention in prev_mention_qs: @@ -437,51 +441,54 @@ class Post(models.Model): return data #todo: when models are merged, it would be great to remove author parameter - def parse_and_save_post(post, author = None, **kwargs): + def parse_and_save(self, author = None, **kwargs): """generic method to use with posts to be used prior to saving post edit or addition """ assert(author is not None) - last_revision = post.html - data = post.parse() + last_revision = self.html + data = self.parse_post_text() - post.html = data['html'] + self.html = data['html'] newly_mentioned_users = set(data['newly_mentioned_users']) - set([author]) removed_mentions = data['removed_mentions'] #a hack allowing to save denormalized .summary field for questions - if hasattr(post, 'summary'): - post.summary = post.get_snippet() + if hasattr(self, 'summary'): + self.summary = self.get_snippet() #delete removed mentions for rm in removed_mentions: rm.delete() - created = post.pk is None + created = self.pk is None #this save must precede saving the mention activity #because generic relation needs primary key of the related object - super(post.__class__, post).save(**kwargs) + super(self.__class__, self).save(**kwargs) if last_revision: - diff = htmldiff(last_revision, post.html) + diff = htmldiff( + sanitize_html(last_revision), + sanitize_html(self.html) + ) else: - diff = post.get_snippet() + diff = sanitize_html(self.get_snippet()) - timestamp = post.get_time_of_last_edit() + timestamp = self.get_time_of_last_edit() #todo: this is handled in signal because models for posts #are too spread out from askbot.models import signals signals.post_updated.send( - post = post, + post = self, updated_by = author, newly_mentioned_users = newly_mentioned_users, timestamp = timestamp, created = created, diff = diff, - sender = post.__class__ + sender = self.__class__ ) try: @@ -491,13 +498,6 @@ class Post(models.Model): except Exception: logging.debug('cannot ping google - did you register with them?') - ###################################### - # TODO: Rename the methods above instead of doing this assignment - parse = parse_post_text - parse_and_save = parse_and_save_post - ###################################### - - def is_question(self): return self.post_type == 'question' @@ -606,40 +606,26 @@ class Post(models.Model): """ return html_utils.strip_tags(self.html)[:max_length] + ' ...' - def format_tags_for_email(self): - """formats tags of the question post for email""" - tag_style = "white-space: nowrap; " \ - + "font-size: 11px; color: #333;" \ - + "background-color: #EEE;" \ - + "border-left: 3px solid #777;" \ - + "border-top: 1px solid #EEE;" \ - + "border-bottom: 1px solid #CCC;" \ - + "border-right: 1px solid #CCC;" \ - + "padding: 1px 8px 1px 8px;" \ - + "margin-right:3px;" - output = '<div>' - for tag_name in self.get_tag_names(): - output += '<span style="%s">%s</span>' % (tag_style, tag_name) - output += '</div>' - return output - - def format_for_email(self, quote_level = 0): + def format_for_email( + self, quote_level = 0, is_leaf_post = False, format = None + ): """format post for the output in email, if quote_level > 0, the post will be indented that number of times + todo: move to views? """ - from askbot.templatetags.extra_filters_jinja import absolutize_urls_func - output = '' - if self.post_type == 'question': - output += '<b>%s</b><br/>' % self.thread.title - - output += absolutize_urls_func(self.html) - if self.post_type == 'question':#add tags to the question - output += self.format_tags_for_email() - quote_style = 'padding-left:5px; border-left: 2px solid #aaa;' - while quote_level > 0: - quote_level = quote_level - 1 - output = '<div style="%s">%s</div>' % (quote_style, output) - return output + from askbot.templatetags.extra_filters_jinja \ + import absolutize_urls_func + from askbot.skins.loaders import get_template + from django.template import Context + template = get_template('email/quoted_post.html') + data = { + 'post': self, + 'quote_level': quote_level, + #'html': absolutize_urls_func(self.html), + 'is_leaf_post': is_leaf_post, + 'format': format + } + return template.render(Context(data)) def format_for_email_as_parent_thread_summary(self): """format for email as summary of parent posts @@ -652,47 +638,32 @@ class Post(models.Model): if parent_post is None: break quote_level += 1 + """ + output += '<p>' output += _( - 'In reply to %(user)s %(post)s of %(date)s<br/>' + 'In reply to %(user)s %(post)s of %(date)s' ) % { 'user': parent_post.author.username, 'post': _(parent_post.post_type), 'date': parent_post.added_at.strftime(const.DATETIME_FORMAT) } - output += parent_post.format_for_email(quote_level = quote_level) + output += '</p>' + """ + output += parent_post.format_for_email( + quote_level = quote_level, + format = 'parent_subthread' + ) current_post = parent_post return output - def format_for_email_as_metadata(self): - output = _( - 'Posted by %(user)s on %(date)s' - ) % { - 'user': self.author.username, - 'date': self.added_at.strftime(const.DATETIME_FORMAT) - } - return '<p>%s</p>' % output - def format_for_email_as_subthread(self): """outputs question or answer and all it's comments returns empty string for all other post types """ - if self.post_type in ('question', 'answer'): - output = self.format_for_email_as_metadata() - output += self.format_for_email() - comments = self.get_cached_comments() - if comments: - comments_heading = ungettext( - '%(count)d comment:', - '%(count)d comments:', - len(comments) - ) % {'count': len(comments)} - output += '<p>%s</p>' % comments_heading - for comment in comments: - output += comment.format_for_email_as_metadata() - output += comment.format_for_email(quote_level = 1) - return output - else: - return '' + from askbot.skins.loaders import get_template + from django.template import Context + template = get_template('email/post_as_subthread.html') + return template.render(Context({'post': self})) def set_cached_comments(self, comments): """caches comments in the lifetime of the object @@ -1050,10 +1021,7 @@ class Post(models.Model): return self.revisions.order_by('-revised_at')[0] def get_latest_revision_number(self): - if self.is_comment(): - return 1 - else: - return self.get_latest_revision().revision + return self.get_latest_revision().revision def get_time_of_last_edit(self): if self.is_comment(): @@ -1672,11 +1640,15 @@ class PostRevisionManager(models.Manager): def create_question_revision(self, *kargs, **kwargs): kwargs['revision_type'] = self.model.QUESTION_REVISION - return super(PostRevisionManager, self).create(*kargs, **kwargs) + revision = super(PostRevisionManager, self).create(*kargs, **kwargs) + revision.moderate_or_publish() + return revision def create_answer_revision(self, *kargs, **kwargs): kwargs['revision_type'] = self.model.ANSWER_REVISION - return super(PostRevisionManager, self).create(*kargs, **kwargs) + revision = super(PostRevisionManager, self).create(*kargs, **kwargs) + revision.moderate_or_publish() + return revision def question_revisions(self): return self.filter(revision_type=self.model.QUESTION_REVISION) @@ -1779,7 +1751,8 @@ class PostRevision(models.Model): self.post.thread.save() #above changes will hide post from the public display if self.by_email: - from askbot.utils.mail import send_mail + #todo: move this to the askbot.mail module + from askbot.mail import send_mail email_context = { 'site': askbot_settings.APP_SHORT_NAME } @@ -1819,6 +1792,33 @@ class PostRevision(models.Model): #todo: make this group-sensitive activity.add_recipients(get_admins_and_moderators()) + def moderate_or_publish(self): + """either place on moderation queue or announce + that this revision is published""" + if self.needs_moderation():#moderate + self.place_on_moderation_queue() + else:#auto-approve + from askbot.models import signals + signals.post_revision_published.send(None, revision = self) + + def should_notify_author_about_publishing(self, was_approved = False): + """True if author should get email about making own post""" + if self.by_email: + schedule = askbot_settings.SELF_NOTIFY_EMAILED_POST_AUTHOR_WHEN + if schedule == const.NEVER: + return False + elif schedule == const.FOR_FIRST_REVISION: + return self.revision == 1 + elif schedule == const.FOR_ANY_REVISION: + return True + else: + raise ValueError() + else: + #logic not implemented yet + #the ``was_approved`` argument will be used here + #schedule = askbot_settings.SELF_NOTIFY_WEB_POST_AUTHOR_WHEN + return False + def revision_type_str(self): return self.REVISION_TYPE_CHOICES_DICT[self.revision_type] diff --git a/askbot/models/question.py b/askbot/models/question.py index dafe7ab0..1729b531 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -74,6 +74,7 @@ class ThreadManager(models.Manager): by_email = False, email_address = None ): + """creates new thread""" # TODO: Some of this code will go to Post.objects.create_new thread = super( @@ -142,16 +143,8 @@ class ThreadManager(models.Manager): models.Q(posts__deleted=False, posts__text__search = search_query) ) elif 'postgresql_psycopg2' in askbot.get_database_engine_name(): - rank_clause = "ts_rank(askbot_thread.text_search_vector, plainto_tsquery(%s))" - search_query = '&'.join(search_query.split()) - extra_params = (search_query,) - extra_kwargs = { - 'select': {'relevance': rank_clause}, - 'where': ['askbot_thread.text_search_vector @@ plainto_tsquery(%s)'], - 'params': extra_params, - 'select_params': extra_params, - } - return qs.extra(**extra_kwargs) + from askbot.search import postgresql + return postgresql.run_full_text_search(qs, search_query) else: return qs.filter( models.Q(title__icontains=search_query) | @@ -794,6 +787,7 @@ class Thread(models.Model): return False def retag(self, retagged_by=None, retagged_at=None, tagnames=None, silent=False): + """changes thread tags""" if None in (retagged_by, retagged_at, tagnames): raise Exception('arguments retagged_at, retagged_by and tagnames are required') diff --git a/askbot/models/reply_by_email.py b/askbot/models/reply_by_email.py index d329d38b..0db7244c 100644 --- a/askbot/models/reply_by_email.py +++ b/askbot/models/reply_by_email.py @@ -1,13 +1,14 @@ from datetime import datetime import random import string +import logging from django.db import models from django.contrib.auth.models import User from django.utils.translation import ugettext as _ from askbot.models.post import Post from askbot.models.base import BaseQuerySetManager from askbot.conf import settings as askbot_settings -from askbot.utils import mail +from askbot import mail class ReplyAddressManager(BaseQuerySetManager): """A manager for the :class:`ReplyAddress` model""" @@ -19,13 +20,10 @@ class ReplyAddressManager(BaseQuerySetManager): used_at__isnull = True ) - def create_new(self, post, user): + def create_new(self, **kwargs): """creates a new reply address""" - reply_address = ReplyAddress( - post = post, - user = user, - allowed_from_email = user.email - ) + kwargs['allowed_from_email'] = kwargs['user'].email + reply_address = ReplyAddress(**kwargs) while True: reply_address.address = ''.join(random.choice(string.letters + string.digits) for i in xrange(random.randint(12, 25))).lower() @@ -35,14 +33,28 @@ class ReplyAddressManager(BaseQuerySetManager): return reply_address +REPLY_ACTION_CHOICES = ( + ('post_answer', _('Post an answer')), + ('post_comment', _('Post a comment')), + ('replace_content', _('Edit post')), + ('append_content', _('Append to post')), + ('auto_answer_or_comment', _('Answer or comment, depending on the size of post')), + ('validate_email', _('Validate email and record signature')), +) class ReplyAddress(models.Model): """Stores a reply address for the post and the user""" address = models.CharField(max_length = 25, unique = True) post = models.ForeignKey( Post, + null = True,#reply not necessarily to posts related_name = 'reply_addresses' )#the emailed post + reply_action = models.CharField( + max_length = 32, + choices = REPLY_ACTION_CHOICES, + default = 'auto_answer_or_comment' + ) response_post = models.ForeignKey( Post, null = True, @@ -64,51 +76,97 @@ class ReplyAddress(models.Model): """True if was used""" return self.used_at != None - def edit_post(self, parts): + def as_email_address(self, prefix = 'reply-'): + """returns email address, prefix is added + in front of the code""" + return '%s%s@%s' % ( + prefix, + self.address, + askbot_settings.REPLY_BY_EMAIL_HOSTNAME + ) + + def edit_post( + self, body_text, title = None, edit_response = False + ): """edits the created post upon repeated response to the same address""" - assert self.was_used == True - content, stored_files = mail.process_parts(parts) - self.user.edit_post( - post = self.response_post, - body_text = content, - revision_comment = _('edited by email'), - by_email = True - ) - self.response_post.thread.invalidate_cached_data() + if self.was_used or edit_response: + reply_action = 'append_content' + else: + reply_action = self.reply_action + + if edit_response: + post = self.response_post + else: + post = self.post + + if reply_action == 'append_content': + body_text = post.text + '\n\n' + body_text + revision_comment = _('added content by email') + else: + assert(reply_action == 'replace_content') + revision_comment = _('edited by email') + + if post.post_type == 'question': + assert(post is self.post) + self.user.edit_question( + question = post, + body_text = body_text, + title = title, + revision_comment = revision_comment, + by_email = True + ) + else: + self.user.edit_post( + post = post, + body_text = body_text, + revision_comment = revision_comment, + by_email = True + ) + self.post.thread.invalidate_cached_data() - def create_reply(self, parts): + def create_reply(self, body_text): """creates a reply to the post which was emailed to the user """ result = None - #todo: delete stored files if this function fails - content, stored_files = mail.process_parts(parts) - if self.post.post_type == 'answer': result = self.user.post_comment( self.post, - content, + body_text, by_email = True ) elif self.post.post_type == 'question': - wordcount = len(content)/6#this is a simplistic hack - if wordcount > askbot_settings.MIN_WORDS_FOR_ANSWER_BY_EMAIL: + if self.reply_action == 'auto_answer_or_comment': + wordcount = len(body_text)/6#todo: this is a simplistic hack + if wordcount > askbot_settings.MIN_WORDS_FOR_ANSWER_BY_EMAIL: + reply_action = 'post_answer' + else: + reply_action = 'post_comment' + else: + reply_action = self.reply_action + + if reply_action == 'post_answer': result = self.user.post_answer( self.post, - content, + body_text, by_email = True ) - else: + elif reply_action == 'post_comment': result = self.user.post_comment( self.post, - content, + body_text, by_email = True ) + else: + logging.critical( + 'Unexpected reply action: "%s", post by email failed' % reply_action + ) + return None#todo: there may be a better action to take here... elif self.post.post_type == 'comment': result = self.user.post_comment( self.post.parent, - content, + body_text, by_email = True ) result.thread.invalidate_cached_data() diff --git a/askbot/models/repute.py b/askbot/models/repute.py index 09e74067..33ec3a42 100644 --- a/askbot/models/repute.py +++ b/askbot/models/repute.py @@ -1,9 +1,10 @@ +import datetime from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic from django.contrib.auth.models import User from django.db import models from django.utils.translation import ugettext as _ -import datetime +from django.utils.html import escape from askbot import const from django.core.urlresolvers import reverse @@ -242,7 +243,7 @@ class Repute(models.Model): return '<a href="%(url)s" title="%(link_title)s">%(question_title)s</a>' \ % { 'url': self.question.get_absolute_url(), - 'question_title': self.question.thread.title, - 'link_title': link_title + 'question_title': escape(self.question.thread.title), + 'link_title': escape(link_title) } diff --git a/askbot/models/signals.py b/askbot/models/signals.py index baa4c149..d538de76 100644 --- a/askbot/models/signals.py +++ b/askbot/models/signals.py @@ -21,6 +21,7 @@ delete_question_or_answer = django.dispatch.Signal( flag_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by']) remove_flag_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by']) user_updated = django.dispatch.Signal(providing_args=['instance', 'updated_by']) +user_registered = django.dispatch.Signal(providing_args=['user',]) #todo: move this to authentication app user_logged_in = django.dispatch.Signal(providing_args=['session']) @@ -31,6 +32,12 @@ post_updated = django.dispatch.Signal( 'newly_mentioned_users' ] ) +post_revision_published = django.dispatch.Signal( + providing_args = [ + 'revision', + 'was_approved' + ] + ) site_visited = django.dispatch.Signal(providing_args=['user', 'timestamp']) def pop_signal_receivers(signal): @@ -65,6 +72,7 @@ def pop_all_db_signal_receivers(): remove_flag_offensive, user_updated, user_logged_in, + user_registered, post_updated, award_badges_signal, #django signals diff --git a/askbot/models/tag.py b/askbot/models/tag.py index 779c0b68..858db2e6 100644 --- a/askbot/models/tag.py +++ b/askbot/models/tag.py @@ -82,21 +82,34 @@ class TagManager(BaseQuerySetManager): def get_query_set(self): return TagQuerySet(self.model) -#todo: implement this -#class GroupTagQuerySet(models.query.QuerySet): -# """Custom query set for the group""" -# def __init__(self, model): +class GroupTagQuerySet(TagQuerySet): + """Custom query set for the group""" + + def get_for_user(self, user = None): + return self.filter(user_memberships__user = user) + + def get_all(self): + return self.annotate( + member_count = models.Count('user_memberships') + ).filter( + member_count__gt = 0 + ) + + def get_by_name(self, group_name = None): + return self.get(name = clean_group_name(group_name)) + + def clean_group_name(name): """group names allow spaces, tag names do not, so we use this method to replace spaces with dashes""" return re.sub('\s+', '-', name.strip()) -class GroupTagManager(TagManager): +class GroupTagManager(BaseQuerySetManager): """manager for group tags""" -# def get_query_set(self): -# return GroupTagQuerySet(self.model) + def get_query_set(self): + return GroupTagQuerySet(self.model) def get_or_create(self, group_name = None, user = None): """creates a group tag or finds one, if exists""" @@ -105,7 +118,9 @@ class GroupTagManager(TagManager): #replace spaces with dashes group_name = clean_group_name(group_name) try: - tag = self.get(name = group_name) + #iexact is important!!! b/c we don't want case variants + #of tags + tag = self.get(name__iexact = group_name) except self.model.DoesNotExist: tag = self.model(name = group_name, created_by = user) tag.save() @@ -114,21 +129,6 @@ class GroupTagManager(TagManager): group_profile.save() return tag - #todo: maybe move this to query set - def get_for_user(self, user = None): - return self.filter(user_memberships__user = user) - - #todo: remove this when the custom query set is done - def get_all(self): - return self.annotate( - member_count = models.Count('user_memberships') - ).filter( - member_count__gt = 0 - ) - - def get_by_name(self, group_name = None): - return self.get(name = clean_group_name(group_name)) - class Tag(models.Model): name = models.CharField(max_length=255, unique=True) created_by = models.ForeignKey(User, related_name='created_tags') diff --git a/askbot/models/user.py b/askbot/models/user.py index 70aee263..e4077ea5 100644 --- a/askbot/models/user.py +++ b/askbot/models/user.py @@ -372,6 +372,7 @@ class GroupProfile(models.Model): ) class Meta: + #added to make account merges work properly app_label = 'askbot' def can_accept_user(self, user): diff --git a/askbot/search/mysql.py b/askbot/search/mysql.py new file mode 100644 index 00000000..df86070d --- /dev/null +++ b/askbot/search/mysql.py @@ -0,0 +1,54 @@ +from django.db import connection + +SUPPORTS_FTS = None +HINT_TABLE = None +NO_FTS_WARNING = """ +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +!! !! +!! WARNING: Your database engine does not support !! +!! full text search. Please switch to PostgresQL !! +!! or select MyISAM engine for MySQL !! +!! !! +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +""" + +def supports_full_text_search(hint_table = None): + """True if the database engine is MyISAM + hint_table - is the table that we look into to determine + whether database supports FTS or not. + """ + global SUPPORTS_FTS + global HINT_TABLE + if SUPPORTS_FTS is None: + cursor = connection.cursor() + if hint_table: + table_name = hint_table + HINT_TABLE = hint_table + else: + from askbot.models import Post + table_name = Post._meta.db_table + cursor.execute("SHOW CREATE TABLE %s" % table_name) + data = cursor.fetchone() + if 'ENGINE=MyISAM' in data[1]: + SUPPORTS_FTS = True + else: + SUPPORTS_FTS = False + return SUPPORTS_FTS + + question_index_sql = get_create_full_text_index_sql( + index_name, + table_namee, + ('title','text','tagnames',) + ) +def get_create_full_text_index_sql(index_name, table_name, column_list): + cursor = connection.cursor() + column_sql = '(%s)' % ','.join(column_list) + sql = 'CREATE FULLTEXT INDEX %s on %s %s' % (index_name, table_name, column_sql) + cursor.execute(question_index_sql) + return sql + else: + print NO_FTS_WARNING + +def get_drop_index_sql(index_name, table_name): + return 'ALTER TABLE %s DROP INDEX %s' % (table_name, index_name) + diff --git a/askbot/search/postgresql/__init__.py b/askbot/search/postgresql/__init__.py index a802a5eb..5b893129 100644 --- a/askbot/search/postgresql/__init__.py +++ b/askbot/search/postgresql/__init__.py @@ -19,3 +19,32 @@ def setup_full_text_search(script_path): cursor.execute(fts_init_query) finally: cursor.close() + +def run_full_text_search(query_set, query_text): + """runs full text search against the query set and + the search text. All words in the query text are + added to the search with the & operator - i.e. + the more terms in search, the narrower it is. + + It is also assumed that we ar searching in the same + table as the query set was built against, also + it is assumed that the table has text search vector + stored in the column called `text_search_vector`. + """ + table_name = query_set.model._meta.db_table + + rank_clause = 'ts_rank(' + table_name + \ + '.text_search_vector, plainto_tsquery(%s))' + + where_clause = table_name + '.text_search_vector @@ plainto_tsquery(%s)' + + search_query = '&'.join(query_text.split())#apply "AND" operator + extra_params = (search_query,) + extra_kwargs = { + 'select': {'relevance': rank_clause}, + 'where': [where_clause,], + 'params': extra_params, + 'select_params': extra_params, + } + + return query_set.extra(**extra_kwargs) diff --git a/askbot/search/postgresql/user_profile_search_051312.plsql b/askbot/search/postgresql/user_profile_search_051312.plsql new file mode 100644 index 00000000..99e3121d --- /dev/null +++ b/askbot/search/postgresql/user_profile_search_051312.plsql @@ -0,0 +1,89 @@ +/* +Script depends on functions defined for general askbot full text search. +to_tsvector(), add_tsvector_column() + +calculates text search vector for the user profile +the searched fields are: +1) user name +2) user profile +3) group names - for groups to which user belongs +*/ +CREATE OR REPLACE FUNCTION get_auth_user_tsv(user_id integer) +RETURNS tsvector AS +$$ +DECLARE + group_query text; + user_query text; + onerow record; + tsv tsvector; +BEGIN + group_query = + 'SELECT user_group.name as group_name ' || + 'FROM tag AS user_group ' || + 'INNER JOIN askbot_groupmembership AS gm ' || + 'ON gm.user_id= ' || user_id || ' AND gm.group_id=user_group.id'; + + tsv = to_tsvector(''); + FOR onerow in EXECUTE group_query LOOP + tsv = tsv || to_tsvector(onerow.group_name); + END LOOP; + + user_query = 'SELECT username, about FROM auth_user WHERE id=' || user_id; + FOR onerow in EXECUTE user_query LOOP + tsv = tsv || to_tsvector(onerow.username) || to_tsvector(onerow.about); + END LOOP; + RETURN tsv; +END; +$$ LANGUAGE plpgsql; + +/* create tsvector columns in the content tables */ +SELECT add_tsvector_column('text_search_vector', 'auth_user'); + +/* populate tsvectors with data */ +UPDATE auth_user SET text_search_vector = get_auth_user_tsv(id); + +/* one trigger per table for tsv updates */ + +/* set up auth_user triggers */ +CREATE OR REPLACE FUNCTION auth_user_tsv_update_handler() +RETURNS trigger AS +$$ +BEGIN + new.text_search_vector = get_auth_user_tsv(new.id); + RETURN new; +END; +$$ LANGUAGE plpgsql; +DROP TRIGGER IF EXISTS auth_user_tsv_update_trigger ON auth_user; + +CREATE TRIGGER auth_user_tsv_update_trigger +BEFORE INSERT OR UPDATE ON auth_user +FOR EACH ROW EXECUTE PROCEDURE auth_user_tsv_update_handler(); + +/* group membership trigger */ +CREATE OR REPLACE FUNCTION group_membership_tsv_update_handler() +RETURNS trigger AS +$$ +DECLARE + tsv tsvector; + user_query text; +BEGIN + user_query = 'UPDATE auth_user SET username=username WHERE ' || + 'id=' || new.user_id; + /* just trigger the tsv update on user */ + EXECUTE user_query; + RETURN new; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS group_membership_tsv_update_trigger +ON askbot_groupmembership; + +CREATE TRIGGER group_membership_tsv_update_trigger +AFTER INSERT OR DELETE +ON askbot_groupmembership +FOR EACH ROW EXECUTE PROCEDURE group_membership_tsv_update_handler(); + +DROP INDEX IF EXISTS auth_user_search_idx; + +CREATE INDEX auth_user_search_idx ON auth_user +USING gin(text_search_vector); diff --git a/askbot/setup_templates/settings.py.mustache b/askbot/setup_templates/settings.py.mustache index e29f1dca..18ac214d 100644 --- a/askbot/setup_templates/settings.py.mustache +++ b/askbot/setup_templates/settings.py.mustache @@ -97,7 +97,7 @@ TEMPLATE_LOADERS = ( MIDDLEWARE_CLASSES = ( #'django.middleware.gzip.GZipMiddleware', - 'askbot.middleware.locale.LocaleMiddleware', + #'askbot.middleware.locale.LocaleMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', #'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', diff --git a/askbot/skins/common/media/js/editor.js b/askbot/skins/common/media/js/editor.js index 2d1f5670..ae4f5aea 100644 --- a/askbot/skins/common/media/js/editor.js +++ b/askbot/skins/common/media/js/editor.js @@ -46,6 +46,11 @@ function ajaxFileUpload(imageUrl, startUploadHandler) success: function (data, status) { var fileURL = $(data).find('file_url').text(); + /* + * hopefully a fix for the "fakepath" issue + * https://www.mediawiki.org/wiki/Special:Code/MediaWiki/83225 + */ + fileURL = fileURL.replace(/\w:.*\\(.*)$/,'$1'); var error = $(data).find('error').text(); if(error != ''){ alert(error); diff --git a/askbot/skins/common/media/js/post.js b/askbot/skins/common/media/js/post.js index 307d3be5..58f2436d 100644 --- a/askbot/skins/common/media/js/post.js +++ b/askbot/skins/common/media/js/post.js @@ -1062,10 +1062,11 @@ var questionRetagger = function(){ var tags_str = ''; links.each(function(index, element){ if (index === 0){ - tags_str = $(element).html(); + //this is pretty bad - we should use Tag.getName() + tags_str = $(element).attr('data-tag-name'); } else { - tags_str += ' ' + $(element).html(); + tags_str += ' ' + $(element).attr('data-tag-name'); } }); return tags_str; @@ -2354,9 +2355,8 @@ UserGroupProfileEditor.prototype.decorate = function(element){ logo_changer.decorate(change_logo_btn); }; -var GroupJoinButton = function(group_id){ +var GroupJoinButton = function(){ TwoStateToggle.call(this); - this._group_id = group_id; }; inherits(GroupJoinButton, TwoStateToggle); @@ -2375,7 +2375,8 @@ GroupJoinButton.prototype.getHandler = function(){ url: askbot['urls']['join_or_leave_group'], success: function(data){ if (data['success']){ - me.setOn(data['is_member']); + var new_state = data['is_member'] ? 'on-state':'off-state'; + me.setState(new_state); } else { showMessage(me.getElement(), data['message']); } @@ -2384,6 +2385,11 @@ GroupJoinButton.prototype.getHandler = function(){ }; }; +GroupJoinButton.prototype.decorate = function(elem) { + GroupJoinButton.superClass_.decorate.call(this, elem); + this._group_id = this._element.data('groupId'); +}; + $(document).ready(function() { $('[id^="comments-for-"]').each(function(index, element){ var comments = new PostCommentsWidget(); diff --git a/askbot/skins/common/media/js/tag_selector.js b/askbot/skins/common/media/js/tag_selector.js index d5482992..be79a9b6 100644 --- a/askbot/skins/common/media/js/tag_selector.js +++ b/askbot/skins/common/media/js/tag_selector.js @@ -72,6 +72,7 @@ TagDetailBox.prototype.renderFor = function(wildcard){ $.each(me._tag_names, function(idx, name){ var tag = new Tag(); tag.setName(name); + tag.setUrlParams(name) //tag.setLinkable(false); me._tags.push(tag); me._tag_list_element.append(tag.getElement()); diff --git a/askbot/skins/common/media/js/user.js b/askbot/skins/common/media/js/user.js index b7dc0951..ad0b8365 100644 --- a/askbot/skins/common/media/js/user.js +++ b/askbot/skins/common/media/js/user.js @@ -644,13 +644,13 @@ UserGroup.prototype.decorate = function(element){ this._name = $.trim(element.find('a').html()); var deleter = new DeleteIcon(); deleter.setHandler(this.getDeleteHandler()); - deleter.setContent('x'); - this._element.find('.group-name').append(deleter.getElement()); + deleter.setContent(gettext('Remove')); + this._element.find('td:last').append(deleter.getElement()); this._delete_icon = deleter; }; UserGroup.prototype.createDom = function(){ - var element = this.makeElement('li'); + var element = this.makeElement('tr'); element.html(this._content); this._element = element; this.decorate(element); @@ -675,7 +675,7 @@ GroupsContainer.prototype.decorate = function(element){ var group_names = []; var me = this; //collect list of groups - $.each(element.find('li'), function(idx, li){ + $.each(element.find('tr'), function(idx, li){ var group = new UserGroup(); group.setGroupsContainer(me); group.decorate($(li)); @@ -829,21 +829,25 @@ UserGroupsEditor.prototype.decorate = function(element){ adder.decorate(add_link); var groups_container = new GroupsContainer(); - groups_container.decorate(element.find('ul')); + groups_container.decorate(element.find('#groups-list')); adder.setGroupsContainer(groups_container); //todo - add group deleters }; (function(){ - var fbtn = $('.follow-toggle'); + var fbtn = $('.follow-user-toggle'); if (fbtn.length === 1){ var follow_user = new FollowUser(); follow_user.decorate(fbtn); follow_user.setUserName(askbot['data']['viewUserName']); } - if (askbot['data']['userIsAdminOrMod']){ - var group_editor = new UserGroupsEditor(); - group_editor.decorate($('#user-groups')); + if (askbot['data']['userId'] !== askbot['data']['viewUserId']) { + if (askbot['data']['userIsAdminOrMod']){ + var group_editor = new UserGroupsEditor(); + group_editor.decorate($('#user-groups')); + } else { + $('#add-group').remove(); + } } else { $('#add-group').remove(); } diff --git a/askbot/skins/common/media/js/utils.js b/askbot/skins/common/media/js/utils.js index 297e3f9a..d04a3cdb 100644 --- a/askbot/skins/common/media/js/utils.js +++ b/askbot/skins/common/media/js/utils.js @@ -109,7 +109,7 @@ var notify = function() { return { show: function(html) { if (html) { - $("body").css("margin-top", "2.2em"); + $("body").addClass('user-messages'); $(".notify span").html(html); } $(".notify").fadeIn("slow"); @@ -123,7 +123,7 @@ var notify = function() { ); } $(".notify").fadeOut("fast"); - $("body").css("margin-top", "0"); + $('body').removeClass('user-messages'); visible = false; }, isVisible: function() { return visible; } @@ -786,9 +786,7 @@ TwoStateToggle.prototype.decorate = function(element){ this.toggleUrl = element.attr('data-toggle-url'); //detect state and save it - if ( - element.attr('nodeName') === 'INPUT' && element.attr('type', 'checkbox') - ) { + if (this.isCheckBox()) { this._state = element.attr('checked') ? 'state-on' : 'state-off'; } else { var text = $.trim(element.html()); @@ -989,7 +987,9 @@ Tag.prototype.decorate = function(element){ this._delete_icon.decorate(del); } this._inner_element = this._element.find('.tag'); - this._name = this.decodeTagName($.trim(this._inner_element.html())); + this._name = this.decodeTagName( + $.trim(this._inner_element.attr('data-tag-name')) + ); if (this._title !== null){ this._inner_element.attr('title', this._title); } diff --git a/askbot/skins/common/templates/question/closed_question_info.html b/askbot/skins/common/templates/question/closed_question_info.html index 87d9379c..f6f3f557 100644 --- a/askbot/skins/common/templates/question/closed_question_info.html +++ b/askbot/skins/common/templates/question/closed_question_info.html @@ -1,5 +1,5 @@ <div class="question-status"> <h3>{% trans close_reason=thread.get_close_reason_display() %}The question has been closed for the following reason <b>"{{ close_reason }}"</b> <i>by{% endtrans %} - <a href="{{ thread.closed_by.get_profile_url() }}">{{ thread.closed_by.username }}</a> </i><br> + <a href="{{ thread.closed_by.get_profile_url() }}">{{ thread.closed_by.username|escape }}</a> </i><br> {% trans closed_at=thread.closed_at %}close date {{closed_at}}{% endtrans %}</h3> </div> diff --git a/askbot/skins/default/media/style/lib_style.less b/askbot/skins/default/media/style/lib_style.less index 63389526..14dcc4bc 100644 --- a/askbot/skins/default/media/style/lib_style.less +++ b/askbot/skins/default/media/style/lib_style.less @@ -14,7 +14,7 @@ @body-font:Arial; /* "Trebuchet MS", sans-serif;*/ @sort-font:Georgia, serif; -@main-font:'Yanone Kaffeesatz', Arial, sans-serif; +@main-font:'Open Sans Condensed', Arial, sans-serif; @secondary-font:Arial; /* Buttons */ diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css index 3d53eba8..348eb6b2 100644 --- a/askbot/skins/default/media/style/style.css +++ b/askbot/skins/default/media/style/style.css @@ -594,13 +594,13 @@ body.anon #searchBar .searchInputCancelable { .box .inputs #ignoredTagInput, .box .inputs #subscribedTagInput, .box .inputs #ab-tag-search { - width: 153px; + width: 156px; padding-left: 5px; border: #c9c9b5 1px solid; height: 25px; } .box .inputs #ab-tag-search { - width: 135px; + width: 138px; } .box .inputs #interestingTagAdd, .box .inputs #ignoredTagAdd, @@ -661,7 +661,6 @@ body.anon #searchBar .searchInputCancelable { } .box .inputs #ab-tag-search-add { width: 47px; - margin-left: 3px; } .box img.gravatar { margin: 1px; @@ -1238,7 +1237,8 @@ ul#related-tags li { color: #1A1A1A; } .users-page h1, -.tags-page h1 { +.tags-page h1, +.groups-page h1 { float: left; } .main-page h1 { @@ -1294,7 +1294,7 @@ ul#related-tags li { } #askFormBar { display: inline-block; - padding: 4px 7px 5px 0px; + padding: 4px 7px 0px 0px; margin-top: 0px; } #askFormBar p { @@ -1314,10 +1314,8 @@ ul#related-tags li { } .ask-page div#question-list, .edit-question-page div#question-list { - float: none; border-bottom: #f0f0ec 1px solid; - float: left; - margin-bottom: 10px; + float: none; } .ask-page div#question-list a, .edit-question-page div#question-list a { @@ -1367,6 +1365,7 @@ ul#related-tags li { .title-desc { color: #707070; font-size: 13px; + margin-bottom: 5px; } #fmanswer input.submit, .ask-page input.submit, @@ -1776,7 +1775,10 @@ ul#related-tags li { background: url(../images/vote-arrow-down-on-new.png) no-repeat; } .question-page #fmanswer_button { - margin: 8px 0px ; + margin: 8px 0px; +} +.question-page #fmanswer_button.answer-own-question { + width: 150px; } .question-page .question-img-favorite:hover { background: url(../images/vote-favorite-on.png); @@ -2517,7 +2519,7 @@ a:hover.medal { margin-top: -2px; width: 100px; height: 26px; - font-size: 12px; + font-size: 14px; text-align: center; text-decoration: none; cursor: pointer; @@ -3258,6 +3260,11 @@ ul.post-retag { margin-bottom: 0px; margin-left: 5px; } +ul.post-retag input { + width: 400px; + height: 1.5em; + margin: 3px 0 0 -3px; +} #question-controls .tags { margin: 0 0 3px 0; } @@ -3526,16 +3533,11 @@ img.group-logo { #groups-list { margin-left: 0px; } -#groups-list li { - display: inline; - list-style-type: none; - list-style-position: inside; - float: left; - text-align: center; -} -#groups-list .group-logo, #groups-list .group-name { - display: block; + padding-right: 20px; +} +#groups-list td { + padding-bottom: 5px; } #reject-edit-modal input, #reject-edit-modal textarea { diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less index 4d758cea..dfda8ec0 100644 --- a/askbot/skins/default/media/style/style.less +++ b/askbot/skins/default/media/style/style.less @@ -270,12 +270,13 @@ body.user-messages { #metaNav {/* Top Navigation bar containing links for tags, people and badges, check widgets/header.html */ float: right;/* for #header.with-logo it is modified */ + margin-right: 7px; a { color: #e2e2ae; padding: 0px 0px 0px 35px; height: 25px; - line-height: 30px; + line-height: 25px; margin:5px 0px 0px 10px; font-size: 18px; font-weight: 100; @@ -368,11 +369,11 @@ body.user-messages { } .scope-selector{ - font-size:21px; - color:#5a5a4b; + font-size:20px; + color:#7a7a6b; height:55px; line-height:55px; - margin-left:24px + margin-left:16px } .on{ background:url(../images/scopearrow.png) no-repeat center bottom; @@ -385,9 +386,9 @@ body.user-messages { } #searchBar { /* Main search form , check widgets/search_bar.html */ - display:inline-block; + display: inline-block; background-color: #fff; - width:412px; + width: 400px; border: 1px solid #c9c9b5; float:right; height:42px; @@ -395,7 +396,7 @@ body.user-messages { .searchInput, .searchInputCancelable{ font-size: 30px; - height: 40px; + height: 39px; font-weight:300; background:#FFF; border:0px; @@ -406,7 +407,7 @@ body.user-messages { } .searchInput,{ - width: 352px; + width: 340px; } .searchInputCancelable { @@ -472,7 +473,7 @@ body.anon { margin-top:6px; float:right; text-transform:uppercase; - .button-style(200px, 42px, 23px); + .button-style(200px, 42px, 20px); } #askButton:hover{ @@ -546,12 +547,10 @@ body.anon { label { color: @info-text; font-size:15px; - display: block; - float: right; + vertical-align: bottom; + display: inline; text-align:left; font-family:@main-font; - width:80px; - margin-right:18px; } #displayTagFilterControl label, @@ -659,7 +658,7 @@ body.anon { /* notify by email box */ .notify-sidebar #question-subscribe-sidebar { - margin: 7px 0 0 3px; + margin: 0 0 0 3px; } } @@ -813,12 +812,12 @@ body.anon { #questionCount{ font-weight:bold; - font-size:23px; + font-size:20px; color:@section-title; width:200px; float:left; - margin-bottom:8px; - padding-top:6px; + margin-bottom:6px; + padding-top: 6px; font-family:@main-font; } @@ -868,16 +867,16 @@ ul#searchTags { .short-summary { position: relative; filter: inherit; - padding: 10px; + padding: 10px 0; border-bottom: 1px solid #DDDBCE; margin-bottom:1px; overflow: hidden; - width: 710px; + width: 733px; float: left; - background: url(../images/summary-background.png) repeat-x; - + /*background: url(../images/summary-background.png) repeat-x;*/ + h2 { - font-size: 24px; + font-size: 20px; font-weight:normal; line-height: 26px; padding-left: 0; @@ -935,7 +934,7 @@ ul#searchTags { } .tags { - margin-top: 0; + margin: 0 0 0 1px; } .votes, .answers, .favorites, .views { @@ -1159,6 +1158,10 @@ ul.tags.marked-tags li, ul#ab-user-tags li { width: 160px; margin:5px; + margin-left: 0; +} +.tags-page ul.tags { + margin-left: 5px; } ul#related-tags li { @@ -1215,7 +1218,9 @@ ul#related-tags li { color: #1A1A1A; } -.users-page h1, .tags-page h1 { +.users-page h1, +.tags-page h1, +.groups-page h1 { float: left; } @@ -1512,14 +1517,14 @@ ul#related-tags li { h1{ padding-top:0px; - font-family:@main-font; - } - - h1 a{ - color:@question-link; - font-size:30px; - font-weight:normal; - line-height:1; + font-family:@main-font; + + a { + color:@question-link; + font-size:26px; + font-weight:normal; + line-height:1; + } } p.rss { @@ -1527,7 +1532,7 @@ ul#related-tags li { clear:both; padding: 3px 0 0 23px; font-size: 15px; - width:110px; + width:130px; background-position:center left; margin-left:0px !important; } @@ -1536,7 +1541,7 @@ ul#related-tags li { font-family:@main-font; vertical-align: top; } - + .question-content{ float:right; width:682px; @@ -1550,7 +1555,7 @@ ul#related-tags li { #question-table, .answer-table { - margin: 6px 0 6px 0; + margin: 8px 0 6px 0; border-spacing: 0px; width: 670px; padding-right:10px; @@ -1694,7 +1699,7 @@ ul#related-tags li { .tabBar{ width:100%; } - + #questionCount{ float:left; font-family:@main-font; @@ -1727,7 +1732,10 @@ ul#related-tags li { } #fmanswer_button{ - margin:8px 0px ; + margin:8px 0px; + } + #fmanswer_button.answer-own-question { + width: 150px; } .question-img-favorite:hover { background: url(../images/vote-favorite-on.png) @@ -1960,8 +1968,8 @@ ul#related-tags li { float: left; text-align: center; padding-top: 2px; - margin:10px 10px 0px 3px; - /* smalls IE fixes */ + margin:0px 10px 0px 3px; + /* small IE fixes */ *margin:0; *height:210px; *width:30px; @@ -1973,7 +1981,7 @@ ul#related-tags li { .vote-number { font-family: @main-font; - padding: 0px 0 5px 0; + padding: 2px 0 5px 0; font-size: 25px; font-weight: bold; color: #777; @@ -1981,10 +1989,11 @@ ul#related-tags li { .vote-buttons .notify-sidebar { text-align: left; - width:120px; - } - .vote-buttons .notify-sidebar label { - vertical-align: top; + width:130px; + margin-top: 7px; + label { + vertical-align: top; + } } .tabBar-answer{ @@ -2001,7 +2010,7 @@ ul#related-tags li { .accepted-answer { background-color: #f7fecc; border-bottom-color: #9BD59B; - + .vote-buttons { width:27px; margin-right:10px; @@ -3162,6 +3171,11 @@ ul.post-tags li { ul.post-retag { margin-bottom:0px; margin-left:5px; + input { + width: 400px; + height: 1.5em; + margin: 3px 0 0 -3px; + } } #question-controls .tags { @@ -3283,19 +3297,26 @@ p.signup_p { .avatar-page li { display: inline; } -.user-profile-page .avatar p { - margin-bottom: 0px; -} -.user-profile-page .tabBar a#stats { - margin-left: 0; -} -.user-profile-page img.gravatar { - margin: 2px 0 3px 0; -} -.user-profile-page h3 { - padding: 0; - margin-top: -3px; + +.user-profile-page { + .avatar p { + margin-bottom: 0px; + } + .tabBar a#stats { + margin-left: 0; + } + img.gravatar { + margin: 2px 0 3px 0; + } + h3 { + padding: 0; + margin-top: -3px; + } + ul.tags { + margin-left: 5px; + } } + .userList { font-size: 13px; } @@ -3424,19 +3445,14 @@ img.group-logo { #groups-list { margin-left: 0px; - li { - display: inline; - list-style-type: none; - list-style-position: inside; - float: left; - text-align: center; + .group-name { + padding-right: 20px; } - .group-logo, .group-name { - display: block; + td { + padding-bottom: 5px; } } - #reject-edit-modal { input, textarea { width: 514px; diff --git a/askbot/skins/default/templates/badge.html b/askbot/skins/default/templates/badge.html index d1f75617..b2c4ce8b 100644 --- a/askbot/skins/default/templates/badge.html +++ b/askbot/skins/default/templates/badge.html @@ -20,7 +20,7 @@ <div class="user"> <ul> <li class="thumb">{{ gravatar(recipient, 32) }}</li> - <li><a href="{{ recipient.get_absolute_url() }}">{{recipient.username}}</a></li> + <li><a href="{{ recipient.get_absolute_url() }}">{{recipient.username|escape}}</a></li> <li>{{ macros.user_score_and_badge_summary(recipient) }}</li> </ul> </div> diff --git a/askbot/skins/default/templates/email/ask_for_signature.html b/askbot/skins/default/templates/email/ask_for_signature.html new file mode 100644 index 00000000..cafeee2b --- /dev/null +++ b/askbot/skins/default/templates/email/ask_for_signature.html @@ -0,0 +1,11 @@ +{% import "email/macros.html" as macros %} +<p style="{{ macros.heading_style() }}"> + {% trans user=username|escape %}{{ user }}, please reply to this message.{% endtrans %} +</p> +<p> + {% trans %}Your post could not be published, because we could not detect signature in your email.{% endtrans %}<br/> + {% trans %}Please make a simple response, without editing this message.{% endtrans %}<br/> + {% trans %}We will then attempt to detect the signature in your response and you should be able to post.{% endtrans %} +</p> +{% include "email/footer.html" %} +<p style="{{ macros.fine_print_style() }}">{{ footer_code }}</p> diff --git a/askbot/skins/default/templates/feedback_email.txt b/askbot/skins/default/templates/email/feedback_email.txt index a729066a..a729066a 100644 --- a/askbot/skins/default/templates/feedback_email.txt +++ b/askbot/skins/default/templates/email/feedback_email.txt diff --git a/askbot/skins/default/templates/email/footer.html b/askbot/skins/default/templates/email/footer.html new file mode 100644 index 00000000..eda1269d --- /dev/null +++ b/askbot/skins/default/templates/email/footer.html @@ -0,0 +1 @@ +<p>{% trans %}Sincerely,<br>{{ site_name }} Administrator{% endtrans %}</p> diff --git a/askbot/skins/default/templates/email/insufficient_rep_to_post_by_email.html b/askbot/skins/default/templates/email/insufficient_rep_to_post_by_email.html new file mode 100644 index 00000000..284cc1b0 --- /dev/null +++ b/askbot/skins/default/templates/email/insufficient_rep_to_post_by_email.html @@ -0,0 +1,15 @@ +{% import "email/macros.html" as macros %} +{# parameters: + * min_upvotes + * username + * site_name - for the footer + * site_link - html for the link +#} +<p style="{{ macros.heading_style() }}"> + {% trans user=username|escape %}{{ username }}, your question could not be posted by email just yet.{% endtrans %} +</p> +<p> + {% trans %}To make posts by email, you need to receive about {{min_upvotes}} upvotes.{% endtrans %}<br/> + {% trans link=site_link|safe %}At this time, please post your question at {{link}}{% endtrans %} +</p> +{% include "email/footer.html" %} diff --git a/askbot/skins/default/templates/email/macros.html b/askbot/skins/default/templates/email/macros.html new file mode 100644 index 00000000..d7817bf9 --- /dev/null +++ b/askbot/skins/default/templates/email/macros.html @@ -0,0 +1,96 @@ +{% macro quoted_post( + post = None, + quote_level = 0, + format = None, + is_leaf_post = False + ) +%} + {% spaceless %} + {{ start_quote(quote_level) }} + {% set author = post.author.username|escape %} + {% if post.post_type == 'question' %} + <p> + {% if format == 'parent_subthread' %} + {% if is_leaf_post %} + {% trans %}Question by {{ author }}:{% endtrans %} + {% else %} + {% trans -%} + In reply to {{ author }}'s question: + {%- endtrans %} + {% endif %} + {% else %} + {% trans %}Question :{% endtrans %} + {% endif %} + {{ post.thread.title }} + </p> + <p> + {% if format != 'parent_subthread' %} + {% trans %}Asked by {{ author }}:{% endtrans %} + {% endif %} + </p> + {% set tag_names = post.get_tag_names() %} + {% if tag_names %} + <p> + {% trans %}Tags:{% endtrans %} + {{ tag_names|join(', ') }}. + </p> + {% endif %} + {% elif post.post_type == 'answer' %} + <p> + {% if format == 'parent_subthread' %} + {% if is_leaf_post %} + {% trans -%} + {{ author }}'s answer: + {%- endtrans %} + {% else %} + {% trans -%} + In reply to {{ author }}'s answer: + {%- endtrans %} + {% endif %} + {% else %} + {% trans %}Answered by {{ author }}:{% endtrans %} + {% endif %} + </p> + {% else %} + <p> + {% if format == 'parent_subthread' %} + {% if is_leaf_post %} + {% trans -%} + {{ author }}'s comment: + {%- endtrans %} + {% else %} + {% trans -%} + In reply to {{ author }}'s comment: + {%- endtrans %} + {% endif %} + {% else %} + {% trans author -%} + Commented by {{ author }}: + {%- endtrans %} + {% endif %} + </p> + {% endif %} + {{ post.html }} + {{ end_quote(quote_level) }} + {% endspaceless %} +{% endmacro %} + +{% macro start_quote(level = 0) %} + {% for number in range(level) %} + <div style="padding-left:5px; border-left: 2px solid #aaa;"> + {% endfor %} +{% endmacro %} + +{% macro end_quote(level = 0) %} + {% for number in range(level) %} + </div> + {% endfor %} +{% endmacro %} + +{% macro heading_style() %} +font-size:14px;font-weight:bold;margin-bottom:0px; +{% endmacro %} + +{% macro fine_print_style() %} +font-size:8px;color:#aaa;margin-bottom:0px; +{% endmacro %} diff --git a/askbot/skins/default/templates/email/notify_author_about_approved_post.html b/askbot/skins/default/templates/email/notify_author_about_approved_post.html new file mode 100644 index 00000000..085141d9 --- /dev/null +++ b/askbot/skins/default/templates/email/notify_author_about_approved_post.html @@ -0,0 +1,21 @@ +{# + parameters: + * reply_separator_line + * replace_content_address + * mailto_link_subject + * post + * reply_code (comma-separated list of emails to respond to this message) +#} +{{ reply_separator_line }} +<p>{% trans + post_text = post.text|safe_urlquote, + subject = mailto_link_subject|safe_urlquote, + author_email_signature = author_email_signature|safe_urlquote +%}If you would like to edit by email, please +<a href="mailto:{{ replace_content_address }}?body={{ post_text }}{{ author_email_signature}}&subject={{ subject }}">click here</a>{% endtrans %}</p> +<p>{% trans %}Below is a copy of your post:{% endtrans %}</p> +{% if post.post_type == 'question' %} + <p style="font-size:16px">{{ post.thread.title }}</p> +{% endif %} +{{ post.html }} +<p style="font-size:8px;color:#aaa;">{{ reply_code }}</p> diff --git a/askbot/skins/default/templates/email/post_as_subthread.html b/askbot/skins/default/templates/email/post_as_subthread.html new file mode 100644 index 00000000..9b6eb728 --- /dev/null +++ b/askbot/skins/default/templates/email/post_as_subthread.html @@ -0,0 +1,17 @@ +{% from "email/macros.html" import quoted_post %} +{% if post.post_type in ('question', 'answer') %} + {{ quoted_post(post) }} + {% set comments = post.get_cached_comments() %} + {% if comments %} + <p> + {% trans count=comments|length -%} + {{ comment }} comment: + {%- pluralize -%} + {{ count }} comments: + {%- endtrans -%} + </p> + {% for comment in comments %} + {{ quoted_post(comment, quote_level = 1) }} + {% endfor %} + {% endif %} +{% endif %} diff --git a/askbot/skins/default/templates/email/quoted_post.html b/askbot/skins/default/templates/email/quoted_post.html new file mode 100644 index 00000000..ecc20ad9 --- /dev/null +++ b/askbot/skins/default/templates/email/quoted_post.html @@ -0,0 +1,5 @@ +{% from "email/macros.html" import quoted_post %} +{{ quoted_post( + post, quote_level, is_leaf_post = is_leaf_post, format = format + ) +}} diff --git a/askbot/skins/default/templates/email/re_welcome_lamson_on.html b/askbot/skins/default/templates/email/re_welcome_lamson_on.html new file mode 100644 index 00000000..412fede8 --- /dev/null +++ b/askbot/skins/default/templates/email/re_welcome_lamson_on.html @@ -0,0 +1,7 @@ +<p style="font-size:16px;font-weight:bold;"> + {% trans %}Great, you are ready to use {{ site_name }}!{% endtrans %} +</p> +<p>{% trans %}You can post questions by emailing them at {{ ask_address }}.{% endtrans %}</p> +<p>{% trans %}When you receive update notifications, you will be able to respond to them, also by email.{% endtrans %}</p> +<p>{% trans %}Of course, you can always visit the {{ site_name }} at <a href="{{ site_url }}">{{ site_url }}</a>{% endtrans %}</p> +{% include "email/footer.html" %} diff --git a/askbot/skins/default/templates/reply_by_email_error.html b/askbot/skins/default/templates/email/reply_by_email_error.html index 53648184..53648184 100644 --- a/askbot/skins/default/templates/reply_by_email_error.html +++ b/askbot/skins/default/templates/email/reply_by_email_error.html diff --git a/askbot/skins/default/templates/email/welcome_lamson_on.html b/askbot/skins/default/templates/email/welcome_lamson_on.html new file mode 100644 index 00000000..0efa7096 --- /dev/null +++ b/askbot/skins/default/templates/email/welcome_lamson_on.html @@ -0,0 +1,15 @@ +{% import "email/macros.html" as macros %} +{# site_name - is short name of the site, email_code - address portion +of the reply email used for this message, we scan to the last appearance +of the email code to detect the response signature that will appear under #} +<p style="{{ macros.heading_style() }}"> + {% trans %}Welcome to {{ site_name }}!{% endtrans %} +</p> +<p> + {% trans %}Important: <em>Please reply</em> to this message, without editing it. We need this to determine your email signature and that the email address is valid and was typed correctly.{% endtrans %} +</p> +<p> + {% trans %}Until we receive the response from you, you will not be able ask or answer questions on {{ site_name }} by email.{% endtrans %} +</p> +{% include "email/footer.html" %} +<p style="{{ macros.fine_print_style() }}">{{ email_code }}</p>{# important #} diff --git a/askbot/skins/default/templates/feedback.html b/askbot/skins/default/templates/feedback.html index 85b5d00a..04b9a5b4 100644 --- a/askbot/skins/default/templates/feedback.html +++ b/askbot/skins/default/templates/feedback.html @@ -11,7 +11,7 @@ <form method="post" action="{% url feedback %}" accept-charset="utf-8">{% csrf_token %} {% if user.is_authenticated() %} <p class="message"> - {% trans user_name=user.username %} + {% trans user_name=user.username|escape %} <span class='big strong'>Dear {{user_name}}</span>, we look forward to hearing your feedback. Please type and send us your message below. {% endtrans %} diff --git a/askbot/skins/default/templates/groups.html b/askbot/skins/default/templates/groups.html index eda0c3ff..2499ac9f 100644 --- a/askbot/skins/default/templates/groups.html +++ b/askbot/skins/default/templates/groups.html @@ -1,18 +1,48 @@ {% import "macros.html" as macros %} -{% extends 'one_column_body.html' %} +{% extends 'two_column_body.html' %} {% block title %}{% trans %}Groups{% endtrans %}{% endblock %} {% block content %} - <h1 class="section-title">{% trans %}Groups{% endtrans %}</h1> - {% if can_edit %} + <div id="content-header"> + <h1 class="section-title">{% trans %}Groups{% endtrans %}</h1> + {% if request.user.is_authenticated() %} + <div class="tabBar"> + <div class="tabsC"> + <a id="all-groups" class="first{% if tab_name=="all-groups" %} on{% endif %}" + title="{% trans %}All groups{% endtrans %}" + href="{% url groups %}?sort=all-groups" + ><span>{% trans %}all groups{% endtrans %}</span></a> + <a id="my-groups" {% if tab_name=="my-groups" %}class="on"{% endif %} + title="{% trans %}My groups{% endtrans %}" + href="{% url groups %}?sort=my-groups" + ><span>{% trans %}my groups{% endtrans %}</span></a> + </div> + </div> + {% endif %} + <div class="clearfix"></div> + </div> + {% if user_can_add_groups %} <p id="group-add-tip"> {% trans %}Tip: to create a new group - please go to some user profile and add the new group there. That user will be the first member of the group{% endtrans %} </p> {% endif %} - <ul id="groups-list"> + <table id="groups-list"> {% for group in groups %} - <li> - {{ macros.user_group(group) }} - </li> + <tr> + {{ macros.user_group(group, groups_membership_info[group.id]) }} + </tr> {% endfor %} - </ul> + </table> +{% endblock %} +{% block endjs %} + <script type='text/javascript' src='{{"/js/jquery.validate.min.js"|media}}'></script> + <script src='{{"/js/post.js"|media}}' type='text/javascript'></script> + {% if request.user.is_authenticated() %} + <script type="text/javascript"> + askbot['urls']['join_or_leave_group'] = '{% url join_or_leave_group %}'; + $.each($('.group-join-btn'), function(idx, elem){ + var group_join_btn = new GroupJoinButton(); + group_join_btn.decorate($(elem)); + }); + </script> + {% endif %} {% endblock %} diff --git a/askbot/skins/default/templates/help.html b/askbot/skins/default/templates/help.html index 7dc58f5d..204fc086 100644 --- a/askbot/skins/default/templates/help.html +++ b/askbot/skins/default/templates/help.html @@ -4,7 +4,7 @@ <h1 class='section-title'>{% trans %}Help{% endtrans %}</h1> <p> {% if request.user.is_authenticated() %} - {% trans username = request.user.username %}Welcome {{username}},{% endtrans %} + {% trans username = request.user.username|escape %}Welcome {{username}},{% endtrans %} {% else %} {% trans %}Welcome,{% endtrans %} {% endif %} diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index c8dc245f..261c860f 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -6,41 +6,24 @@ >{% if icon == False %}{% if site_label %}{{ site_label }}{% else %}{{ site }}{% endif %}{% endif %}</a> {%- endmacro -%} -{%- macro follow_toggle(follow, name, alias, id) -%} - {# follow - boolean; name - object type name; alias - e.g. users name; id - object id #} - <div - class="follow-toggle" - id="follow-{{ name }}-{{ id }}" - > - {% if follow %} - <div class="follow">{% trans %}follow {{alias}}{% endtrans %}</div> - {% else %} - <div class="unfollow"> - <div class="unfollow-red">{% trans %}unfollow {{alias}}{% endtrans %}</div> - <div class="unfollow-green">{% trans %}following {{alias}}{% endtrans %}</div> - </div> - {% endif %} - </div> -{%- endmacro -%} - {%- macro inbox_post_snippet(response, inbox_section) -%} <div id="re_{{response.id}}" class="re{% if response.is_new %} new highlight{% else %} seen{% endif %}"> <input type="checkbox" /> <div class="face"> {{ gravatar(response.user, 48) }} </div> - <a style="font-size:12px" href="{{ response.user.get_absolute_url() }}">{{ response.user.username }}</a> + <a style="font-size:12px" href="{{ response.user.get_absolute_url() }}">{{ response.user.username|escape }}</a> <a style="text-decoration:none;" href="{{ response.response_url }}"> {{ response.response_type }} ({{ timeago(response.timestamp) }}):<br/> {% if inbox_section != 'flags' %} - {{ response.response_snippet }} + {{ response.response_snippet|escape }} {% endif %} </a> {% if inbox_section == 'flags' %} <a class="re_expand" href="{{ response.response_url }}"> - <!--div class="re_snippet">{{ response.response_snippet }}</div--> - <div class="re_content">{{ response.response_content }}</div> + <!--div class="re_snippet">{{ response.response_snippet|escape }}</div--> + <div class="re_content">{{ response.response_content|escape }}</div> </a> {% endif %} </div> @@ -200,7 +183,8 @@ poor design of the data or methods on data objects #} search_state = None, css_class = None, tag_css_class = None, - tag_html_tag = 'li' + tag_html_tag = 'li', + truncate_long_tags = False ) -%} <ul {% if id %}id="{{ id }}"{% endif %} @@ -214,29 +198,58 @@ poor design of the data or methods on data objects #} deletable = deletable, is_link = make_links, search_state = search_state, - html_tag = tag_html_tag + html_tag = tag_html_tag, + truncate_long_tag = False )}} {% endfor %} {% endif %} </ul> {%- endmacro -%} -{%- macro user_group(group) -%} - {% if group.group_profile.logo_url %} - <a href="{% url users_by_group group.id, group.name|replace('-', ' ')|slugify %}"> - <img class="group-logo" src="{{group.group_profile.logo_url}}" alt='{% trans name=group.name|escape %}logo for user group "{{name}}"{% endtrans %}' /> - </a> - {% endif %} - <div class="group-name"> - <a +{%- macro user_group(group, membership_info) -%} + <td> + <a class="group-name" href="{% url users_by_group group.id, group.name|replace('-', ' ')|slugify %}" >{{ group.name|escape }}</a> - </div> - <!--div id="group-{{group.id}}-description"> + </td> + <td> + <span class="group-description"> {% if group.tag_wiki %} - {{ group.tag_wiki.html }} + {{ group.tag_wiki.summary }} + {% endif %} + </span> + </td> + <td> + {% if membership_info %} + {{ group_join_button( + group_id = group.id, + can_join = membership_info['can_join'], + is_member = membership_info['is_member'] + ) + }} + {% endif %} + </td> +{%- endmacro -%} + +{%- macro group_join_button(group_id = None, can_join = False, is_member = False) -%} + {% if can_join or is_member %} + <button + class="group-join-btn follow-toggle {% if is_member %}on on-state{% endif %}" + data-group-id="{{group_id}}" + data-off-prompt-text="{% trans %}Leave this group{% endtrans %}" + data-on-prompt-text="{% trans %}Join this group{% endtrans %}" + data-on-state-text="{% trans %}You are a member{% endtrans %}" + data-off-state-text="{% trans %}Join this group{% endtrans %}" + > + {% if is_member %} + {% trans %}You are a member{% endtrans %} + {% else %} + {% if can_join %} + {% trans %}Join this group{% endtrans %} + {% endif %} + {% endif %} + </button> {% endif %} - </div--> {%- endmacro -%} {# todo: remove the extra content argument to make its usage more explicit #} @@ -248,7 +261,8 @@ poor design of the data or methods on data objects #} css_class = None, search_state = None, html_tag = 'div', - extra_content = '' + extra_content = '', + truncate_long_tag = False ) -%} {% if not search_state %} {# get empty SearchState() if there's none; CAUTION: for some reason this doesn't work inside `spaceless` tag below! #} @@ -260,10 +274,15 @@ poor design of the data or methods on data objects #} class="tag tag-right{% if css_class %} {{ css_class }}{% endif %}" {% if is_link %} href="{{ search_state.add_tag(tag).full_url() }}" - title="{% trans %}see questions tagged '{{ tag }}'{% endtrans %}" + title="{% trans tag=tag|escape %}see questions tagged '{{ tag }}'{% endtrans %}" {% endif %} rel="tag" - >{{ tag|replace('*', '✽')|truncate(20,True)}}</{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %}> + data-tag-name="{{ tag|replace('*', '✽')|escape }}" + >{% if truncate_long_tag -%} + {{ tag|replace('*', '✽')|truncate(17, True)|escape }} + {%- else -%} + {{ tag|replace('*', '✽')|escape }} + {%- endif %}</{% if not is_link or tag[-1] == '*' %}span{% else %}a{% endif %}> {% if deletable %} <div class="delete-icon" {% if delete_link_title %} @@ -366,7 +385,7 @@ for the purposes of the AJAX comment editor #} </div> <div class="comment-body"> {{comment.html}} - <a class="author" href="{{comment.author.get_profile_url()}}">{{comment.author.username}}</a> + <a class="author" href="{{comment.author.get_profile_url()}}">{{comment.author.username|escape}}</a> <span class="age"> ({{ timeago(comment.added_at) }})</span> <a id="post-{{comment.id}}-edit" class="edit">{% trans %}edit{% endtrans %}</a> @@ -494,8 +513,8 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ {%- macro follow_toggle(follow, name, alias, id) -%} {# follow - boolean; name - object type name; alias - e.g. users name; id - object id #} <div - class="follow-toggle" - id="follow-{{ name }}-{{ id }}" + class="follow-toggle follow-user-toggle" + id="follow-{{ name|escape }}-{{ id }}" > {% if follow %} <div class="follow">{% trans %}follow {{alias}}{% endtrans %}</div> @@ -510,13 +529,13 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ {%- macro follow_user_toggle(visitor = None, subject = None) -%} {% if visitor.is_anonymous() %} - {{ follow_toggle(True, 'user', subject.username, subject.id) }} + {{ follow_toggle(True, 'user', subject.username|escape, subject.id) }} {% else %} {% if visitor != subject %} {% if visitor.is_following(subject) %} - {{ follow_toggle(False, 'user', subject.username, subject.id) }} + {{ follow_toggle(False, 'user', subject.username|escape, subject.id) }} {% else %} - {{ follow_toggle(True, 'user', subject.username, subject.id) }} + {{ follow_toggle(True, 'user', subject.username|escape, subject.id) }} {% endif %} {% endif %} {% endif %} @@ -536,7 +555,7 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ endtrans %}" title="{% trans country=user.country.name, - person=user.username %}{{person}} is from {{country}}{% + person=user.username|escape %}{{person}} is from {{country}}{% endtrans %}" /> {% endif %} @@ -571,8 +590,8 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ ><img class="gravatar" width="{{size}}" height="{{size}}" src="{{ user.get_avatar_url(size) }}" - title="{{user.username}}" - alt="{% trans username=user.username %}{{username}} gravatar image{% endtrans %}" + title="{{user.username|escape}}" + alt="{% trans username=user.username|escape %}{{username}} gravatar image{% endtrans %}" /></a> {% endspaceless %} {%- endmacro -%} @@ -672,7 +691,7 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ {% if user.new_response_count > 0 or user.seen_response_count > 0 %} <a id='ab-responses' href="{{user.get_absolute_url()}}?sort=inbox§ion=forum"> <img - alt="{% trans username=user.username %}responses for {{username}}{% endtrans %}" + alt="{% trans username=user.username|escape %}responses for {{username}}{% endtrans %}" {% if user.new_response_count > 0 %} src="{{ "/images/mail-envelope-full.png"|media }}" title="{% trans response_count=user.new_response_count %}you have {{response_count}} new response{% pluralize %}you have {{response_count}} new responses{% endtrans %}" diff --git a/askbot/skins/default/templates/main_page/javascript.html b/askbot/skins/default/templates/main_page/javascript.html index d968dcd5..10f8ede2 100644 --- a/askbot/skins/default/templates/main_page/javascript.html +++ b/askbot/skins/default/templates/main_page/javascript.html @@ -27,4 +27,4 @@ {% if request.user.is_authenticated() %} <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> {% endif %} -<script type="text/javascript" src="{{"/js/live_search.js"|media}}"></script> +<script type="text/javascript" src='{{"/js/live_search.js"|media}}'></script> diff --git a/askbot/skins/default/templates/meta/editor_data.html b/askbot/skins/default/templates/meta/editor_data.html index 2363281c..9e243baa 100644 --- a/askbot/skins/default/templates/meta/editor_data.html +++ b/askbot/skins/default/templates/meta/editor_data.html @@ -3,11 +3,11 @@ askbot['settings']['tagsAreRequired'] = {% if settings.TAGS_ARE_REQUIRED %}true{% else %}false{% endif %}; askbot['settings']['maxTagLength'] = {{settings.MAX_TAG_LENGTH}}; - 'each tag must be shorter than %(max_chars)d characters', - askbot['messages']['maxTagLength'] = '{% trans max_chars = settings.MAX_TAG_LENGTH %}each tag must be shorter that {{max_chars}} character{% pluralize %}each tag must be shorter than {{max_chars}} characters{% endtrans %}'; + "each tag must be shorter than %(max_chars)d characters", + askbot['messages']['maxTagLength'] = "{% trans max_chars = settings.MAX_TAG_LENGTH %}each tag must be shorter that {{max_chars}} character{% pluralize %}each tag must be shorter than {{max_chars}} characters{% endtrans %}"; askbot['settings']['maxTagsPerPost'] = {{settings.MAX_TAGS_PER_POST}}; - askbot['messages']['maxTagsPerPost'] = '{% trans tag_count = settings.MAX_TAGS_PER_POST %}please use {{tag_count}} tag{% pluralize %}please use {{tag_count}} tags or less{% endtrans %}'; - askbot['messages']['tagLimits'] = '{% trans tag_count=settings.MAX_TAGS_PER_POST, max_chars=settings.MAX_TAG_LENGTH %}please use up to {{tag_count}} tags, less than {{max_chars}} characters each{% endtrans %}'; + askbot['messages']['maxTagsPerPost'] = "{% trans tag_count = settings.MAX_TAGS_PER_POST %}please use {{tag_count}} tag{% pluralize %}please use {{tag_count}} tags or less{% endtrans %}"; + askbot['messages']['tagLimits'] = "{% trans tag_count=settings.MAX_TAGS_PER_POST, max_chars=settings.MAX_TAG_LENGTH %}please use up to {{tag_count}} tags, less than {{max_chars}} characters each{% endtrans %}"; askbot['urls']['upload'] = '{% url "upload" %}'; askbot['settings']['minTitleLength'] = {{settings.MIN_TITLE_LENGTH}} askbot['settings']['minQuestionBodyLength'] = {{settings.MIN_QUESTION_BODY_LENGTH}} diff --git a/askbot/skins/default/templates/meta/html_head_stylesheets.html b/askbot/skins/default/templates/meta/html_head_stylesheets.html index 0d2ba463..8977f152 100644 --- a/askbot/skins/default/templates/meta/html_head_stylesheets.html +++ b/askbot/skins/default/templates/meta/html_head_stylesheets.html @@ -1,4 +1,5 @@ {% if settings.ASKBOT_CSS_DEVEL == False %} + <link href="{{"/style/style.css"|media }}" rel="stylesheet" type="text/css" /> {% else %} <link href="{{"/style/style.less"|media }}" rel="stylesheet/less" type="text/css" /> @@ -7,7 +8,7 @@ {% if settings.USE_LOCAL_FONTS %} {% include "meta/fonts.html" %} {% else %} - <link href='http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700' rel='stylesheet' type='text/css'> + <link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:400,700&subset=latin,cyrillic-ext,latin-ext' rel='stylesheet' type='text/css'> {% endif %} {{ skin.get_extra_css_link() }} {% if settings.USE_CUSTOM_CSS %} diff --git a/askbot/skins/default/templates/question.html b/askbot/skins/default/templates/question.html index f22796db..bb74db33 100644 --- a/askbot/skins/default/templates/question.html +++ b/askbot/skins/default/templates/question.html @@ -130,6 +130,7 @@ var add_answer_btn = document.getElementById('add-answer-btn'); if (askbot['data']['userIsAuthenticated']){ if (askbot['data']['userId'] == {{question.author_id}}){ + add_answer_btn.className += ' answer-own-question'; add_answer_btn.setAttribute( 'value', '{% trans %}Answer Your Own Question{% endtrans %}' @@ -156,6 +157,9 @@ </script> {% endblock %} {% block content %} + <div> + {{ settings.QUESTION_PAGE_TOP_BANNER }} + </div> {% if is_cacheable %} {% cache long_time "thread-content-html" thread.id %} {% include "question/content.html" %} diff --git a/askbot/skins/default/templates/question/content.html b/askbot/skins/default/templates/question/content.html index 738738dd..906b1953 100644 --- a/askbot/skins/default/templates/question/content.html +++ b/askbot/skins/default/templates/question/content.html @@ -3,11 +3,6 @@ {# ==== BEGIN: question/question_card.html ==== #} {% include "question/question_card.html" %} {# ==== END: question/question_card.html ==== #} -{% if thread.closed %} - {# ==== START: question/closed_question_info.html ==== #} - {% include "question/closed_question_info.html" %} - {# ==== END: question/closed_question_info.html ==== #} -{% endif %} {% if answers %} <div class="clean"></div> @@ -37,5 +32,5 @@ {% include "question/new_answer_form.html" %} {# ==== END: question/new_answer_form.html ==== #} {% if request.user == question.author %}{# this is outside the form on purpose #} - <input type="button" class="submit after-editor" id="fmanswer_button" value="{% trans %}Answer Your Own Question{% endtrans %}"/> + <input type="button" class="submit after-editor answer-own-question" id="fmanswer_button" value="{% trans %}Answer Your Own Question{% endtrans %}"/> {%endif%} diff --git a/askbot/skins/default/templates/question/question_card.html b/askbot/skins/default/templates/question/question_card.html index dd52ea0f..c787bf34 100644 --- a/askbot/skins/default/templates/question/question_card.html +++ b/askbot/skins/default/templates/question/question_card.html @@ -25,6 +25,12 @@ } })(); </script> + {% if thread.closed %} + <div class="clearfix"></div> + {# ==== START: question/closed_question_info.html ==== #} + {% include "question/closed_question_info.html" %} + {# ==== END: question/closed_question_info.html ==== #} + {% endif %} {% include "question/question_comments.html" %} </div> diff --git a/askbot/skins/default/templates/question/sidebar.html b/askbot/skins/default/templates/question/sidebar.html index 620268e1..2450a384 100644 --- a/askbot/skins/default/templates/question/sidebar.html +++ b/askbot/skins/default/templates/question/sidebar.html @@ -1,7 +1,9 @@ {% from "macros.html" import timeago %} +{% if settings.SIDEBAR_QUESTION_HEADER %} <div class="box"> {{ settings.SIDEBAR_QUESTION_HEADER }} </div> +{% endif %} <div class="box vote-buttons"> <h2 >{% trans %}Question tools{% endtrans %}</h2> {% if favorited %} @@ -45,7 +47,7 @@ <div class="clearfix"></div> <h2>{% trans %}Stats{% endtrans %}</h2> <p> - {% trans %}Asked{% endtrans %}: {{ timeago(question.added_at) }} + {% trans %}Asked{% endtrans %}: <strong>{{ timeago(question.added_at) }}</strong> </p> <p> {% trans %}Seen{% endtrans %}: <strong>{{ thread.view_count|intcomma }} {% trans %}times{% endtrans %}</strong> diff --git a/askbot/skins/default/templates/reopen.html b/askbot/skins/default/templates/reopen.html index 894fa3a0..4ddd6f31 100644 --- a/askbot/skins/default/templates/reopen.html +++ b/askbot/skins/default/templates/reopen.html @@ -9,8 +9,8 @@ <span class="big">{{ question.get_question_title()|escape }}</span> </a> </p> -<p>{% trans %}This question has been closed by - <a href="{{closed_by_profile_url}}">{{closed_by_username}}</a> +<p>{% trans username = closed_by_username|escape %}This question has been closed by + <a href="{{closed_by_profile_url}}">{{username}}</a> {% endtrans %} </p> <p> diff --git a/askbot/skins/default/templates/tags.html b/askbot/skins/default/templates/tags.html index 6b89bdba..007388af 100644 --- a/askbot/skins/default/templates/tags.html +++ b/askbot/skins/default/templates/tags.html @@ -40,6 +40,7 @@ {{ macros.tag_widget( tag = tag.name, html_tag = 'div', + truncate_long_tag = True, extra_content = '<span class="tag-number">× ' ~ tag.used_count|intcomma ~ '</span>' ) diff --git a/askbot/skins/default/templates/user_profile/macros.html b/askbot/skins/default/templates/user_profile/macros.html new file mode 100644 index 00000000..ac573553 --- /dev/null +++ b/askbot/skins/default/templates/user_profile/macros.html @@ -0,0 +1,24 @@ +{% import "macros.html" as macros %} + +{% macro tag_selection(tag_names, selection_type) %} + <a name="{{selection_type}}-tags"></a> + {% spaceless %} + <h2>{% + trans counter=tag_names|length, type=gettext(selection_type)|capitalize + %}<span class="count">{{counter}}</span> {{type}} Tag{% + pluralize + %}<span class="count">{{counter}}</span> {{type}} Tags{% + endtrans + %} + </h2> + {% endspaceless %} + <div class="user-stats-table"> + <table class="tags"> + <tr> + <td valign="top"> + {{ macros.tag_list_widget(tag_names, deletable = False) }} + </td> + </tr> + </table> + </div> +{% endmacro %} diff --git a/askbot/skins/default/templates/user_profile/user.html b/askbot/skins/default/templates/user_profile/user.html index dfe9b787..2f06a3c9 100644 --- a/askbot/skins/default/templates/user_profile/user.html +++ b/askbot/skins/default/templates/user_profile/user.html @@ -9,7 +9,7 @@ {% block content %} <h1 class="section-title"> {% spaceless %} - {% trans username=view_user.username %}{{username}}'s profile{% endtrans %} - {% block profilesection %}{% endblock %} + {% trans username=view_user.username|escape %}{{username}}'s profile{% endtrans %} - {% block profilesection %}{% endblock %} {% endspaceless %} </h1> {% include "user_profile/user_tabs.html" %} @@ -21,7 +21,7 @@ {% block endjs %} <script type="text/javascript"> var viewUserID = {{view_user.id}}; - askbot['data']['viewUserName'] = '{{ view_user.username }}'; + askbot['data']['viewUserName'] = '{{ view_user.username|escape }}'; askbot['data']['viewUserId'] = {{view_user.id}}; askbot['urls']['edit_group_membership'] = '{% url edit_group_membership %}'; askbot['urls']['get_groups_list'] = '{% url get_groups_list %}'; @@ -30,6 +30,8 @@ <script type='text/javascript' src='{{"/js/jquery.form.js"|media}}'></script> {% endif %} <script type="text/javascript" src='{{"/js/user.js"|media}}'></script> + <script type='text/javascript' src='{{"/js/jquery.validate.min.js"|media}}'></script> + <script type="text/javascript" src='{{"/js/post.js"|media}}'></script> {% block userjs %} {% endblock %} {% endblock %} diff --git a/askbot/skins/default/templates/user_profile/user_edit.html b/askbot/skins/default/templates/user_profile/user_edit.html index 7735ba93..c95bf815 100644 --- a/askbot/skins/default/templates/user_profile/user_edit.html +++ b/askbot/skins/default/templates/user_profile/user_edit.html @@ -4,7 +4,7 @@ {% block title %}{% spaceless %}{% trans %}Edit user profile{% endtrans %}{% endspaceless %}{% endblock %} {% block content %} <h1 class="section-title"> - {{ request.user.username }} - {% trans %}edit profile{% endtrans %} + {{ request.user.username|escape }} - {% trans %}edit profile{% endtrans %} </h1> <div id="main-body" style="width:100%;padding-top:10px"> <form name="" action="{% url edit_user request.user.id %}" method="post">{% csrf_token %} @@ -42,7 +42,7 @@ {{ form.username }} <span class="form-error"> {{ form.username.errors }} </span></td> {% else %} - {{ view_user.username }} + {{ view_user.username|escape }} {% endif %} </td> </tr> @@ -96,6 +96,14 @@ <td style="vertical-align:top">{{ form.about.label_tag() }}:</td> <td>{{ form.about }} <span class="form-error"> {{ form.about.errors }} </span></td> </tr> + {% if marked_tags_setting == 'when-user-wants' %} + <tr> + <td>{{ form.show_marked_tags.label_tag() }}:</td> + <td>{{ form.show_marked_tags }} + <span class="form-error">{{ form.show_marked_tags.errors }}</span> + </td> + </tr> + {% endif %} </table> <div style="margin:30px 0 60px 0"> <input type="submit" value="{% trans %}Update{% endtrans %}" class="submit" > diff --git a/askbot/skins/default/templates/user_profile/user_moderate.html b/askbot/skins/default/templates/user_profile/user_moderate.html index 347ec3af..a7f05b1c 100644 --- a/askbot/skins/default/templates/user_profile/user_moderate.html +++ b/askbot/skins/default/templates/user_profile/user_moderate.html @@ -5,7 +5,7 @@ {% endblock %} {% block usercontent %} {% if request.user != view_user %} - <h3>{% trans username=view_user.username, status=view_user.get_status_display() %}{{username}}'s current status is "{{status}}"{% endtrans %} + <h3>{% trans username=view_user.username|escape, status=view_user.get_status_display() %}{{username}}'s current status is "{{status}}"{% endtrans %} </h3> {% if user_status_changed %} <p class="action-status"><span>{% trans %}User status changed{% endtrans %}</span></p> @@ -40,7 +40,7 @@ </form> {% if request.user != view_user %} <hr/> -<h3>{% trans username=view_user.username %}Send message to {{username}}{% endtrans %}</h3> +<h3>{% trans username=view_user.username|escape %}Send message to {{username}}{% endtrans %}</h3> <p>{% trans %}An email will be sent to the user with 'reply-to' field set to your email address. Please make sure that your address is entered correctly.{% endtrans %}</p> {% if message_sent %} <p class="action-status"><span>{% trans %}Message sent{% endtrans %}</span></p> diff --git a/askbot/skins/default/templates/user_profile/user_network.html b/askbot/skins/default/templates/user_profile/user_network.html index e6134e0c..f64d95b0 100644 --- a/askbot/skins/default/templates/user_profile/user_network.html +++ b/askbot/skins/default/templates/user_profile/user_network.html @@ -32,7 +32,7 @@ {% if request.user == view_user %} <p>{% trans %}Your network is empty. Would you like to follow someone? - Just visit their profiles and click "follow"{% endtrans %}</p> {% else %} - <p>{% trans username = view_user.username %}{{username}}'s network is empty{% endtrans %}</p> + <p>{% trans username = view_user.username|escape %}{{username}}'s network is empty{% endtrans %}</p> {% endif %} {% endif %} {% endblock %} diff --git a/askbot/skins/default/templates/user_profile/user_reputation.html b/askbot/skins/default/templates/user_profile/user_reputation.html index 1bb9b1ba..1cdf014a 100644 --- a/askbot/skins/default/templates/user_profile/user_reputation.html +++ b/askbot/skins/default/templates/user_profile/user_reputation.html @@ -11,7 +11,7 @@ {% if view_user.id == user.id %} <h2>{% trans %}Your karma change log.{% endtrans %}</h2> {% else %} - <h2>{% trans user_name=view_user.username %}{{user_name}}'s karma change log{% endtrans %}</h2> + <h2>{% trans user_name=view_user.username|escape %}{{user_name}}'s karma change log{% endtrans %}</h2> {% endif %} {% for rep in reputation %} <p> diff --git a/askbot/skins/default/templates/user_profile/user_stats.html b/askbot/skins/default/templates/user_profile/user_stats.html index 43b7f4fa..2ccc277f 100644 --- a/askbot/skins/default/templates/user_profile/user_stats.html +++ b/askbot/skins/default/templates/user_profile/user_stats.html @@ -1,5 +1,6 @@ {% extends "user_profile/user.html" %} {% import "macros.html" as macros %} +{% import "user_profile/macros.html" as user_profile_macros %} <!-- user_stats.html --> {% block profilesection %} {% trans %}overview{% endtrans %} @@ -9,18 +10,16 @@ {% if settings.GROUPS_ENABLED %} <div id="user-groups"> <h2>{% trans - username = view_user.username + username = view_user.username|escape %}{{username}}'s groups{% endtrans %} </h2> - <ul id="groups-list"> - {% if user_groups %} + <table id="groups-list"> {% for group in user_groups %} - <li> - {{ macros.user_group(group) }} - </li> + <tr> + {{ macros.user_group(group, groups_membership_info[group.id]) }} + </tr> {% endfor %} - {% endif %} - </ul> + </table> <div class="clearfix"></div> <a id="add-group">{% trans %}add group{% endtrans %}</a> </div> @@ -92,6 +91,7 @@ tag.name, html_tag = 'div', search_state = search_state, + truncate_long_tag = True, extra_content = '<span class="tag-number">× ' ~ tag.user_tag_usage_count|intcomma ~ @@ -111,6 +111,15 @@ </tr> </table> </div> + {% if interesting_tag_names %} + {{ user_profile_macros.tag_selection(interesting_tag_names, 'interesting') }} + {% endif %} + {% if ignored_tag_names %} + {{ user_profile_macros.tag_selection(ignored_tag_names, 'ignored') }} + {% endif %} + {% if subscribed_tag_names %} + {{ user_profile_macros.tag_selection(subscribed_tag_names, 'subscribed') }} + {% endif %} {% if settings.BADGES_MODE == 'public' %} <a name="badges"></a> {% spaceless %} @@ -137,7 +146,7 @@ <a title="{{ award.content_object.get_snippet()|collapse }}" href="{{ award.content_object.get_absolute_url() }}" - >{% if award.content_type.post_type == 'answer' %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title }}</a> + >{% if award.content_type.post_type == 'answer' %}{% trans %}Answer to:{% endtrans %}{% endif %} {{ award.content_object.thread.title|escape }}</a> </li> {% endif %} {% endfor %} @@ -156,9 +165,18 @@ {% block endjs %} {{ super() }} <script type="text/javascript"> + askbot['urls']['join_or_leave_group'] = '{% url join_or_leave_group %}'; $(document).ready(function(){ setup_badge_details_toggle(); + $.each($('.group-join-btn'), function(idx, elem){ + var group_join_btn = new GroupJoinButton(); + group_join_btn.decorate($(elem)); + }); }); </script> + <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> + <script type="text/javascript"> + askbot['urls']['questions'] = '{% url "questions" %}'; + </script> {% endblock %} <!-- end user_stats.html --> diff --git a/askbot/skins/default/templates/users.html b/askbot/skins/default/templates/users.html index e80e0aec..5b963399 100644 --- a/askbot/skins/default/templates/users.html +++ b/askbot/skins/default/templates/users.html @@ -52,8 +52,9 @@ </div> <div class="clearfix"></div> </div> -{% if suser %} - <p>{% trans %}users matching query {{suser}}:{% endtrans %}</p> +<div class="clean"></div> +{% if search_query %} + <p>{% trans %}users matching query {{search_query}}:{% endtrans %}</p> {% endif %} {% if not users.object_list %} <p><span>{% trans %}Nothing found.{% endtrans %}</span></p> @@ -97,45 +98,45 @@ {% else %} var codeFriendlyMarkdown = false; {% endif %} - $().ready(function(){ - {% if group and request.user.is_authenticated() %} - var group_join_btn = new GroupJoinButton({{ group.id }}); + {% if group and request.user.is_authenticated() %} + $().ready(function(){ + var group_join_btn = new GroupJoinButton(); group_join_btn.decorate($('.group-join-btn')); - {% endif %} - //setup WMD editor - if (askbot['data']['userIsAdminOrMod'] === true){ - //todo: this is kind of Attacklab.init ... should not be here - Attacklab.wmd = function(){ - Attacklab.loadEnv = function(){ - var mergeEnv = function(env){ - if(!env){ - return; - } - - for(var key in env){ - Attacklab.wmd_env[key] = env[key]; - } + //setup WMD editor + if (askbot['data']['userIsAdminOrMod'] === true){ + //todo: this is kind of Attacklab.init ... should not be here + Attacklab.wmd = function(){ + Attacklab.loadEnv = function(){ + var mergeEnv = function(env){ + if(!env){ + return; + } + + for(var key in env){ + Attacklab.wmd_env[key] = env[key]; + } + }; + + mergeEnv(Attacklab.wmd_defaults); + mergeEnv(Attacklab.account_options); + mergeEnv(top["wmd_options"]); + Attacklab.full = true; + + var defaultButtons = "bold italic link blockquote code image ol ul heading hr"; + Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons; }; - - mergeEnv(Attacklab.wmd_defaults); - mergeEnv(Attacklab.account_options); - mergeEnv(top["wmd_options"]); - Attacklab.full = true; - - var defaultButtons = "bold italic link blockquote code image ol ul heading hr"; - Attacklab.wmd_env.buttons = Attacklab.wmd_env.buttons || defaultButtons; + Attacklab.loadEnv(); }; - Attacklab.loadEnv(); - }; - Attacklab.wmd(); - Attacklab.wmdBase(); - var group_editor = new UserGroupProfileEditor(); - group_editor.decorate($('#group-wiki-{{group.id}}')); - } - Hilite.exact = false; - Hilite.elementid = "main-body"; - Hilite.debug_referrer = location.href; - }); + Attacklab.wmd(); + Attacklab.wmdBase(); + var group_editor = new UserGroupProfileEditor(); + group_editor.decorate($('#group-wiki-{{group.id}}')); + } + Hilite.exact = false; + Hilite.elementid = "main-body"; + Hilite.debug_referrer = location.href; + }); + {% endif %} </script> {% endblock %} <!-- end users.html --> diff --git a/askbot/skins/default/templates/widgets/group_info.html b/askbot/skins/default/templates/widgets/group_info.html index 601930af..5d3a4c7f 100644 --- a/askbot/skins/default/templates/widgets/group_info.html +++ b/askbot/skins/default/templates/widgets/group_info.html @@ -1,3 +1,4 @@ +{% import "macros.html" as macros %} <div id="group-wiki-{{group.id}}" class="box group-wiki"> <h2>{% trans %}Group info{% endtrans %}</h2> <img class="group-logo" @@ -13,23 +14,12 @@ {% endif %} </div> <div class="clearfix"></div> - {% if user_can_join_group or user_is_group_member %} - <button - class="group-join-btn follow-toggle {% if user_is_group_member %}on on-state{% endif %}" - data-off-prompt-text="{% trans %}Leave this group{% endtrans %}" - data-on-prompt-text="{% trans %}Join this group{% endtrans %}" - data-on-state-text="{% trans %}You are a member{% endtrans %}" - data-off-state-text="{% trans %}Join this group{% endtrans %}" - > - {% if user_is_group_member %} - {% trans %}You are a member{% endtrans %} - {% else %} - {% if user_can_join_group %} - {% trans %}Join this group{% endtrans %} - {% endif %} - {% endif %} - </button> - {% endif %} + {{ macros.group_join_button( + group_id = group.id, + can_join = user_can_join_group, + is_member = user_is_group_member + ) + }} {% if request.user.is_authenticated() and request.user.is_administrator() %} <div class="controls"> <a class="edit-description" diff --git a/askbot/skins/default/templates/widgets/question_summary.html b/askbot/skins/default/templates/widgets/question_summary.html index c6e7bc5d..5fd51e08 100644 --- a/askbot/skins/default/templates/widgets/question_summary.html +++ b/askbot/skins/default/templates/widgets/question_summary.html @@ -46,7 +46,7 @@ {% if question.is_anonymous %} <span class="anonymous">{{ thread.last_activity_by.get_anonymous_name() }}</span> {% else %} - <a href="{% url user_profile thread.last_activity_by.id, thread.last_activity_by.username|slugify %}">{{thread.last_activity_by.username}}</a> {{ user_country_flag(thread.last_activity_by) }} + <a href="{% url user_profile thread.last_activity_by.id, thread.last_activity_by.username|slugify %}">{{thread.last_activity_by.username|escape}}</a> {{ user_country_flag(thread.last_activity_by) }} {#{user_score_and_badge_summary(thread.last_activity_by)}#} {% endif %} </div> diff --git a/askbot/skins/default/templates/widgets/user_list.html b/askbot/skins/default/templates/widgets/user_list.html index 11f2ed50..e51abc5b 100644 --- a/askbot/skins/default/templates/widgets/user_list.html +++ b/askbot/skins/default/templates/widgets/user_list.html @@ -7,7 +7,7 @@ <div class="user"> <ul> <li class="thumb">{{ gravatar(user, 32) }}</li> - <li><a href="{% url user_profile user.id, user.username|slugify %}{% if profile_section %}?sort={{profile_section}}{% endif %}">{{user.username}}</a>{{ user_country_flag(user) }}</li> + <li><a href="{% url user_profile user.id, user.username|slugify %}{% if profile_section %}?sort={{profile_section}}{% endif %}">{{user.username|escape}}</a>{{ user_country_flag(user) }}</li> <li>{{ user_score_and_badge_summary( user, diff --git a/askbot/skins/default/templates/widgets/user_navigation.html b/askbot/skins/default/templates/widgets/user_navigation.html index eec7e628..717cd7ee 100644 --- a/askbot/skins/default/templates/widgets/user_navigation.html +++ b/askbot/skins/default/templates/widgets/user_navigation.html @@ -1,5 +1,5 @@ {%- if request.user.is_authenticated() -%} - <a href="{{ request.user.get_absolute_url() }}">{{ request.user.username }}</a> + <a href="{{ request.user.get_absolute_url() }}">{{ request.user.username|escape }}</a> <span class="user-info"> {{ macros.inbox_link(request.user) }} {{ macros.moderation_items_link(request.user, moderation_items) }} diff --git a/askbot/skins/loaders.py b/askbot/skins/loaders.py index 24559512..aa3188e9 100644 --- a/askbot/skins/loaders.py +++ b/askbot/skins/loaders.py @@ -129,4 +129,3 @@ def render_text_into_skin(text, data, request): skin = get_skin(request) template = skin.from_string(text) return template.render(context) - diff --git a/askbot/tasks.py b/askbot/tasks.py index e5ba143d..da07477b 100644 --- a/askbot/tasks.py +++ b/askbot/tasks.py @@ -21,9 +21,13 @@ import sys import traceback from django.contrib.contenttypes.models import ContentType +from django.template import Context +from django.utils.translation import ugettext as _ from celery.decorators import task from askbot.conf import settings as askbot_settings -from askbot.models import Activity, Post, Thread, User +from askbot import const +from askbot import mail +from askbot.models import Activity, Post, Thread, User, ReplyAddress from askbot.models import send_instant_notifications_about_activity_in_post from askbot.models.badges import award_badges_signal @@ -32,6 +36,62 @@ from askbot.models.badges import award_badges_signal # (i.e. if Celery tasks are not deferred but executed straight away) @task(ignore_result = True) +def notify_author_of_published_revision_celery_task(revision): + #todo: move this to ``askbot.mail`` module + #for answerable email only for now, because + #we don't yet have the template for the read-only notification + if askbot_settings.REPLY_BY_EMAIL: + #generate two reply codes (one for edit and one for addition) + #to format an answerable email or not answerable email + reply_options = { + 'user': revision.author, + 'post': revision.post, + 'reply_action': 'append_content' + } + append_content_address = ReplyAddress.objects.create_new( + **reply_options + ).as_email_address() + reply_options['reply_action'] = 'replace_content' + replace_content_address = ReplyAddress.objects.create_new( + **reply_options + ).as_email_address() + + #populate template context variables + reply_code = append_content_address + ',' + replace_content_address + if revision.post.post_type == 'question': + mailto_link_subject = revision.post.thread.title + else: + mailto_link_subject = _('An edit for my answer') + #todo: possibly add more mailto thread headers to organize messages + + prompt = _('To add to your post EDIT ABOVE THIS LINE') + reply_separator_line = const.SIMPLE_REPLY_SEPARATOR_TEMPLATE % prompt + data = { + 'site_name': askbot_settings.APP_SHORT_NAME, + 'post': revision.post, + 'author_email_signature': revision.author.email_signature, + 'replace_content_address': replace_content_address, + 'reply_separator_line': reply_separator_line, + 'mailto_link_subject': mailto_link_subject, + 'reply_code': reply_code + } + + #load the template + from askbot.skins.loaders import get_template + template = get_template('email/notify_author_about_approved_post.html') + #todo: possibly add headers to organize messages in threads + headers = {'Reply-To': append_content_address} + #send the message + mail.send_mail( + subject_line = _('Your post at %(site_name)s is now published') % data, + body_text = template.render(Context(data)), + recipient_list = [revision.author.email,], + related_object = revision, + activity_type = const.TYPE_ACTIVITY_EMAIL_UPDATE_SENT, + headers = headers + ) + +@task(ignore_result = True) def record_post_update_celery_task( post_id, post_content_type_id, @@ -156,6 +216,7 @@ def record_post_update( post = post, recipients = notification_subscribers, ) + @task(ignore_result = True) def record_question_visit( diff --git a/askbot/templatetags/extra_filters_jinja.py b/askbot/templatetags/extra_filters_jinja.py index b03e4a89..3643e3c9 100644 --- a/askbot/templatetags/extra_filters_jinja.py +++ b/askbot/templatetags/extra_filters_jinja.py @@ -47,6 +47,13 @@ def add_tz_offset(datetime_object): return str(datetime_object) + ' ' + TIMEZONE_STR @register.filter +def safe_urlquote(text, quote_plus = False): + if quote_plus: + return urllib.quote_plus(text.encode('utf8')) + else: + return urllib.quote(text.encode('utf8')) + +@register.filter def strip_path(url): """removes path part of the url""" return url_utils.strip_path(url) diff --git a/askbot/tests/db_api_tests.py b/askbot/tests/db_api_tests.py index 1f3d3b9b..3a0c9582 100644 --- a/askbot/tests/db_api_tests.py +++ b/askbot/tests/db_api_tests.py @@ -15,6 +15,9 @@ from askbot.conf import settings as askbot_settings import datetime class DBApiTests(AskbotTestCase): + """tests methods on User object, + that were added for askbot + """ def setUp(self): self.create_user() @@ -161,7 +164,8 @@ class DBApiTests(AskbotTestCase): matches = models.Post.objects.get_questions().get_by_text_query("database'") self.assertTrue(len(matches) == 1) -class UserLikeTests(AskbotTestCase): +class UserLikeTagTests(AskbotTestCase): + """tests for user liking and disliking tags""" def setUp(self): self.create_user() self.question = self.post_question(tags = 'one two three') @@ -387,3 +391,14 @@ class CommentTests(AskbotTestCase): self.other_user.upvote(comment, cancel = True) comment = models.Post.objects.get_comments().get(id = self.comment.id) self.assertEquals(comment.score, 0) + +class TagAndGroupTests(AskbotTestCase): + def setUp(self): + self.u1 = self.create_user('u1') + + def test_group_cannot_create_case_variant_tag(self): + self.post_question(user = self.u1, tags = 'one two three') + models.Tag.group_tags.get_or_create(user = self.u1, group_name = 'One') + tag_one = models.Tag.objects.filter(name__iexact = 'one') + self.assertEqual(tag_one.count(), 1) + self.assertEqual(tag_one[0].name, 'one') diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py index abdb1c58..9ec1a412 100644 --- a/askbot/tests/email_alert_tests.py +++ b/askbot/tests/email_alert_tests.py @@ -11,7 +11,7 @@ from django.test import TestCase from django.test.client import Client from askbot.tests import utils from askbot import models -from askbot.utils import mail +from askbot import mail from askbot.conf import settings as askbot_settings from askbot import const from askbot.models.question import Thread @@ -949,3 +949,67 @@ class EmailFeedSettingTests(utils.AskbotTestCase): new_user.add_missing_askbot_subscriptions() data_after = TO_JSON(self.get_user_feeds()) self.assertEquals(data_before, data_after) + +class PostApprovalTests(utils.AskbotTestCase): + """test notifications sent to authors when their posts + are approved or published""" + def setUp(self): + self.reply_by_email = askbot_settings.REPLY_BY_EMAIL + askbot_settings.update('REPLY_BY_EMAIL', True) + self.enable_content_moderation = \ + askbot_settings.ENABLE_CONTENT_MODERATION + askbot_settings.update('ENABLE_CONTENT_MODERATION', True) + self.self_notify_when = \ + askbot_settings.SELF_NOTIFY_EMAILED_POST_AUTHOR_WHEN + when = const.FOR_FIRST_REVISION + askbot_settings.update('SELF_NOTIFY_EMAILED_POST_AUTHOR_WHEN', when) + assert( + django_settings.EMAIL_BACKEND == 'django.core.mail.backends.locmem.EmailBackend' + ) + + def tearDown(self): + askbot_settings.update( + 'REPLY_BY_EMAIL', self.reply_by_email + ) + askbot_settings.update( + 'ENABLE_CONTENT_MODERATION', + self.enable_content_moderation + ) + askbot_settings.update( + 'SELF_NOTIFY_EMAILED_POST_AUTHOR_WHEN', + self.self_notify_when + ) + + def test_emailed_question_answerable_approval_notification(self): + self.u1 = self.create_user('user1', status = 'a')#regular user + question = self.post_question(user = self.u1, by_email = True) + outbox = django.core.mail.outbox + #here we should get just the notification of the post + #being placed on the moderation queue + self.assertEquals(len(outbox), 1) + self.assertEquals(outbox[0].recipients(), [self.u1.email]) + + def test_moderated_question_answerable_approval_notification(self): + u1 = self.create_user('user1', status = 'a') + question = self.post_question(user = u1, by_email = True) + + self.assertEquals(question.approved, False) + + u2 = self.create_user('admin', status = 'd') + + self.assertEquals(question.revisions.count(), 1) + u2.approve_post_revision(question.get_latest_revision()) + + outbox = django.core.mail.outbox + self.assertEquals(len(outbox), 2) + #moderation notification + self.assertEquals(outbox[0].recipients(), [u1.email,]) + self.assertEquals(outbox[1].recipients(), [u1.email,])#approval + + +class MailMessagesTests(utils.AskbotTestCase): + def test_ask_for_signature(self): + from askbot.mail import messages + user = self.create_user('user') + message = messages.ask_for_signature(user, footer_code = 'nothing') + self.assertTrue(user.username in message) diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py index ebfba0c3..e3e699a7 100644 --- a/askbot/tests/page_load_tests.py +++ b/askbot/tests/page_load_tests.py @@ -67,7 +67,9 @@ class PageLoadTestCase(AskbotTestCase): def setUp(self): self.old_cache = cache.cache - cache.cache = DummyCache('', {}) # Disable caching (to not interfere with production cache, not sure if that's possible but let's not risk it) + #Disable caching (to not interfere with production cache, + #not sure if that's possible but let's not risk it) + cache.cache = DummyCache('', {}) def tearDown(self): cache.cache = self.old_cache # Restore caching @@ -610,3 +612,18 @@ class QuestionPageRedirectTests(AskbotTestCase): #point to a non-existing comment resp = self.client.get(url, data={'comment': 100301}) self.assertRedirects(resp, expected_url = self.q.get_absolute_url()) + +class CommandViewTests(AskbotTestCase): + def test_get_tag_wiki_text_succeeds(self): + tag1 = self.create_tag('tag1') + response = self.client.get( + reverse('load_tag_wiki_text'), + data = {'tag_id': tag1.id} + ) + self.assertEqual(response.status_code, 200) + + def test_get_tag_wiki_text_fails(self): + tag1 = self.create_tag('tag1') + response = self.client.get(reverse('load_tag_wiki_text')) + self.assertEqual(response.status_code, 400)#bad request + diff --git a/askbot/tests/post_model_tests.py b/askbot/tests/post_model_tests.py index dd1399c1..9a4d47c8 100644 --- a/askbot/tests/post_model_tests.py +++ b/askbot/tests/post_model_tests.py @@ -344,7 +344,7 @@ class ThreadRenderLowLevelCachingTests(AskbotTestCase): # Make sure that title and body text are escaped properly. # This should be obvious at this point, if the above test passes, but why not be explicit # UPDATE: And voila, these tests catched double-escaping bug in template, where `<` was `&lt;` - # And indeed, post.summary is escaped before saving, in parse_and_save_post() + # And indeed, post.summary is escaped before saving, in parse_and_save() # UPDATE 2:Weird things happen with question summary (it's double escaped etc., really weird) so # let's just make sure that there are no tag placeholders left self.assertTrue('<<<tag1>>> fake title' in proper_html) diff --git a/askbot/tests/reply_by_email_tests.py b/askbot/tests/reply_by_email_tests.py index a1272b0d..698662fc 100644 --- a/askbot/tests/reply_by_email_tests.py +++ b/askbot/tests/reply_by_email_tests.py @@ -1,6 +1,6 @@ from django.utils.translation import ugettext as _ from askbot.models import ReplyAddress -from askbot.lamson_handlers import PROCESS +from askbot.mail.lamson_handlers import PROCESS, VALIDATE_EMAIL, get_parts from askbot import const @@ -8,25 +8,32 @@ from askbot.tests.utils import AskbotTestCase from askbot.models import Post, PostRevision TEST_CONTENT = 'Test content' -TEST_EMAIL_PARTS = ( - ('body', TEST_CONTENT), -) TEST_LONG_CONTENT = 'Test content' * 10 -TEST_LONG_EMAIL_PARTS = ( - ('body', TEST_LONG_CONTENT), -) class MockPart(object): def __init__(self, body): self.body = body self.content_encoding = {'Content-Type':('text/plain',)} -class MockMessage(object): +class MockMessage(dict): - def __init__(self, body, from_email): - self._body = body - self._part = MockPart(body) + def __init__( + self, content, from_email, signature = '', response_code = False + ): self.From= from_email + self['Subject'] = 'test subject' + + if response_code != False: + #in this case we modify the content + re_separator = const.REPLY_SEPARATOR_TEMPLATE % { + 'user_action': 'john did something', + 'instruction': 'reply above this line' + } + content += '\n\n\nToday someone wrote:\n' + re_separator + \ + '\nblah blah\n' + response_code + '\n' + signature + + self._body = content + self._part = MockPart(content) def body(self): return self._body @@ -35,7 +42,7 @@ class MockMessage(object): """todo: add real file attachment""" return [self._part] -class EmailProcessingTests(AskbotTestCase): +class ReplyAddressModelTests(AskbotTestCase): def setUp(self): self.u1 = self.create_user(username='user1') @@ -61,7 +68,10 @@ class EmailProcessingTests(AskbotTestCase): self.comment = self.post_comment(user = self.u2, parent_post = self.answer) def test_process_correct_answer_comment(self): - addr = ReplyAddress.objects.create_new( self.answer, self.u1).address + addr = ReplyAddress.objects.create_new( + post = self.answer, + user = self.u1 + ).address reply_separator = const.REPLY_SEPARATOR_TEMPLATE % { 'user_action': 'john did something', 'instruction': 'reply above this line' @@ -71,64 +81,108 @@ class EmailProcessingTests(AskbotTestCase): "wrote something \n\n%s\nlorem ipsum " % (reply_separator), "user1@domain.com" ) - PROCESS(msg, addr, '') + msg['Subject'] = 'test subject' + PROCESS(msg, address = addr) self.assertEquals(self.answer.comments.count(), 2) self.assertEquals(self.answer.comments.all().order_by('-pk')[0].text.strip(), "This is a test reply") - - -class ReplyAddressModelTests(AskbotTestCase): - - def setUp(self): - self.u1 = self.create_user(username='user1') - self.u1.set_status('a') - self.u1.moderate_user_reputation(self.u1, reputation_change = 100, comment= "no comment") - self.u2 = self.create_user(username='user2') - self.u1.moderate_user_reputation(self.u2, reputation_change = 100, comment= "no comment") - self.u3 = self.create_user(username='user3') - self.u1.moderate_user_reputation(self.u3, reputation_change = 100, comment= "no comment") - - self.question = self.post_question( - user = self.u1, - follow = True, - ) - self.answer = self.post_answer( - user = self.u2, - question = self.question - ) - - self.comment = self.post_comment(user = self.u2, parent_post = self.answer) - def test_address_creation(self): self.assertEquals(ReplyAddress.objects.all().count(), 0) - result = ReplyAddress.objects.create_new( self.answer, self.u1) + result = ReplyAddress.objects.create_new( + post = self.answer, + user = self.u1 + ) self.assertTrue(len(result.address) >= 12 and len(result.address) <= 25) self.assertEquals(ReplyAddress.objects.all().count(), 1) def test_create_answer_reply(self): - result = ReplyAddress.objects.create_new( self.answer, self.u1) - post = result.create_reply(TEST_EMAIL_PARTS) + result = ReplyAddress.objects.create_new( + post = self.answer, + user = self.u1 + ) + post = result.create_reply(TEST_CONTENT) self.assertEquals(post.post_type, "comment") self.assertEquals(post.text, TEST_CONTENT) self.assertEquals(self.answer.comments.count(), 2) def test_create_comment_reply(self): - result = ReplyAddress.objects.create_new( self.comment, self.u1) - post = result.create_reply(TEST_EMAIL_PARTS) + result = ReplyAddress.objects.create_new( + post = self.comment, + user = self.u1 + ) + post = result.create_reply(TEST_CONTENT) self.assertEquals(post.post_type, "comment") self.assertEquals(post.text, TEST_CONTENT) self.assertEquals(self.answer.comments.count(), 2) def test_create_question_comment_reply(self): - result = ReplyAddress.objects.create_new( self.question, self.u3) - post = result.create_reply(TEST_EMAIL_PARTS) + result = ReplyAddress.objects.create_new( + post = self.question, + user = self.u3 + ) + post = result.create_reply(TEST_CONTENT) self.assertEquals(post.post_type, "comment") self.assertEquals(post.text, TEST_CONTENT) def test_create_question_answer_reply(self): - result = ReplyAddress.objects.create_new( self.question, self.u3) - post = result.create_reply(TEST_LONG_EMAIL_PARTS) + result = ReplyAddress.objects.create_new( + post = self.question, + user = self.u3 + ) + post = result.create_reply(TEST_LONG_CONTENT) self.assertEquals(post.post_type, "answer") self.assertEquals(post.text, TEST_LONG_CONTENT) + +class EmailSignatureDetectionTests(AskbotTestCase): + + def setUp(self): + self.u1 = self.create_user('user1', status = 'a') + self.u2 = self.create_user('user2', status = 'a') + + def test_detect_signature_in_response(self): + question = self.post_question(user = self.u1) + + #create a response address record + reply_token = ReplyAddress.objects.create_new( + post = question, + user = self.u2, + reply_action = 'post_answer' + ) + + self.u2.email_signature = '' + self.u2.save() + + msg = MockMessage( + 'some text', + self.u2.email, + signature = 'Yours Truly', + response_code = reply_token.address + ) + PROCESS(msg, address = reply_token.address) + + signature = self.reload_object(self.u2).email_signature + self.assertEqual(signature, 'Yours Truly') + + def test_detect_signature_in_welcome_response(self): + reply_token = ReplyAddress.objects.create_new( + user = self.u2, + reply_action = 'validate_email' + ) + self.u2.email_signature = '' + self.u2.save() + + msg = MockMessage( + 'some text', + self.u2.email, + signature = 'Yours Truly', + response_code = reply_token.address + ) + VALIDATE_EMAIL( + msg, + address = reply_token.address + ) + + signature = self.reload_object(self.u2).email_signature + self.assertEqual(signature, 'Yours Truly') diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py index fdeea371..4bc69ac4 100644 --- a/askbot/tests/utils.py +++ b/askbot/tests/utils.py @@ -115,10 +115,11 @@ class AskbotTestCase(TestCase): title = 'test question title', body_text = 'test question body text', tags = 'test', + by_email = False, wiki = False, is_anonymous = False, follow = False, - timestamp = None + timestamp = None, ): """posts and returns question on behalf of user. If user is not given, it will be self.user @@ -135,6 +136,7 @@ class AskbotTestCase(TestCase): title = title, body_text = body_text, tags = tags, + by_email = by_email, wiki = wiki, is_anonymous = is_anonymous, timestamp = timestamp @@ -155,6 +157,7 @@ class AskbotTestCase(TestCase): user = None, question = None, body_text = 'test answer text', + by_email = False, follow = False, wiki = False, timestamp = None @@ -165,16 +168,30 @@ class AskbotTestCase(TestCase): return user.post_answer( question = question, body_text = body_text, + by_email = by_email, follow = follow, wiki = wiki, timestamp = timestamp ) + def create_tag(self, tag_name, user = None): + """creates a user, b/c it is necessary""" + if user is None: + try: + user = models.User.objects.get(username = 'tag_creator') + except models.User.DoesNotExist: + user = self.create_user('tag_creator') + + tag = models.Tag(created_by = user, name = tag_name) + tag.save() + return tag + def post_comment( self, user = None, parent_post = None, body_text = 'test comment text', + by_email = False, timestamp = None ): """posts and returns a comment to parent post, uses @@ -187,6 +204,7 @@ class AskbotTestCase(TestCase): comment = user.post_comment( parent_post = parent_post, body_text = body_text, + by_email = by_email, timestamp = timestamp, ) diff --git a/askbot/urls.py b/askbot/urls.py index 799cc346..869f7d69 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -246,12 +246,12 @@ urlpatterns = patterns('', ), url( r'^%s$' % _('users/'), - views.users.users, + views.users.show_users, name='users' ), url( r'^%s%s(?P<group_id>\d+)/(?P<group_slug>.*)/$' % (_('users/'), _('by-group/')), - views.users.users, + views.users.show_users, kwargs = {'by_group': True}, name = 'users_by_group' ), diff --git a/askbot/utils/email.py b/askbot/utils/email.py deleted file mode 100644 index 34b11c45..00000000 --- a/askbot/utils/email.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.core.mail import EmailMultiAlternatives -from django.conf import settings as django_settings -from django.template import loader, Context -from django.utils.html import strip_tags -from threading import Thread - -def send_email( - subject, - recipients, - template, - context = {}, - sender = django_settings.DEFAULT_FROM_EMAIL, - txt_template = None - ): - context['settings'] = django_settings - html_body = loader.get_template(template).render(Context(context)) - - if txt_template is None: - txt_body = strip_tags(html_body) - else: - txt_body = loader.get_template(txt_template).render(Context(context)) - - msg = EmailMultiAlternatives(subject, txt_body, sender, recipients) - msg.attach_alternative(html_body, "text/html") - - thread = Thread(target=EmailMultiAlternatives.send, args=[msg]) - thread.setDaemon(True) - thread.start() diff --git a/askbot/utils/html.py b/askbot/utils/html.py index f91fa980..2e3c1913 100644 --- a/askbot/utils/html.py +++ b/askbot/utils/html.py @@ -1,7 +1,11 @@ """Utilities for working with HTML.""" import html5lib from html5lib import sanitizer, serializer, tokenizer, treebuilders, treewalkers -import re, htmlentitydefs +import re +import htmlentitydefs +from urlparse import urlparse +from django.core.urlresolvers import reverse +from django.utils.html import escape class HTMLSanitizerMixin(sanitizer.HTMLSanitizerMixin): acceptable_elements = ('a', 'abbr', 'acronym', 'address', 'b', 'big', @@ -51,6 +55,16 @@ def sanitize_html(html): output_generator = s.serialize(stream) return u''.join(output_generator) +def site_link(url_name, title): + """returns html for the link to the given url + todo: may be improved to process url parameters, keyword + and other arguments + """ + from askbot.conf import settings + base_url = urlparse(settings.APP_URL) + url = base_url.scheme + '://' + base_url.netloc + reverse(url_name) + return '<a href="%s">%s</a>' % (url, escape(title)) + def unescape(text): """source: http://effbot.org/zone/re-sub.htm#unescape-html Removes HTML or XML character references and entities from a text string. diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 6fd493cc..e343c85e 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -14,6 +14,7 @@ from django.forms import ValidationError, IntegerField, CharField from django.shortcuts import get_object_or_404 from django.views.decorators import csrf from django.utils import simplejson +from django.utils.html import escape from django.utils.translation import ugettext as _ from django.utils.translation import string_concat from askbot import models @@ -22,7 +23,7 @@ from askbot.conf import should_show_sort_by_relevance from askbot.conf import settings as askbot_settings from askbot.utils import decorators from askbot.utils import url_utils -from askbot.utils import mail +from askbot import mail from askbot.skins.loaders import render_into_skin, get_template from askbot import const import logging @@ -485,6 +486,9 @@ def get_tag_list(request): @decorators.get_only def load_tag_wiki_text(request): """returns text of the tag wiki in markdown format""" + if 'tag_id' not in request.GET: + return HttpResponse('', status = 400)#bad request + tag = get_object_or_404(models.Tag, id = request.GET['tag_id']) tag_wiki_text = getattr(tag.tag_wiki, 'text', '').strip() return HttpResponse(tag_wiki_text, mimetype = 'text/plain') @@ -573,7 +577,7 @@ def api_get_questions(request): threads = threads.distinct()[:30] thread_list = [{ 'url': thread.get_absolute_url(), - 'title': thread.title, + 'title': escape(thread.title), 'answer_count': thread.answer_count } for thread in threads] json_data = simplejson.dumps(thread_list) diff --git a/askbot/views/meta.py b/askbot/views/meta.py index 3e5a0b35..b8411b41 100644 --- a/askbot/views/meta.py +++ b/askbot/views/meta.py @@ -14,7 +14,7 @@ from django.views.decorators import csrf from django.db.models import Max, Count from askbot.forms import FeedbackForm from askbot.utils.forms import get_next_url -from askbot.utils.mail import mail_moderators +from askbot.mail import mail_moderators from askbot.models import BadgeData, Award, User from askbot.models import badges as badge_data from askbot.skins.loaders import get_template, render_into_skin, render_text_into_skin @@ -87,7 +87,7 @@ def feedback(request): data['email'] = form.cleaned_data.get('email',None) data['message'] = form.cleaned_data['message'] data['name'] = form.cleaned_data.get('name',None) - template = get_template('feedback_email.txt', request) + template = get_template('email/feedback_email.txt', request) message = template.render(RequestContext(request, data)) mail_moderators(_('Q&A forum feedback'), message) msg = _('Thanks for the feedback!') diff --git a/askbot/views/readers.py b/askbot/views/readers.py index 3259cddd..c73b0c35 100644 --- a/askbot/views/readers.py +++ b/askbot/views/readers.py @@ -15,6 +15,7 @@ from django.http import HttpResponseRedirect, HttpResponse, Http404, HttpRespons from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.template import Context from django.utils import simplejson +from django.utils.html import escape from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from django.utils import translation @@ -23,6 +24,7 @@ from django.core.urlresolvers import reverse from django.core import exceptions as django_exceptions from django.contrib.humanize.templatetags import humanize from django.http import QueryDict +from django.conf import settings import askbot from askbot import exceptions @@ -33,6 +35,7 @@ from askbot import schedules from askbot.models.tag import Tag from askbot import const from askbot.utils import functions +from askbot.utils.html import sanitize_html from askbot.utils.decorators import anonymous_forbidden, ajax_only, get_only from askbot.search.state_manager import SearchState, DummySearchState from askbot.templatetags import extra_tags @@ -68,6 +71,7 @@ def questions(request, **kwargs): List of Questions, Tagged questions, and Unanswered questions. matching search query or user selection """ + #before = datetime.datetime.now() if request.method != 'GET': return HttpResponseNotAllowed(['GET']) @@ -125,7 +129,7 @@ def questions(request, **kwargs): # We have tags in session - pass it to the # QueryDict but as a list - we want tags+ rss_query_dict.setlist("tags", search_state.tags) - context_feed_url = '/feeds/rss/?%s' % rss_query_dict.urlencode() # Format the url with the QueryDict + context_feed_url = '/%sfeeds/rss/?%s' % (settings.ASKBOT_URL, rss_query_dict.urlencode()) # Format the url with the QueryDict reset_method_count = len(filter(None, [search_state.query, search_state.tags, meta_data.get('author_name', None)])) @@ -161,7 +165,7 @@ def questions(request, **kwargs): }, 'paginator': paginator_html, 'question_counter': question_counter, - 'faces': [extra_tags.gravatar(contributor, 48) for contributor in contributors], + 'faces': [],#[extra_tags.gravatar(contributor, 48) for contributor in contributors], 'feed_url': context_feed_url, 'query_string': search_state.query_string(), 'page_size' : page_size, @@ -169,7 +173,7 @@ def questions(request, **kwargs): 'non_existing_tags': meta_data['non_existing_tags'] } ajax_data['related_tags'] = [{ - 'name': tag.name, + 'name': escape(tag.name), 'used_count': humanize.intcomma(tag.local_used_count) } for tag in related_tags] @@ -311,7 +315,7 @@ def question(request, id):#refactor - long subroutine. display question body, an """ #process url parameters #todo: fix inheritance of sort method from questions - before = datetime.datetime.now() + #before = datetime.datetime.now() default_sort_method = request.session.get('questions_sort_method', 'votes') form = ShowQuestionForm(request.GET, default_sort_method) form.full_clean()#always valid @@ -551,9 +555,7 @@ def question(request, id):#refactor - long subroutine. display question body, an 'show_comment_position': show_comment_position, } - result = render_into_skin('question.html', data, request) - #print datetime.datetime.now() - before - return result + return render_into_skin('question.html', data, request) def revisions(request, id, post_type = None): assert post_type in ('question', 'answer') @@ -562,10 +564,13 @@ def revisions(request, id, post_type = None): revisions.reverse() for i, revision in enumerate(revisions): if i == 0: - revision.diff = revisions[i].html + revision.diff = sanitize_html(revisions[i].html) revision.summary = _('initial version') else: - revision.diff = htmldiff(revisions[i-1].html, revision.html) + revision.diff = htmldiff( + sanitize_html(revisions[i-1].html), + sanitize_html(revision.html) + ) data = { 'page_class':'revisions-page', diff --git a/askbot/views/users.py b/askbot/views/users.py index 19613838..0ebfb936 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -13,7 +13,7 @@ import datetime import logging import operator -from django.db.models import Count, Q +from django.db.models import Count from django.conf import settings as django_settings from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator, EmptyPage, InvalidPage @@ -28,7 +28,7 @@ from django.views.decorators import csrf from askbot.utils.slug import slugify from askbot.utils.html import sanitize_html -from askbot.utils.mail import send_mail +from askbot.mail import send_mail from askbot.utils.http import get_request_info from askbot.utils import functions from askbot import forms @@ -56,7 +56,7 @@ def owner_or_moderator_required(f): return f(request, profile_owner, context) return wrapped_func -def users(request, by_group = False, group_id = None, group_slug = None): +def show_users(request, by_group = False, group_id = None, group_slug = None): """Users view, including listing of users by group""" users = models.User.objects.exclude(status = 'b') group = None @@ -81,11 +81,15 @@ def users(request, by_group = False, group_id = None, group_slug = None): except models.Tag.DoesNotExist: raise Http404 if group_slug == slugify(group.name): - users = models.User.objects.filter( + group_users = models.User.objects.filter( group_memberships__group__id = group_id ) if request.user.is_authenticated(): - user_is_group_member = bool(users.filter(id = request.user.id).count()) + user_is_group_member = bool( + group_users.filter( + id = request.user.id + ).count() + ) else: group_page_url = reverse( 'users_by_group', @@ -103,13 +107,13 @@ def users(request, by_group = False, group_id = None, group_slug = None): if askbot_settings.KARMA_MODE == 'private' and sortby == 'reputation': sortby = 'newest' - suser = request.REQUEST.get('query', "") try: page = int(request.GET.get('page', '1')) except ValueError: page = 1 - if suser == "": + search_query = request.REQUEST.get('query', "") + if search_query == "": if sortby == "newest": order_by_parameter = '-date_joined' elif sortby == "last": @@ -121,21 +125,18 @@ def users(request, by_group = False, group_id = None, group_slug = None): order_by_parameter = '-reputation' objects_list = Paginator( - users.order_by(order_by_parameter), + models.User.objects.order_by(order_by_parameter), const.USERS_PAGE_SIZE ) - base_url = request.path + '?sort=%s&' % sortby + base_url = request.path + '?sort=%s&' % sortby else: sortby = "reputation" + matching_users = models.get_users_by_text_query(search_query) objects_list = Paginator( - users.filter( - username__icontains = suser - ).order_by( - '-reputation' - ), + matching_users.order_by('-reputation'), const.USERS_PAGE_SIZE ) - base_url = request.path + '?name=%s&sort=%s&' % (suser, sortby) + base_url = request.path + '?name=%s&sort=%s&' % (search_query, sortby) try: users_page = objects_list.page(page) @@ -158,8 +159,7 @@ def users(request, by_group = False, group_id = None, group_slug = None): 'page_class': 'users-page', 'users' : users_page, 'group': group, - 'suser' : suser, - 'keywords' : suser, + 'search_query' : search_query, 'tab_id' : sortby, 'paginator_context' : paginator_context, 'group_email_moderation_enabled': group_email_moderation_enabled, @@ -297,7 +297,7 @@ def edit_user(request, id): user.about = sanitize_html(form.cleaned_data['about']) user.country = form.cleaned_data['country'] user.show_country = form.cleaned_data['show_country'] - + user.show_marked_tags = form.cleaned_data['show_marked_tags'] user.save() # send user updated signal if full fields have been updated award_badges_signal.send(None, @@ -308,10 +308,12 @@ def edit_user(request, id): return HttpResponseRedirect(user.get_profile_url()) else: form = forms.EditUserForm(user) + data = { 'active_tab': 'users', 'page_class': 'user-profile-edit-page', 'form' : form, + 'marked_tags_setting': askbot_settings.MARKED_TAGS_ARE_PUBLIC_WHEN, 'support_custom_avatars': ('avatar' in django_settings.INSTALLED_APPS), 'view_user': user, } @@ -368,6 +370,18 @@ def user_stats(request, user, context): order_by('-user_tag_usage_count')[:const.USER_VIEW_DATA_SIZE] user_tags = list(user_tags) # evaluate + when = askbot_settings.MARKED_TAGS_ARE_PUBLIC_WHEN + if when == 'always' or \ + (when == 'when-user-wants' and user.show_marked_tags == True): + #refactor into: user.get_marked_tag_names('good'/'bad'/'subscribed') + interesting_tag_names = user.get_marked_tag_names('good') + ignored_tag_names = user.get_marked_tag_names('bad') + subscribed_tag_names = user.get_marked_tag_names('subscribed') + else: + interesting_tag_names = None + ignored_tag_names = None + subscribed_tag_names = None + # tags = models.Post.objects.filter(author=user).values('id', 'thread', 'thread__tags') # post_ids = set() # thread_ids = set() @@ -423,6 +437,13 @@ def user_stats(request, user, context): badges = badges_dict.items() badges.sort(key=operator.itemgetter(1), reverse=True) + user_groups = models.Tag.group_tags.get_for_user(user = user) + + if request.user == user: + groups_membership_info = user.get_groups_membership_info(user_groups) + else: + groups_membership_info = collections.defaultdict() + data = { 'active_tab':'users', 'page_class': 'user-profile-page', @@ -444,7 +465,11 @@ def user_stats(request, user, context): 'votes_total_per_day': votes_total, 'user_tags' : user_tags, - 'user_groups': models.Tag.group_tags.get_for_user(user = user), + 'user_groups': user_groups, + 'groups_membership_info': groups_membership_info, + 'interesting_tag_names': interesting_tag_names, + 'ignored_tag_names': ignored_tag_names, + 'subscribed_tag_names': subscribed_tag_names, 'badges': badges, 'total_badges' : len(badges), } @@ -585,12 +610,13 @@ def user_recent(request, user, context): elif activity.activity_type == const.TYPE_ACTIVITY_PRIZE: award = activity.content_object - activities.append(AwardEvent( - time=award.awarded_at, - type=activity.activity_type, - content_object=award.content_object, - badge=award.badge, - )) + if award is not None:#todo: work around halfa$$ comment deletion + activities.append(AwardEvent( + time=award.awarded_at, + type=activity.activity_type, + content_object=award.content_object, + badge=award.badge, + )) activities.sort(key=operator.attrgetter('time'), reverse=True) @@ -691,7 +717,7 @@ def user_responses(request, user, context): #6) sort responses by time filtered_response_list.sort(lambda x,y: cmp(y['timestamp'], x['timestamp'])) - reject_reasons = models.PostFlagReason.objects.all().order_by('title'); + reject_reasons = models.PostFlagReason.objects.all().order_by('title') data = { 'active_tab':'users', 'page_class': 'user-profile-page', @@ -896,8 +922,11 @@ def user(request, id, slug=None, tab_name=None): elif askbot_settings.KARMA_MODE == 'hidden': can_show_karma = False else: - if request.user.is_administrator_or_moderator() \ - or request.user == profile_owner: + if request.user.is_anonymous(): + can_show_karma = False + elif request.user.is_administrator_or_moderator(): + can_show_karma = True + elif request.user == profile_owner: can_show_karma = True else: can_show_karma = False @@ -944,12 +973,38 @@ def groups(request, id = None, slug = None): """ if askbot_settings.GROUPS_ENABLED == False: raise Http404 - groups = models.Tag.group_tags.get_all() - can_edit = request.user.is_authenticated() and \ + + #6 lines of input cleaning code + if request.user.is_authenticated(): + scope = request.GET.get('sort', 'all-groups') + if scope not in ('all-groups', 'my-groups'): + scope = 'all-groups' + else: + scope = 'all-groups' + + if scope == 'all-groups': + groups = models.Tag.group_tags.get_all() + else: + groups = models.Tag.group_tags.get_for_user( + user = request.user + ) + + groups = groups.select_related('group_profile') + + user_can_add_groups = request.user.is_authenticated() and \ request.user.is_administrator_or_moderator() + + groups_membership_info = collections.defaultdict() + if request.user.is_authenticated(): + #collect group memberhship information + groups_membership_info = request.user.get_groups_membership_info(groups) + data = { 'groups': groups, - 'can_edit': can_edit, - 'active_tab': 'users' + 'groups_membership_info': groups_membership_info, + 'user_can_add_groups': user_can_add_groups, + 'active_tab': 'groups',#todo vars active_tab and tab_name are too similar + 'tab_name': scope, + 'page_class': 'groups-page' } return render_into_skin('groups.html', data, request) diff --git a/askbot/views/writers.py b/askbot/views/writers.py index 935b7cfa..232dbaea 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -17,7 +17,7 @@ from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import login_required from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404 from django.utils import simplejson -from django.utils.html import strip_tags +from django.utils.html import strip_tags, escape from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse from django.core import exceptions @@ -545,7 +545,7 @@ def __generate_comments_json(obj, user):#non-view generates json data for the po 'object_id': obj.id, 'comment_added_at': str(comment.added_at.replace(microsecond = 0)) + tz, 'html': comment.html, - 'user_display_name': comment_owner.username, + 'user_display_name': escape(comment_owner.username), 'user_url': comment_owner.get_profile_url(), 'user_id': comment_owner.id, 'is_deletable': is_deletable, diff --git a/askbot_requirements_dev.txt b/askbot_requirements_dev.txt index ada0d83e..b960c76e 100644 --- a/askbot_requirements_dev.txt +++ b/askbot_requirements_dev.txt @@ -5,6 +5,7 @@ Coffin>=0.3 South>=0.7.1 #-e git+https://github.com/matthiask/south.git#egg=south oauth2 +Lamson markdown2 html5lib==0.90 django-keyedcache |