##// END OF EJS Templates
Fixed sticker autocompletions. Localized 'too many files' message and added max file count there
neko259 -
r1766:8d73e763 default
parent child Browse files
Show More
@@ -1,483 +1,486
1 import hashlib
1 import hashlib
2 import logging
2 import logging
3 import re
3 import re
4 import time
4 import time
5
5
6 import pytz
6 import pytz
7 from django import forms
7 from django import forms
8 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
8 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
9 from django.forms.utils import ErrorList
9 from django.forms.utils import ErrorList
10 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
10 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
11
11
12 import boards.settings as board_settings
12 import boards.settings as board_settings
13 import neboard
13 import neboard
14 from boards.abstracts.attachment_alias import get_image_by_alias
14 from boards.abstracts.attachment_alias import get_image_by_alias
15 from boards.abstracts.settingsmanager import get_settings_manager
15 from boards.abstracts.settingsmanager import get_settings_manager
16 from boards.forms.fields import UrlFileField
16 from boards.forms.fields import UrlFileField
17 from boards.mdx_neboard import formatters
17 from boards.mdx_neboard import formatters
18 from boards.models import Tag
18 from boards.models import Tag
19 from boards.models.attachment.downloaders import download, REGEX_MAGNET
19 from boards.models.attachment.downloaders import download, REGEX_MAGNET
20 from boards.models.post import TITLE_MAX_LENGTH
20 from boards.models.post import TITLE_MAX_LENGTH
21 from boards.utils import validate_file_size, get_file_mimetype, \
21 from boards.utils import validate_file_size, get_file_mimetype, \
22 FILE_EXTENSION_DELIMITER
22 FILE_EXTENSION_DELIMITER
23 from neboard import settings
23 from neboard import settings
24
24
25 SECTION_FORMS = 'Forms'
25 SECTION_FORMS = 'Forms'
26
26
27 POW_HASH_LENGTH = 16
27 POW_HASH_LENGTH = 16
28 POW_LIFE_MINUTES = 5
28 POW_LIFE_MINUTES = 5
29
29
30 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
30 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
31 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
31 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
32 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
32 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
33
33
34 VETERAN_POSTING_DELAY = 5
34 VETERAN_POSTING_DELAY = 5
35
35
36 ATTRIBUTE_PLACEHOLDER = 'placeholder'
36 ATTRIBUTE_PLACEHOLDER = 'placeholder'
37 ATTRIBUTE_ROWS = 'rows'
37 ATTRIBUTE_ROWS = 'rows'
38
38
39 LAST_POST_TIME = 'last_post_time'
39 LAST_POST_TIME = 'last_post_time'
40 LAST_LOGIN_TIME = 'last_login_time'
40 LAST_LOGIN_TIME = 'last_login_time'
41 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
41 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
42 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
42 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
43
43
44 LABEL_TITLE = _('Title')
44 LABEL_TITLE = _('Title')
45 LABEL_TEXT = _('Text')
45 LABEL_TEXT = _('Text')
46 LABEL_TAG = _('Tag')
46 LABEL_TAG = _('Tag')
47 LABEL_SEARCH = _('Search')
47 LABEL_SEARCH = _('Search')
48 LABEL_FILE = _('File')
48 LABEL_FILE = _('File')
49
49
50 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
50 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
51 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
51 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
52 ERROR_MANY_FILES = _('Too many files.')
52 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
53 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
53
54
54 TAG_MAX_LENGTH = 20
55 TAG_MAX_LENGTH = 20
55
56
56 TEXTAREA_ROWS = 4
57 TEXTAREA_ROWS = 4
57
58
58 TRIPCODE_DELIM = '#'
59 TRIPCODE_DELIM = '#'
59
60
60 # TODO Maybe this may be converted into the database table?
61 # TODO Maybe this may be converted into the database table?
61 MIMETYPE_EXTENSIONS = {
62 MIMETYPE_EXTENSIONS = {
62 'image/jpeg': 'jpeg',
63 'image/jpeg': 'jpeg',
63 'image/png': 'png',
64 'image/png': 'png',
64 'image/gif': 'gif',
65 'image/gif': 'gif',
65 'video/webm': 'webm',
66 'video/webm': 'webm',
66 'application/pdf': 'pdf',
67 'application/pdf': 'pdf',
67 'x-diff': 'diff',
68 'x-diff': 'diff',
68 'image/svg+xml': 'svg',
69 'image/svg+xml': 'svg',
69 'application/x-shockwave-flash': 'swf',
70 'application/x-shockwave-flash': 'swf',
70 'image/x-ms-bmp': 'bmp',
71 'image/x-ms-bmp': 'bmp',
71 'image/bmp': 'bmp',
72 'image/bmp': 'bmp',
72 }
73 }
73
74
74
75
75 logger = logging.getLogger('boards.forms')
76 logger = logging.getLogger('boards.forms')
76
77
77
78
78 def get_timezones():
79 def get_timezones():
79 timezones = []
80 timezones = []
80 for tz in pytz.common_timezones:
81 for tz in pytz.common_timezones:
81 timezones.append((tz, tz),)
82 timezones.append((tz, tz),)
82 return timezones
83 return timezones
83
84
84
85
85 class FormatPanel(forms.Textarea):
86 class FormatPanel(forms.Textarea):
86 """
87 """
87 Panel for text formatting. Consists of buttons to add different tags to the
88 Panel for text formatting. Consists of buttons to add different tags to the
88 form text area.
89 form text area.
89 """
90 """
90
91
91 def render(self, name, value, attrs=None):
92 def render(self, name, value, attrs=None):
92 output = '<div id="mark-panel">'
93 output = '<div id="mark-panel">'
93 for formatter in formatters:
94 for formatter in formatters:
94 output += '<span class="mark_btn"' + \
95 output += '<span class="mark_btn"' + \
95 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
96 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
96 '\', \'' + formatter.format_right + '\')">' + \
97 '\', \'' + formatter.format_right + '\')">' + \
97 formatter.preview_left + formatter.name + \
98 formatter.preview_left + formatter.name + \
98 formatter.preview_right + '</span>'
99 formatter.preview_right + '</span>'
99
100
100 output += '</div>'
101 output += '</div>'
101 output += super(FormatPanel, self).render(name, value, attrs=attrs)
102 output += super(FormatPanel, self).render(name, value, attrs=attrs)
102
103
103 return output
104 return output
104
105
105
106
106 class PlainErrorList(ErrorList):
107 class PlainErrorList(ErrorList):
107 def __unicode__(self):
108 def __unicode__(self):
108 return self.as_text()
109 return self.as_text()
109
110
110 def as_text(self):
111 def as_text(self):
111 return ''.join(['(!) %s ' % e for e in self])
112 return ''.join(['(!) %s ' % e for e in self])
112
113
113
114
114 class NeboardForm(forms.Form):
115 class NeboardForm(forms.Form):
115 """
116 """
116 Form with neboard-specific formatting.
117 Form with neboard-specific formatting.
117 """
118 """
118 required_css_class = 'required-field'
119 required_css_class = 'required-field'
119
120
120 def as_div(self):
121 def as_div(self):
121 """
122 """
122 Returns this form rendered as HTML <as_div>s.
123 Returns this form rendered as HTML <as_div>s.
123 """
124 """
124
125
125 return self._html_output(
126 return self._html_output(
126 # TODO Do not show hidden rows in the list here
127 # TODO Do not show hidden rows in the list here
127 normal_row='<div class="form-row">'
128 normal_row='<div class="form-row">'
128 '<div class="form-label">'
129 '<div class="form-label">'
129 '%(label)s'
130 '%(label)s'
130 '</div>'
131 '</div>'
131 '<div class="form-input">'
132 '<div class="form-input">'
132 '%(field)s'
133 '%(field)s'
133 '</div>'
134 '</div>'
134 '</div>'
135 '</div>'
135 '<div class="form-row">'
136 '<div class="form-row">'
136 '%(help_text)s'
137 '%(help_text)s'
137 '</div>',
138 '</div>',
138 error_row='<div class="form-row">'
139 error_row='<div class="form-row">'
139 '<div class="form-label"></div>'
140 '<div class="form-label"></div>'
140 '<div class="form-errors">%s</div>'
141 '<div class="form-errors">%s</div>'
141 '</div>',
142 '</div>',
142 row_ender='</div>',
143 row_ender='</div>',
143 help_text_html='%s',
144 help_text_html='%s',
144 errors_on_separate_row=True)
145 errors_on_separate_row=True)
145
146
146 def as_json_errors(self):
147 def as_json_errors(self):
147 errors = []
148 errors = []
148
149
149 for name, field in list(self.fields.items()):
150 for name, field in list(self.fields.items()):
150 if self[name].errors:
151 if self[name].errors:
151 errors.append({
152 errors.append({
152 'field': name,
153 'field': name,
153 'errors': self[name].errors.as_text(),
154 'errors': self[name].errors.as_text(),
154 })
155 })
155
156
156 return errors
157 return errors
157
158
158
159
159 class PostForm(NeboardForm):
160 class PostForm(NeboardForm):
160
161
161 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
162 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
162 label=LABEL_TITLE,
163 label=LABEL_TITLE,
163 widget=forms.TextInput(
164 widget=forms.TextInput(
164 attrs={ATTRIBUTE_PLACEHOLDER: 'title#tripcode'}))
165 attrs={ATTRIBUTE_PLACEHOLDER: 'title#tripcode'}))
165 text = forms.CharField(
166 text = forms.CharField(
166 widget=FormatPanel(attrs={
167 widget=FormatPanel(attrs={
167 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
168 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
168 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
169 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
169 }),
170 }),
170 required=False, label=LABEL_TEXT)
171 required=False, label=LABEL_TEXT)
171 file = UrlFileField(required=False, label=LABEL_FILE)
172 file = UrlFileField(required=False, label=LABEL_FILE)
172
173
173 # This field is for spam prevention only
174 # This field is for spam prevention only
174 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
175 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
175 widget=forms.TextInput(attrs={
176 widget=forms.TextInput(attrs={
176 'class': 'form-email'}))
177 'class': 'form-email'}))
177 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
178 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
178
179
179 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
180 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
180 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
181 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
181 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
182 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
182
183
183 session = None
184 session = None
184 need_to_ban = False
185 need_to_ban = False
185 image = None
186 image = None
186
187
187 def clean_title(self):
188 def clean_title(self):
188 title = self.cleaned_data['title']
189 title = self.cleaned_data['title']
189 if title:
190 if title:
190 if len(title) > TITLE_MAX_LENGTH:
191 if len(title) > TITLE_MAX_LENGTH:
191 raise forms.ValidationError(_('Title must have less than %s '
192 raise forms.ValidationError(_('Title must have less than %s '
192 'characters') %
193 'characters') %
193 str(TITLE_MAX_LENGTH))
194 str(TITLE_MAX_LENGTH))
194 return title
195 return title
195
196
196 def clean_text(self):
197 def clean_text(self):
197 text = self.cleaned_data['text'].strip()
198 text = self.cleaned_data['text'].strip()
198 if text:
199 if text:
199 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
200 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
200 if len(text) > max_length:
201 if len(text) > max_length:
201 raise forms.ValidationError(_('Text must have less than %s '
202 raise forms.ValidationError(_('Text must have less than %s '
202 'characters') % str(max_length))
203 'characters') % str(max_length))
203 return text
204 return text
204
205
205 def clean_file(self):
206 def clean_file(self):
206 return self._clean_files(self.cleaned_data['file'])
207 return self._clean_files(self.cleaned_data['file'])
207
208
208 def clean(self):
209 def clean(self):
209 cleaned_data = super(PostForm, self).clean()
210 cleaned_data = super(PostForm, self).clean()
210
211
211 if cleaned_data['email']:
212 if cleaned_data['email']:
212 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
213 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
213 self.need_to_ban = True
214 self.need_to_ban = True
214 raise forms.ValidationError('A human cannot enter a hidden field')
215 raise forms.ValidationError('A human cannot enter a hidden field')
215
216
216 if not self.errors:
217 if not self.errors:
217 self._clean_text_file()
218 self._clean_text_file()
218
219
219 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
220 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
220 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
221 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
221
222
222 settings_manager = get_settings_manager(self)
223 settings_manager = get_settings_manager(self)
223 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')):
224 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')):
224 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
225 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
225 if pow_difficulty > 0:
226 if pow_difficulty > 0:
226 # PoW-based
227 # PoW-based
227 if cleaned_data['timestamp'] \
228 if cleaned_data['timestamp'] \
228 and cleaned_data['iteration'] and cleaned_data['guess'] \
229 and cleaned_data['iteration'] and cleaned_data['guess'] \
229 and not settings_manager.get_setting('confirmed_user'):
230 and not settings_manager.get_setting('confirmed_user'):
230 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
231 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
231 else:
232 else:
232 # Time-based
233 # Time-based
233 self._validate_posting_speed()
234 self._validate_posting_speed()
234 settings_manager.set_setting('confirmed_user', True)
235 settings_manager.set_setting('confirmed_user', True)
235
236
236 return cleaned_data
237 return cleaned_data
237
238
238 def get_files(self):
239 def get_files(self):
239 """
240 """
240 Gets file from form or URL.
241 Gets file from form or URL.
241 """
242 """
242
243
243 files = []
244 files = []
244 for file in self.cleaned_data['file']:
245 for file in self.cleaned_data['file']:
245 if isinstance(file, UploadedFile):
246 if isinstance(file, UploadedFile):
246 files.append(file)
247 files.append(file)
247
248
248 return files
249 return files
249
250
250 def get_file_urls(self):
251 def get_file_urls(self):
251 files = []
252 files = []
252 for file in self.cleaned_data['file']:
253 for file in self.cleaned_data['file']:
253 if type(file) == str:
254 if type(file) == str:
254 files.append(file)
255 files.append(file)
255
256
256 return files
257 return files
257
258
258 def get_tripcode(self):
259 def get_tripcode(self):
259 title = self.cleaned_data['title']
260 title = self.cleaned_data['title']
260 if title is not None and TRIPCODE_DELIM in title:
261 if title is not None and TRIPCODE_DELIM in title:
261 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
262 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
262 tripcode = hashlib.md5(code.encode()).hexdigest()
263 tripcode = hashlib.md5(code.encode()).hexdigest()
263 else:
264 else:
264 tripcode = ''
265 tripcode = ''
265 return tripcode
266 return tripcode
266
267
267 def get_title(self):
268 def get_title(self):
268 title = self.cleaned_data['title']
269 title = self.cleaned_data['title']
269 if title is not None and TRIPCODE_DELIM in title:
270 if title is not None and TRIPCODE_DELIM in title:
270 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
271 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
271 else:
272 else:
272 return title
273 return title
273
274
274 def get_images(self):
275 def get_images(self):
275 if self.image:
276 if self.image:
276 return [self.image]
277 return [self.image]
277 else:
278 else:
278 return []
279 return []
279
280
280 def is_subscribe(self):
281 def is_subscribe(self):
281 return self.cleaned_data['subscribe']
282 return self.cleaned_data['subscribe']
282
283
283 def _update_file_extension(self, file):
284 def _update_file_extension(self, file):
284 if file:
285 if file:
285 mimetype = get_file_mimetype(file)
286 mimetype = get_file_mimetype(file)
286 extension = MIMETYPE_EXTENSIONS.get(mimetype)
287 extension = MIMETYPE_EXTENSIONS.get(mimetype)
287 if extension:
288 if extension:
288 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
289 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
289 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
290 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
290
291
291 file.name = new_filename
292 file.name = new_filename
292 else:
293 else:
293 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
294 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
294
295
295 def _clean_files(self, inputs):
296 def _clean_files(self, inputs):
296 files = []
297 files = []
297
298
298 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
299 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
299 if len(inputs) > max_file_count:
300 if len(inputs) > max_file_count:
300 raise forms.ValidationError(ERROR_MANY_FILES)
301 raise forms.ValidationError(
302 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
303 max_file_count) % {'files': max_file_count})
301 for file_input in inputs:
304 for file_input in inputs:
302 if isinstance(file_input, UploadedFile):
305 if isinstance(file_input, UploadedFile):
303 files.append(self._clean_file_file(file_input))
306 files.append(self._clean_file_file(file_input))
304 else:
307 else:
305 files.append(self._clean_file_url(file_input))
308 files.append(self._clean_file_url(file_input))
306
309
307 return files
310 return files
308
311
309 def _clean_file_file(self, file):
312 def _clean_file_file(self, file):
310 validate_file_size(file.size)
313 validate_file_size(file.size)
311 self._update_file_extension(file)
314 self._update_file_extension(file)
312
315
313 return file
316 return file
314
317
315 def _clean_file_url(self, url):
318 def _clean_file_url(self, url):
316 file = None
319 file = None
317
320
318 if url:
321 if url:
319 try:
322 try:
320 file = get_image_by_alias(url, self.session)
323 file = get_image_by_alias(url, self.session)
321 self.image = file
324 self.image = file
322
325
323 if file is not None:
326 if file is not None:
324 return
327 return
325
328
326 if file is None:
329 if file is None:
327 file = self._get_file_from_url(url)
330 file = self._get_file_from_url(url)
328 if not file:
331 if not file:
329 raise forms.ValidationError(_('Invalid URL'))
332 raise forms.ValidationError(_('Invalid URL'))
330 else:
333 else:
331 validate_file_size(file.size)
334 validate_file_size(file.size)
332 self._update_file_extension(file)
335 self._update_file_extension(file)
333 except forms.ValidationError as e:
336 except forms.ValidationError as e:
334 # Assume we will get the plain URL instead of a file and save it
337 # Assume we will get the plain URL instead of a file and save it
335 if REGEX_URL.match(url) or REGEX_MAGNET.match(url):
338 if REGEX_URL.match(url) or REGEX_MAGNET.match(url):
336 logger.info('Error in forms: {}'.format(e))
339 logger.info('Error in forms: {}'.format(e))
337 return url
340 return url
338 else:
341 else:
339 raise e
342 raise e
340
343
341 return file
344 return file
342
345
343 def _clean_text_file(self):
346 def _clean_text_file(self):
344 text = self.cleaned_data.get('text')
347 text = self.cleaned_data.get('text')
345 file = self.get_files()
348 file = self.get_files()
346 file_url = self.get_file_urls()
349 file_url = self.get_file_urls()
347 images = self.get_images()
350 images = self.get_images()
348
351
349 if (not text) and (not file) and (not file_url) and len(images) == 0:
352 if (not text) and (not file) and (not file_url) and len(images) == 0:
350 error_message = _('Either text or file must be entered.')
353 error_message = _('Either text or file must be entered.')
351 self._errors['text'] = self.error_class([error_message])
354 self._errors['text'] = self.error_class([error_message])
352
355
353 def _validate_posting_speed(self):
356 def _validate_posting_speed(self):
354 can_post = True
357 can_post = True
355
358
356 posting_delay = board_settings.get_int(SECTION_FORMS, 'PostingDelay')
359 posting_delay = board_settings.get_int(SECTION_FORMS, 'PostingDelay')
357
360
358 if board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed'):
361 if board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed'):
359 now = time.time()
362 now = time.time()
360
363
361 current_delay = 0
364 current_delay = 0
362
365
363 if LAST_POST_TIME not in self.session:
366 if LAST_POST_TIME not in self.session:
364 self.session[LAST_POST_TIME] = now
367 self.session[LAST_POST_TIME] = now
365
368
366 need_delay = True
369 need_delay = True
367 else:
370 else:
368 last_post_time = self.session.get(LAST_POST_TIME)
371 last_post_time = self.session.get(LAST_POST_TIME)
369 current_delay = int(now - last_post_time)
372 current_delay = int(now - last_post_time)
370
373
371 need_delay = current_delay < posting_delay
374 need_delay = current_delay < posting_delay
372
375
373 if need_delay:
376 if need_delay:
374 delay = posting_delay - current_delay
377 delay = posting_delay - current_delay
375 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
378 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
376 delay) % {'delay': delay}
379 delay) % {'delay': delay}
377 self._errors['text'] = self.error_class([error_message])
380 self._errors['text'] = self.error_class([error_message])
378
381
379 can_post = False
382 can_post = False
380
383
381 if can_post:
384 if can_post:
382 self.session[LAST_POST_TIME] = now
385 self.session[LAST_POST_TIME] = now
383
386
384 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
387 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
385 """
388 """
386 Gets an file file from URL.
389 Gets an file file from URL.
387 """
390 """
388
391
389 try:
392 try:
390 return download(url)
393 return download(url)
391 except forms.ValidationError as e:
394 except forms.ValidationError as e:
392 raise e
395 raise e
393 except Exception as e:
396 except Exception as e:
394 raise forms.ValidationError(e)
397 raise forms.ValidationError(e)
395
398
396 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
399 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
397 payload = timestamp + message.replace('\r\n', '\n')
400 payload = timestamp + message.replace('\r\n', '\n')
398 difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
401 difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
399 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
402 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
400 if len(target) < POW_HASH_LENGTH:
403 if len(target) < POW_HASH_LENGTH:
401 target = '0' * (POW_HASH_LENGTH - len(target)) + target
404 target = '0' * (POW_HASH_LENGTH - len(target)) + target
402
405
403 computed_guess = hashlib.sha256((payload + iteration).encode())\
406 computed_guess = hashlib.sha256((payload + iteration).encode())\
404 .hexdigest()[0:POW_HASH_LENGTH]
407 .hexdigest()[0:POW_HASH_LENGTH]
405 if guess != computed_guess or guess > target:
408 if guess != computed_guess or guess > target:
406 self._errors['text'] = self.error_class(
409 self._errors['text'] = self.error_class(
407 [_('Invalid PoW.')])
410 [_('Invalid PoW.')])
408
411
409
412
410 class ThreadForm(PostForm):
413 class ThreadForm(PostForm):
411
414
412 tags = forms.CharField(
415 tags = forms.CharField(
413 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
416 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
414 max_length=100, label=_('Tags'), required=True)
417 max_length=100, label=_('Tags'), required=True)
415 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
418 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
416
419
417 def clean_tags(self):
420 def clean_tags(self):
418 tags = self.cleaned_data['tags'].strip()
421 tags = self.cleaned_data['tags'].strip()
419
422
420 if not tags or not REGEX_TAGS.match(tags):
423 if not tags or not REGEX_TAGS.match(tags):
421 raise forms.ValidationError(
424 raise forms.ValidationError(
422 _('Inappropriate characters in tags.'))
425 _('Inappropriate characters in tags.'))
423
426
424 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
427 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
425 .strip().lower()
428 .strip().lower()
426
429
427 required_tag_exists = False
430 required_tag_exists = False
428 tag_set = set()
431 tag_set = set()
429 for tag_string in tags.split():
432 for tag_string in tags.split():
430 if tag_string.strip().lower() == default_tag_name:
433 if tag_string.strip().lower() == default_tag_name:
431 required_tag_exists = True
434 required_tag_exists = True
432 tag, created = Tag.objects.get_or_create(
435 tag, created = Tag.objects.get_or_create(
433 name=tag_string.strip().lower(), required=True)
436 name=tag_string.strip().lower(), required=True)
434 else:
437 else:
435 tag, created = Tag.objects.get_or_create(
438 tag, created = Tag.objects.get_or_create(
436 name=tag_string.strip().lower())
439 name=tag_string.strip().lower())
437 tag_set.add(tag)
440 tag_set.add(tag)
438
441
439 # If this is a new tag, don't check for its parents because nobody
442 # If this is a new tag, don't check for its parents because nobody
440 # added them yet
443 # added them yet
441 if not created:
444 if not created:
442 tag_set |= set(tag.get_all_parents())
445 tag_set |= set(tag.get_all_parents())
443
446
444 for tag in tag_set:
447 for tag in tag_set:
445 if tag.required:
448 if tag.required:
446 required_tag_exists = True
449 required_tag_exists = True
447 break
450 break
448
451
449 # Use default tag if no section exists
452 # Use default tag if no section exists
450 if not required_tag_exists:
453 if not required_tag_exists:
451 default_tag, created = Tag.objects.get_or_create(
454 default_tag, created = Tag.objects.get_or_create(
452 name=default_tag_name, required=True)
455 name=default_tag_name, required=True)
453 tag_set.add(default_tag)
456 tag_set.add(default_tag)
454
457
455 return tag_set
458 return tag_set
456
459
457 def clean(self):
460 def clean(self):
458 cleaned_data = super(ThreadForm, self).clean()
461 cleaned_data = super(ThreadForm, self).clean()
459
462
460 return cleaned_data
463 return cleaned_data
461
464
462 def is_monochrome(self):
465 def is_monochrome(self):
463 return self.cleaned_data['monochrome']
466 return self.cleaned_data['monochrome']
464
467
465
468
466 class SettingsForm(NeboardForm):
469 class SettingsForm(NeboardForm):
467
470
468 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
471 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
469 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
472 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
470 username = forms.CharField(label=_('User name'), required=False)
473 username = forms.CharField(label=_('User name'), required=False)
471 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
474 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
472
475
473 def clean_username(self):
476 def clean_username(self):
474 username = self.cleaned_data['username']
477 username = self.cleaned_data['username']
475
478
476 if username and not REGEX_USERNAMES.match(username):
479 if username and not REGEX_USERNAMES.match(username):
477 raise forms.ValidationError(_('Inappropriate characters.'))
480 raise forms.ValidationError(_('Inappropriate characters.'))
478
481
479 return username
482 return username
480
483
481
484
482 class SearchForm(NeboardForm):
485 class SearchForm(NeboardForm):
483 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
486 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,569 +1,575
1 # SOME DESCRIPTIVE TITLE.
1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package.
3 # This file is distributed under the same license as the PACKAGE package.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 #
5 #
6 msgid ""
6 msgid ""
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2015-10-09 23:21+0300\n"
10 "POT-Creation-Date: 2015-10-09 23:21+0300\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: ru\n"
14 "Language: ru\n"
15 "MIME-Version: 1.0\n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
17 "Content-Transfer-Encoding: 8bit\n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20
20
21 #: admin.py:22
21 #: admin.py:22
22 msgid "{} posters were banned"
22 msgid "{} posters were banned"
23 msgstr ""
23 msgstr ""
24
24
25 #: authors.py:9
25 #: authors.py:9
26 msgid "author"
26 msgid "author"
27 msgstr "Π°Π²Ρ‚ΠΎΡ€"
27 msgstr "Π°Π²Ρ‚ΠΎΡ€"
28
28
29 #: authors.py:10
29 #: authors.py:10
30 msgid "developer"
30 msgid "developer"
31 msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ"
31 msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ"
32
32
33 #: authors.py:11
33 #: authors.py:11
34 msgid "javascript developer"
34 msgid "javascript developer"
35 msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ javascript"
35 msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ javascript"
36
36
37 #: authors.py:12
37 #: authors.py:12
38 msgid "designer"
38 msgid "designer"
39 msgstr "Π΄ΠΈΠ·Π°ΠΉΠ½Π΅Ρ€"
39 msgstr "Π΄ΠΈΠ·Π°ΠΉΠ½Π΅Ρ€"
40
40
41 #: forms.py:30
41 #: forms.py:30
42 msgid "Type message here. Use formatting panel for more advanced usage."
42 msgid "Type message here. Use formatting panel for more advanced usage."
43 msgstr ""
43 msgstr ""
44 "Π’Π²ΠΎΠ΄ΠΈΡ‚Π΅ сообщСниС сюда. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ панСль для Π±ΠΎΠ»Π΅Π΅ слоТного форматирования."
44 "Π’Π²ΠΎΠ΄ΠΈΡ‚Π΅ сообщСниС сюда. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ панСль для Π±ΠΎΠ»Π΅Π΅ слоТного форматирования."
45
45
46 #: forms.py:31
46 #: forms.py:31
47 msgid "music images i_dont_like_tags"
47 msgid "music images i_dont_like_tags"
48 msgstr "ΠΌΡƒΠ·Ρ‹ΠΊΠ° ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠΈ Ρ‚Π΅Π³ΠΈ_Π½Π΅_Π½ΡƒΠΆΠ½Ρ‹"
48 msgstr "ΠΌΡƒΠ·Ρ‹ΠΊΠ° ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠΈ Ρ‚Π΅Π³ΠΈ_Π½Π΅_Π½ΡƒΠΆΠ½Ρ‹"
49
49
50 #: forms.py:33
50 #: forms.py:33
51 msgid "Title"
51 msgid "Title"
52 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
52 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
53
53
54 #: forms.py:34
54 #: forms.py:34
55 msgid "Text"
55 msgid "Text"
56 msgstr "ВСкст"
56 msgstr "ВСкст"
57
57
58 #: forms.py:35
58 #: forms.py:35
59 msgid "Tag"
59 msgid "Tag"
60 msgstr "ΠœΠ΅Ρ‚ΠΊΠ°"
60 msgstr "ΠœΠ΅Ρ‚ΠΊΠ°"
61
61
62 #: forms.py:36 templates/boards/base.html:40 templates/search/search.html:7
62 #: forms.py:36 templates/boards/base.html:40 templates/search/search.html:7
63 msgid "Search"
63 msgid "Search"
64 msgstr "Поиск"
64 msgstr "Поиск"
65
65
66 #: forms.py:48
66 #: forms.py:48
67 msgid "File 1"
67 msgid "File 1"
68 msgstr "Π€Π°ΠΉΠ» 1"
68 msgstr "Π€Π°ΠΉΠ» 1"
69
69
70 #: forms.py:48
70 #: forms.py:48
71 msgid "File 2"
71 msgid "File 2"
72 msgstr "Π€Π°ΠΉΠ» 2"
72 msgstr "Π€Π°ΠΉΠ» 2"
73
73
74 #: forms.py:142
74 #: forms.py:142
75 msgid "File URL"
75 msgid "File URL"
76 msgstr "URL Ρ„Π°ΠΉΠ»Π°"
76 msgstr "URL Ρ„Π°ΠΉΠ»Π°"
77
77
78 #: forms.py:148
78 #: forms.py:148
79 msgid "e-mail"
79 msgid "e-mail"
80 msgstr ""
80 msgstr ""
81
81
82 #: forms.py:151
82 #: forms.py:151
83 msgid "Additional threads"
83 msgid "Additional threads"
84 msgstr "Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
84 msgstr "Π”ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
85
85
86 #: forms.py:162
86 #: forms.py:162
87 #, python-format
87 #, python-format
88 msgid "Title must have less than %s characters"
88 msgid "Title must have less than %s characters"
89 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΈΠΌΠ΅Ρ‚ΡŒ мСньшС %s символов"
89 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΈΠΌΠ΅Ρ‚ΡŒ мСньшС %s символов"
90
90
91 #: forms.py:172
91 #: forms.py:172
92 #, python-format
92 #, python-format
93 msgid "Text must have less than %s characters"
93 msgid "Text must have less than %s characters"
94 msgstr "ВСкст Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΊΠΎΡ€ΠΎΡ‡Π΅ %s символов"
94 msgstr "ВСкст Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΊΠΎΡ€ΠΎΡ‡Π΅ %s символов"
95
95
96 #: forms.py:192
96 #: forms.py:192
97 msgid "Invalid URL"
97 msgid "Invalid URL"
98 msgstr "НСвСрный URL"
98 msgstr "НСвСрный URL"
99
99
100 #: forms.py:213
100 #: forms.py:213
101 msgid "Invalid additional thread list"
101 msgid "Invalid additional thread list"
102 msgstr "НСвСрный список Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
102 msgstr "НСвСрный список Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
103
103
104 #: forms.py:258
104 #: forms.py:258
105 msgid "Either text or file must be entered."
105 msgid "Either text or file must be entered."
106 msgstr "ВСкст ΠΈΠ»ΠΈ Ρ„Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Ρ‹."
106 msgstr "ВСкст ΠΈΠ»ΠΈ Ρ„Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Ρ‹."
107
107
108 #: forms.py:317 templates/boards/all_threads.html:153
108 #: forms.py:317 templates/boards/all_threads.html:153
109 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
109 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
110 msgid "Tags"
110 msgid "Tags"
111 msgstr "ΠœΠ΅Ρ‚ΠΊΠΈ"
111 msgstr "ΠœΠ΅Ρ‚ΠΊΠΈ"
112
112
113 #: forms.py:324
113 #: forms.py:324
114 msgid "Inappropriate characters in tags."
114 msgid "Inappropriate characters in tags."
115 msgstr "НСдопустимыС символы Π² ΠΌΠ΅Ρ‚ΠΊΠ°Ρ…."
115 msgstr "НСдопустимыС символы Π² ΠΌΠ΅Ρ‚ΠΊΠ°Ρ…."
116
116
117 #: forms.py:344
117 #: forms.py:344
118 msgid "Need at least one section."
118 msgid "Need at least one section."
119 msgstr "НуТСн хотя Π±Ρ‹ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·Π΄Π΅Π»."
119 msgstr "НуТСн хотя Π±Ρ‹ ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·Π΄Π΅Π»."
120
120
121 #: forms.py:356
121 #: forms.py:356
122 msgid "Theme"
122 msgid "Theme"
123 msgstr "Π’Π΅ΠΌΠ°"
123 msgstr "Π’Π΅ΠΌΠ°"
124
124
125 #: forms.py:357
125 #: forms.py:357
126 msgid "Image view mode"
126 msgid "Image view mode"
127 msgstr "Π Π΅ΠΆΠΈΠΌ просмотра ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
127 msgstr "Π Π΅ΠΆΠΈΠΌ просмотра ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
128
128
129 #: forms.py:358
129 #: forms.py:358
130 msgid "User name"
130 msgid "User name"
131 msgstr "Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
131 msgstr "Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
132
132
133 #: forms.py:359
133 #: forms.py:359
134 msgid "Time zone"
134 msgid "Time zone"
135 msgstr "Часовой пояс"
135 msgstr "Часовой пояс"
136
136
137 #: forms.py:365
137 #: forms.py:365
138 msgid "Inappropriate characters."
138 msgid "Inappropriate characters."
139 msgstr "НСдопустимыС символы."
139 msgstr "НСдопустимыС символы."
140
140
141 #: templates/boards/404.html:6
141 #: templates/boards/404.html:6
142 msgid "Not found"
142 msgid "Not found"
143 msgstr "НС найдСно"
143 msgstr "НС найдСно"
144
144
145 #: templates/boards/404.html:12
145 #: templates/boards/404.html:12
146 msgid "This page does not exist"
146 msgid "This page does not exist"
147 msgstr "Π­Ρ‚ΠΎΠΉ страницы Π½Π΅ сущСствуСт"
147 msgstr "Π­Ρ‚ΠΎΠΉ страницы Π½Π΅ сущСствуСт"
148
148
149 #: templates/boards/all_threads.html:35
149 #: templates/boards/all_threads.html:35
150 msgid "Details"
150 msgid "Details"
151 msgstr "ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΡΡ‚ΠΈ"
151 msgstr "ΠŸΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΡΡ‚ΠΈ"
152
152
153 #: templates/boards/all_threads.html:69
153 #: templates/boards/all_threads.html:69
154 msgid "Edit tag"
154 msgid "Edit tag"
155 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΊΡƒ"
155 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΊΡƒ"
156
156
157 #: templates/boards/all_threads.html:76
157 #: templates/boards/all_threads.html:76
158 #, python-format
158 #, python-format
159 msgid "%(count)s active thread"
159 msgid "%(count)s active thread"
160 msgid_plural "%(count)s active threads"
160 msgid_plural "%(count)s active threads"
161 msgstr[0] "%(count)s активная Ρ‚Π΅ΠΌΠ°"
161 msgstr[0] "%(count)s активная Ρ‚Π΅ΠΌΠ°"
162 msgstr[1] "%(count)s Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
162 msgstr[1] "%(count)s Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
163 msgstr[2] "%(count)s Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
163 msgstr[2] "%(count)s Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
164
164
165 #: templates/boards/all_threads.html:76
165 #: templates/boards/all_threads.html:76
166 #, python-format
166 #, python-format
167 msgid "%(count)s thread in bumplimit"
167 msgid "%(count)s thread in bumplimit"
168 msgid_plural "%(count)s threads in bumplimit"
168 msgid_plural "%(count)s threads in bumplimit"
169 msgstr[0] "%(count)s Ρ‚Π΅ΠΌΠ° Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
169 msgstr[0] "%(count)s Ρ‚Π΅ΠΌΠ° Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
170 msgstr[1] "%(count)s Ρ‚Π΅ΠΌΡ‹ Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
170 msgstr[1] "%(count)s Ρ‚Π΅ΠΌΡ‹ Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
171 msgstr[2] "%(count)s Ρ‚Π΅ΠΌ Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
171 msgstr[2] "%(count)s Ρ‚Π΅ΠΌ Π² Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π΅"
172
172
173 #: templates/boards/all_threads.html:77
173 #: templates/boards/all_threads.html:77
174 #, python-format
174 #, python-format
175 msgid "%(count)s archived thread"
175 msgid "%(count)s archived thread"
176 msgid_plural "%(count)s archived thread"
176 msgid_plural "%(count)s archived thread"
177 msgstr[0] "%(count)s архивная Ρ‚Π΅ΠΌΠ°"
177 msgstr[0] "%(count)s архивная Ρ‚Π΅ΠΌΠ°"
178 msgstr[1] "%(count)s Π°Ρ€Ρ…ΠΈΠ²Π½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
178 msgstr[1] "%(count)s Π°Ρ€Ρ…ΠΈΠ²Π½Ρ‹Π΅ Ρ‚Π΅ΠΌΡ‹"
179 msgstr[2] "%(count)s Π°Ρ€Ρ…ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
179 msgstr[2] "%(count)s Π°Ρ€Ρ…ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ"
180
180
181 #: templates/boards/all_threads.html:78 templates/boards/post.html:102
181 #: templates/boards/all_threads.html:78 templates/boards/post.html:102
182 #, python-format
182 #, python-format
183 #| msgid "%(count)s message"
183 #| msgid "%(count)s message"
184 #| msgid_plural "%(count)s messages"
184 #| msgid_plural "%(count)s messages"
185 msgid "%(count)s message"
185 msgid "%(count)s message"
186 msgid_plural "%(count)s messages"
186 msgid_plural "%(count)s messages"
187 msgstr[0] "%(count)s сообщСниС"
187 msgstr[0] "%(count)s сообщСниС"
188 msgstr[1] "%(count)s сообщСния"
188 msgstr[1] "%(count)s сообщСния"
189 msgstr[2] "%(count)s сообщСний"
189 msgstr[2] "%(count)s сообщСний"
190
190
191 #: templates/boards/all_threads.html:95 templates/boards/feed.html:30
191 #: templates/boards/all_threads.html:95 templates/boards/feed.html:30
192 #: templates/boards/notifications.html:17 templates/search/search.html:26
192 #: templates/boards/notifications.html:17 templates/search/search.html:26
193 msgid "Previous page"
193 msgid "Previous page"
194 msgstr "ΠŸΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π°Ρ страница"
194 msgstr "ΠŸΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π°Ρ страница"
195
195
196 #: templates/boards/all_threads.html:109
196 #: templates/boards/all_threads.html:109
197 #, python-format
197 #, python-format
198 msgid "Skipped %(count)s reply. Open thread to see all replies."
198 msgid "Skipped %(count)s reply. Open thread to see all replies."
199 msgid_plural "Skipped %(count)s replies. Open thread to see all replies."
199 msgid_plural "Skipped %(count)s replies. Open thread to see all replies."
200 msgstr[0] "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ %(count)s ΠΎΡ‚Π²Π΅Ρ‚. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
200 msgstr[0] "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ %(count)s ΠΎΡ‚Π²Π΅Ρ‚. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
201 msgstr[1] ""
201 msgstr[1] ""
202 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚Π°. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
202 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚Π°. ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
203 msgstr[2] ""
203 msgstr[2] ""
204 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ². ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
204 "ΠŸΡ€ΠΎΠΏΡƒΡ‰Π΅Π½ΠΎ %(count)s ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ². ΠžΡ‚ΠΊΡ€ΠΎΠΉΡ‚Π΅ Ρ‚Ρ€Π΅Π΄, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡƒΠ²ΠΈΠ΄Π΅Ρ‚ΡŒ всС ΠΎΡ‚Π²Π΅Ρ‚Ρ‹."
205
205
206 #: templates/boards/all_threads.html:127 templates/boards/feed.html:40
206 #: templates/boards/all_threads.html:127 templates/boards/feed.html:40
207 #: templates/boards/notifications.html:27 templates/search/search.html:37
207 #: templates/boards/notifications.html:27 templates/search/search.html:37
208 msgid "Next page"
208 msgid "Next page"
209 msgstr "Π‘Π»Π΅Π΄ΡƒΡŽΡ‰Π°Ρ страница"
209 msgstr "Π‘Π»Π΅Π΄ΡƒΡŽΡ‰Π°Ρ страница"
210
210
211 #: templates/boards/all_threads.html:132
211 #: templates/boards/all_threads.html:132
212 msgid "No threads exist. Create the first one!"
212 msgid "No threads exist. Create the first one!"
213 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
213 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
214
214
215 #: templates/boards/all_threads.html:138
215 #: templates/boards/all_threads.html:138
216 msgid "Create new thread"
216 msgid "Create new thread"
217 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
217 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
218
218
219 #: templates/boards/all_threads.html:143 templates/boards/preview.html:16
219 #: templates/boards/all_threads.html:143 templates/boards/preview.html:16
220 #: templates/boards/thread_normal.html:51
220 #: templates/boards/thread_normal.html:51
221 msgid "Post"
221 msgid "Post"
222 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
222 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
223
223
224 #: templates/boards/all_threads.html:144 templates/boards/preview.html:6
224 #: templates/boards/all_threads.html:144 templates/boards/preview.html:6
225 #: templates/boards/staticpages/help.html:21
225 #: templates/boards/staticpages/help.html:21
226 #: templates/boards/thread_normal.html:52
226 #: templates/boards/thread_normal.html:52
227 msgid "Preview"
227 msgid "Preview"
228 msgstr "ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
228 msgstr "ΠŸΡ€Π΅Π΄ΠΏΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
229
229
230 #: templates/boards/all_threads.html:149
230 #: templates/boards/all_threads.html:149
231 msgid "Tags must be delimited by spaces. Text or image is required."
231 msgid "Tags must be delimited by spaces. Text or image is required."
232 msgstr ""
232 msgstr ""
233 "ΠœΠ΅Ρ‚ΠΊΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
233 "ΠœΠ΅Ρ‚ΠΊΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
234
234
235 #: templates/boards/all_threads.html:152 templates/boards/thread_normal.html:58
235 #: templates/boards/all_threads.html:152 templates/boards/thread_normal.html:58
236 msgid "Text syntax"
236 msgid "Text syntax"
237 msgstr "Бинтаксис тСкста"
237 msgstr "Бинтаксис тСкста"
238
238
239 #: templates/boards/all_threads.html:166 templates/boards/feed.html:53
239 #: templates/boards/all_threads.html:166 templates/boards/feed.html:53
240 msgid "Pages:"
240 msgid "Pages:"
241 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
241 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
242
242
243 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
243 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
244 msgid "Authors"
244 msgid "Authors"
245 msgstr "Авторы"
245 msgstr "Авторы"
246
246
247 #: templates/boards/authors.html:26
247 #: templates/boards/authors.html:26
248 msgid "Distributed under the"
248 msgid "Distributed under the"
249 msgstr "РаспространяСтся ΠΏΠΎΠ΄"
249 msgstr "РаспространяСтся ΠΏΠΎΠ΄"
250
250
251 #: templates/boards/authors.html:28
251 #: templates/boards/authors.html:28
252 msgid "license"
252 msgid "license"
253 msgstr "Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ"
253 msgstr "Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ"
254
254
255 #: templates/boards/authors.html:30
255 #: templates/boards/authors.html:30
256 msgid "Repository"
256 msgid "Repository"
257 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
257 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
258
258
259 #: templates/boards/base.html:14 templates/boards/base.html.py:41
259 #: templates/boards/base.html:14 templates/boards/base.html.py:41
260 msgid "Feed"
260 msgid "Feed"
261 msgstr "Π›Π΅Π½Ρ‚Π°"
261 msgstr "Π›Π΅Π½Ρ‚Π°"
262
262
263 #: templates/boards/base.html:31
263 #: templates/boards/base.html:31
264 msgid "All threads"
264 msgid "All threads"
265 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
265 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
266
266
267 #: templates/boards/base.html:37
267 #: templates/boards/base.html:37
268 msgid "Add tags"
268 msgid "Add tags"
269 msgstr "Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΊΠΈ"
269 msgstr "Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΊΠΈ"
270
270
271 #: templates/boards/base.html:39
271 #: templates/boards/base.html:39
272 msgid "Tag management"
272 msgid "Tag management"
273 msgstr "Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΊΠ°ΠΌΠΈ"
273 msgstr "Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΊΠ°ΠΌΠΈ"
274
274
275 #: templates/boards/base.html:39
275 #: templates/boards/base.html:39
276 msgid "tags"
276 msgid "tags"
277 msgstr "ΠΌΠ΅Ρ‚ΠΊΠΈ"
277 msgstr "ΠΌΠ΅Ρ‚ΠΊΠΈ"
278
278
279 #: templates/boards/base.html:40
279 #: templates/boards/base.html:40
280 msgid "search"
280 msgid "search"
281 msgstr "поиск"
281 msgstr "поиск"
282
282
283 #: templates/boards/base.html:41 templates/boards/feed.html:11
283 #: templates/boards/base.html:41 templates/boards/feed.html:11
284 msgid "feed"
284 msgid "feed"
285 msgstr "Π»Π΅Π½Ρ‚Π°"
285 msgstr "Π»Π΅Π½Ρ‚Π°"
286
286
287 #: templates/boards/base.html:42 templates/boards/random.html:6
287 #: templates/boards/base.html:42 templates/boards/random.html:6
288 msgid "Random images"
288 msgid "Random images"
289 msgstr "Π‘Π»ΡƒΡ‡Π°ΠΉΠ½Ρ‹Π΅ изобраТСния"
289 msgstr "Π‘Π»ΡƒΡ‡Π°ΠΉΠ½Ρ‹Π΅ изобраТСния"
290
290
291 #: templates/boards/base.html:42
291 #: templates/boards/base.html:42
292 msgid "random"
292 msgid "random"
293 msgstr "случайныС"
293 msgstr "случайныС"
294
294
295 #: templates/boards/base.html:44
295 #: templates/boards/base.html:44
296 msgid "favorites"
296 msgid "favorites"
297 msgstr "ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ΅"
297 msgstr "ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ΅"
298
298
299 #: templates/boards/base.html:48 templates/boards/base.html.py:49
299 #: templates/boards/base.html:48 templates/boards/base.html.py:49
300 #: templates/boards/notifications.html:8
300 #: templates/boards/notifications.html:8
301 msgid "Notifications"
301 msgid "Notifications"
302 msgstr "УвСдомлСния"
302 msgstr "УвСдомлСния"
303
303
304 #: templates/boards/base.html:56 templates/boards/settings.html:8
304 #: templates/boards/base.html:56 templates/boards/settings.html:8
305 msgid "Settings"
305 msgid "Settings"
306 msgstr "Настройки"
306 msgstr "Настройки"
307
307
308 #: templates/boards/base.html:59
308 #: templates/boards/base.html:59
309 msgid "Loading..."
309 msgid "Loading..."
310 msgstr "Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°..."
310 msgstr "Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°..."
311
311
312 #: templates/boards/base.html:71
312 #: templates/boards/base.html:71
313 msgid "Admin"
313 msgid "Admin"
314 msgstr "АдминистрированиС"
314 msgstr "АдминистрированиС"
315
315
316 #: templates/boards/base.html:73
316 #: templates/boards/base.html:73
317 #, python-format
317 #, python-format
318 msgid "Speed: %(ppd)s posts per day"
318 msgid "Speed: %(ppd)s posts per day"
319 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
319 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
320
320
321 #: templates/boards/base.html:75
321 #: templates/boards/base.html:75
322 msgid "Up"
322 msgid "Up"
323 msgstr "Π’Π²Π΅Ρ€Ρ…"
323 msgstr "Π’Π²Π΅Ρ€Ρ…"
324
324
325 #: templates/boards/feed.html:45
325 #: templates/boards/feed.html:45
326 msgid "No posts exist. Create the first one!"
326 msgid "No posts exist. Create the first one!"
327 msgstr "НСт сообщСний. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΠΎΠ΅!"
327 msgstr "НСт сообщСний. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΠΎΠ΅!"
328
328
329 #: templates/boards/post.html:33
329 #: templates/boards/post.html:33
330 msgid "Open"
330 msgid "Open"
331 msgstr "ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ"
331 msgstr "ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ"
332
332
333 #: templates/boards/post.html:35 templates/boards/post.html.py:46
333 #: templates/boards/post.html:35 templates/boards/post.html.py:46
334 msgid "Reply"
334 msgid "Reply"
335 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ"
335 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ"
336
336
337 #: templates/boards/post.html:41
337 #: templates/boards/post.html:41
338 msgid " in "
338 msgid " in "
339 msgstr " Π² "
339 msgstr " Π² "
340
340
341 #: templates/boards/post.html:51
341 #: templates/boards/post.html:51
342 msgid "Edit"
342 msgid "Edit"
343 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ"
343 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ"
344
344
345 #: templates/boards/post.html:53
345 #: templates/boards/post.html:53
346 msgid "Edit thread"
346 msgid "Edit thread"
347 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΌΡƒ"
347 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΌΡƒ"
348
348
349 #: templates/boards/post.html:91
349 #: templates/boards/post.html:91
350 msgid "Replies"
350 msgid "Replies"
351 msgstr "ΠžΡ‚Π²Π΅Ρ‚Ρ‹"
351 msgstr "ΠžΡ‚Π²Π΅Ρ‚Ρ‹"
352
352
353 #: templates/boards/post.html:103
353 #: templates/boards/post.html:103
354 #, python-format
354 #, python-format
355 msgid "%(count)s image"
355 msgid "%(count)s image"
356 msgid_plural "%(count)s images"
356 msgid_plural "%(count)s images"
357 msgstr[0] "%(count)s ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
357 msgstr[0] "%(count)s ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
358 msgstr[1] "%(count)s изобраТСния"
358 msgstr[1] "%(count)s изобраТСния"
359 msgstr[2] "%(count)s ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
359 msgstr[2] "%(count)s ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
360
360
361 #: templates/boards/rss/post.html:5
361 #: templates/boards/rss/post.html:5
362 msgid "Post image"
362 msgid "Post image"
363 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ сообщСния"
363 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ сообщСния"
364
364
365 #: templates/boards/settings.html:15
365 #: templates/boards/settings.html:15
366 msgid "You are moderator."
366 msgid "You are moderator."
367 msgstr "Π’Ρ‹ ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ‚ΠΎΡ€."
367 msgstr "Π’Ρ‹ ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ‚ΠΎΡ€."
368
368
369 #: templates/boards/settings.html:19
369 #: templates/boards/settings.html:19
370 msgid "Hidden tags:"
370 msgid "Hidden tags:"
371 msgstr "Π‘ΠΊΡ€Ρ‹Ρ‚Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΊΠΈ:"
371 msgstr "Π‘ΠΊΡ€Ρ‹Ρ‚Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΊΠΈ:"
372
372
373 #: templates/boards/settings.html:25
373 #: templates/boards/settings.html:25
374 msgid "No hidden tags."
374 msgid "No hidden tags."
375 msgstr "НСт скрытых ΠΌΠ΅Ρ‚ΠΎΠΊ."
375 msgstr "НСт скрытых ΠΌΠ΅Ρ‚ΠΎΠΊ."
376
376
377 #: templates/boards/settings.html:34
377 #: templates/boards/settings.html:34
378 msgid "Save"
378 msgid "Save"
379 msgstr "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ"
379 msgstr "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ"
380
380
381 #: templates/boards/staticpages/banned.html:6
381 #: templates/boards/staticpages/banned.html:6
382 msgid "Banned"
382 msgid "Banned"
383 msgstr "Π—Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½"
383 msgstr "Π—Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½"
384
384
385 #: templates/boards/staticpages/banned.html:11
385 #: templates/boards/staticpages/banned.html:11
386 msgid "Your IP address has been banned. Contact the administrator"
386 msgid "Your IP address has been banned. Contact the administrator"
387 msgstr "Π’Π°Ρˆ IP адрСс Π±Ρ‹Π» Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½. Π‘Π²ΡΠΆΠΈΡ‚Π΅ΡΡŒ с администратором"
387 msgstr "Π’Π°Ρˆ IP адрСс Π±Ρ‹Π» Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½. Π‘Π²ΡΠΆΠΈΡ‚Π΅ΡΡŒ с администратором"
388
388
389 #: templates/boards/staticpages/help.html:6
389 #: templates/boards/staticpages/help.html:6
390 #: templates/boards/staticpages/help.html:10
390 #: templates/boards/staticpages/help.html:10
391 msgid "Syntax"
391 msgid "Syntax"
392 msgstr "Бинтаксис"
392 msgstr "Бинтаксис"
393
393
394 #: templates/boards/staticpages/help.html:11
394 #: templates/boards/staticpages/help.html:11
395 msgid "Italic text"
395 msgid "Italic text"
396 msgstr "ΠšΡƒΡ€ΡΠΈΠ²Π½Ρ‹ΠΉ тСкст"
396 msgstr "ΠšΡƒΡ€ΡΠΈΠ²Π½Ρ‹ΠΉ тСкст"
397
397
398 #: templates/boards/staticpages/help.html:12
398 #: templates/boards/staticpages/help.html:12
399 msgid "Bold text"
399 msgid "Bold text"
400 msgstr "ΠŸΠΎΠ»ΡƒΠΆΠΈΡ€Π½Ρ‹ΠΉ тСкст"
400 msgstr "ΠŸΠΎΠ»ΡƒΠΆΠΈΡ€Π½Ρ‹ΠΉ тСкст"
401
401
402 #: templates/boards/staticpages/help.html:13
402 #: templates/boards/staticpages/help.html:13
403 msgid "Spoiler"
403 msgid "Spoiler"
404 msgstr "Π‘ΠΏΠΎΠΉΠ»Π΅Ρ€"
404 msgstr "Π‘ΠΏΠΎΠΉΠ»Π΅Ρ€"
405
405
406 #: templates/boards/staticpages/help.html:14
406 #: templates/boards/staticpages/help.html:14
407 msgid "Link to a post"
407 msgid "Link to a post"
408 msgstr "Бсылка Π½Π° сообщСниС"
408 msgstr "Бсылка Π½Π° сообщСниС"
409
409
410 #: templates/boards/staticpages/help.html:15
410 #: templates/boards/staticpages/help.html:15
411 msgid "Strikethrough text"
411 msgid "Strikethrough text"
412 msgstr "Π—Π°Ρ‡Π΅Ρ€ΠΊΠ½ΡƒΡ‚Ρ‹ΠΉ тСкст"
412 msgstr "Π—Π°Ρ‡Π΅Ρ€ΠΊΠ½ΡƒΡ‚Ρ‹ΠΉ тСкст"
413
413
414 #: templates/boards/staticpages/help.html:16
414 #: templates/boards/staticpages/help.html:16
415 msgid "Comment"
415 msgid "Comment"
416 msgstr "ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ"
416 msgstr "ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ"
417
417
418 #: templates/boards/staticpages/help.html:17
418 #: templates/boards/staticpages/help.html:17
419 #: templates/boards/staticpages/help.html:18
419 #: templates/boards/staticpages/help.html:18
420 msgid "Quote"
420 msgid "Quote"
421 msgstr "Π¦ΠΈΡ‚Π°Ρ‚Π°"
421 msgstr "Π¦ΠΈΡ‚Π°Ρ‚Π°"
422
422
423 #: templates/boards/staticpages/help.html:21
423 #: templates/boards/staticpages/help.html:21
424 msgid "You can try pasting the text and previewing the result here:"
424 msgid "You can try pasting the text and previewing the result here:"
425 msgstr "Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ Π²ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ тСкст ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ здСсь:"
425 msgstr "Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ Π²ΡΡ‚Π°Π²ΠΈΡ‚ΡŒ тСкст ΠΈ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ здСсь:"
426
426
427 #: templates/boards/tags.html:17
427 #: templates/boards/tags.html:17
428 msgid "Sections:"
428 msgid "Sections:"
429 msgstr "Π Π°Π·Π΄Π΅Π»Ρ‹:"
429 msgstr "Π Π°Π·Π΄Π΅Π»Ρ‹:"
430
430
431 #: templates/boards/tags.html:30
431 #: templates/boards/tags.html:30
432 msgid "Other tags:"
432 msgid "Other tags:"
433 msgstr "Π”Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΊΠΈ:"
433 msgstr "Π”Ρ€ΡƒΠ³ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΊΠΈ:"
434
434
435 #: templates/boards/tags.html:43
435 #: templates/boards/tags.html:43
436 msgid "All tags..."
436 msgid "All tags..."
437 msgstr "ВсС ΠΌΠ΅Ρ‚ΠΊΠΈ..."
437 msgstr "ВсС ΠΌΠ΅Ρ‚ΠΊΠΈ..."
438
438
439 #: templates/boards/thread.html:14
439 #: templates/boards/thread.html:14
440 msgid "Normal"
440 msgid "Normal"
441 msgstr "ΠΠΎΡ€ΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ"
441 msgstr "ΠΠΎΡ€ΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ"
442
442
443 #: templates/boards/thread.html:15
443 #: templates/boards/thread.html:15
444 msgid "Gallery"
444 msgid "Gallery"
445 msgstr "ГалСрСя"
445 msgstr "ГалСрСя"
446
446
447 #: templates/boards/thread.html:16
447 #: templates/boards/thread.html:16
448 msgid "Tree"
448 msgid "Tree"
449 msgstr "Π”Π΅Ρ€Π΅Π²ΠΎ"
449 msgstr "Π”Π΅Ρ€Π΅Π²ΠΎ"
450
450
451 #: templates/boards/thread.html:35
451 #: templates/boards/thread.html:35
452 msgid "message"
452 msgid "message"
453 msgid_plural "messages"
453 msgid_plural "messages"
454 msgstr[0] "сообщСниС"
454 msgstr[0] "сообщСниС"
455 msgstr[1] "сообщСния"
455 msgstr[1] "сообщСния"
456 msgstr[2] "сообщСний"
456 msgstr[2] "сообщСний"
457
457
458 #: templates/boards/thread.html:38
458 #: templates/boards/thread.html:38
459 msgid "image"
459 msgid "image"
460 msgid_plural "images"
460 msgid_plural "images"
461 msgstr[0] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
461 msgstr[0] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
462 msgstr[1] "изобраТСния"
462 msgstr[1] "изобраТСния"
463 msgstr[2] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
463 msgstr[2] "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
464
464
465 #: templates/boards/thread.html:40
465 #: templates/boards/thread.html:40
466 msgid "Last update: "
466 msgid "Last update: "
467 msgstr "ПослСднСС обновлСниС: "
467 msgstr "ПослСднСС обновлСниС: "
468
468
469 #: templates/boards/thread_gallery.html:36
469 #: templates/boards/thread_gallery.html:36
470 msgid "No images."
470 msgid "No images."
471 msgstr "НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ."
471 msgstr "НСт ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ."
472
472
473 #: templates/boards/thread_normal.html:30
473 #: templates/boards/thread_normal.html:30
474 msgid "posts to bumplimit"
474 msgid "posts to bumplimit"
475 msgstr "сообщСний Π΄ΠΎ Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π°"
475 msgstr "сообщСний Π΄ΠΎ Π±Π°ΠΌΠΏΠ»ΠΈΠΌΠΈΡ‚Π°"
476
476
477 #: templates/boards/thread_normal.html:44
477 #: templates/boards/thread_normal.html:44
478 msgid "Reply to thread"
478 msgid "Reply to thread"
479 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π² Ρ‚Π΅ΠΌΡƒ"
479 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π² Ρ‚Π΅ΠΌΡƒ"
480
480
481 #: templates/boards/thread_normal.html:44
481 #: templates/boards/thread_normal.html:44
482 msgid "to message "
482 msgid "to message "
483 msgstr "Π½Π° сообщСниС"
483 msgstr "Π½Π° сообщСниС"
484
484
485 #: templates/boards/thread_normal.html:59
485 #: templates/boards/thread_normal.html:59
486 msgid "Close form"
486 msgid "Close form"
487 msgstr "Π—Π°ΠΊΡ€Ρ‹Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΡƒ"
487 msgstr "Π—Π°ΠΊΡ€Ρ‹Ρ‚ΡŒ Ρ„ΠΎΡ€ΠΌΡƒ"
488
488
489 #: templates/search/search.html:17
489 #: templates/search/search.html:17
490 msgid "Ok"
490 msgid "Ok"
491 msgstr "Ок"
491 msgstr "Ок"
492
492
493 #: utils.py:120
493 #: utils.py:120
494 #, python-format
494 #, python-format
495 msgid "File must be less than %s but is %s."
495 msgid "File must be less than %s but is %s."
496 msgstr "Π€Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΌΠ΅Π½Π΅Π΅ %s, Π½ΠΎ Π΅Π³ΠΎ Ρ€Π°Π·ΠΌΠ΅Ρ€ %s."
496 msgstr "Π€Π°ΠΉΠ» Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ ΠΌΠ΅Π½Π΅Π΅ %s, Π½ΠΎ Π΅Π³ΠΎ Ρ€Π°Π·ΠΌΠ΅Ρ€ %s."
497
497
498 msgid "Please wait %(delay)d second before sending message"
498 msgid "Please wait %(delay)d second before sending message"
499 msgid_plural "Please wait %(delay)d seconds before sending message"
499 msgid_plural "Please wait %(delay)d seconds before sending message"
500 msgstr[0] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунду ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
500 msgstr[0] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунду ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
501 msgstr[1] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунды ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
501 msgstr[1] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунды ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
502 msgstr[2] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунд ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
502 msgstr[2] "ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π° ΠΏΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %(delay)d сСкунд ΠΏΠ΅Ρ€Π΅Π΄ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ сообщСния"
503
503
504 msgid "New threads"
504 msgid "New threads"
505 msgstr "НовыС Ρ‚Π΅ΠΌΡ‹"
505 msgstr "НовыС Ρ‚Π΅ΠΌΡ‹"
506
506
507 #, python-format
507 #, python-format
508 msgid "Max file size is %(size)s."
508 msgid "Max file size is %(size)s."
509 msgstr "ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° %(size)s."
509 msgstr "ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° %(size)s."
510
510
511 msgid "Size of media:"
511 msgid "Size of media:"
512 msgstr "Π Π°Π·ΠΌΠ΅Ρ€ ΠΌΠ΅Π΄ΠΈΠ°:"
512 msgstr "Π Π°Π·ΠΌΠ΅Ρ€ ΠΌΠ΅Π΄ΠΈΠ°:"
513
513
514 msgid "Statistics"
514 msgid "Statistics"
515 msgstr "Бтатистика"
515 msgstr "Бтатистика"
516
516
517 msgid "Invalid PoW."
517 msgid "Invalid PoW."
518 msgstr "НСвСрный PoW."
518 msgstr "НСвСрный PoW."
519
519
520 msgid "Stale PoW."
520 msgid "Stale PoW."
521 msgstr "PoW устарСл."
521 msgstr "PoW устарСл."
522
522
523 msgid "Show"
523 msgid "Show"
524 msgstr "ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ"
524 msgstr "ΠŸΠΎΠΊΠ°Π·Ρ‹Π²Π°Ρ‚ΡŒ"
525
525
526 msgid "Hide"
526 msgid "Hide"
527 msgstr "Π‘ΠΊΡ€Ρ‹Π²Π°Ρ‚ΡŒ"
527 msgstr "Π‘ΠΊΡ€Ρ‹Π²Π°Ρ‚ΡŒ"
528
528
529 msgid "Add to favorites"
529 msgid "Add to favorites"
530 msgstr "Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π² ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ΅"
530 msgstr "Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π² ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ΅"
531
531
532 msgid "Remove from favorites"
532 msgid "Remove from favorites"
533 msgstr "Π£Π±Ρ€Π°Ρ‚ΡŒ ΠΈΠ· ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ³ΠΎ"
533 msgstr "Π£Π±Ρ€Π°Ρ‚ΡŒ ΠΈΠ· ΠΈΠ·Π±Ρ€Π°Π½Π½ΠΎΠ³ΠΎ"
534
534
535 msgid "Monochrome"
535 msgid "Monochrome"
536 msgstr "ΠœΠΎΠ½ΠΎΡ…Ρ€ΠΎΠΌΠ½Ρ‹ΠΉ"
536 msgstr "ΠœΠΎΠ½ΠΎΡ…Ρ€ΠΎΠΌΠ½Ρ‹ΠΉ"
537
537
538 msgid "Subsections: "
538 msgid "Subsections: "
539 msgstr "ΠŸΠΎΠ΄Ρ€Π°Π·Π΄Π΅Π»Ρ‹: "
539 msgstr "ΠŸΠΎΠ΄Ρ€Π°Π·Π΄Π΅Π»Ρ‹: "
540
540
541 msgid "Change file source"
541 msgid "Change file source"
542 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ источник Ρ„Π°ΠΉΠ»Π°"
542 msgstr "Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ источник Ρ„Π°ΠΉΠ»Π°"
543
543
544 msgid "interesting"
544 msgid "interesting"
545 msgstr "интСрСсноС"
545 msgstr "интСрСсноС"
546
546
547 msgid "images"
547 msgid "images"
548 msgstr "изобраТСния"
548 msgstr "изобраТСния"
549
549
550 msgid "Delete post"
550 msgid "Delete post"
551 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ пост"
551 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ пост"
552
552
553 msgid "Delete thread"
553 msgid "Delete thread"
554 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΌΡƒ"
554 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Ρ‚Π΅ΠΌΡƒ"
555
555
556 msgid "Messages per day/week/month:"
556 msgid "Messages per day/week/month:"
557 msgstr "Π‘ΠΎΠΎΠ±Ρ‰Π΅Π½ΠΈΠΉ Π·Π° дСнь/нСдСлю/мСсяц:"
557 msgstr "Π‘ΠΎΠΎΠ±Ρ‰Π΅Π½ΠΈΠΉ Π·Π° дСнь/нСдСлю/мСсяц:"
558
558
559 msgid "Subscribe to thread"
559 msgid "Subscribe to thread"
560 msgstr "ΠŸΠΎΠ΄ΠΏΠΈΡΠ°Ρ‚ΡŒΡΡ Π½Π° Ρ‚Π΅ΠΌΡƒ"
560 msgstr "ΠŸΠΎΠ΄ΠΏΠΈΡΠ°Ρ‚ΡŒΡΡ Π½Π° Ρ‚Π΅ΠΌΡƒ"
561
561
562 msgid "Active threads:"
562 msgid "Active threads:"
563 msgstr "АктивныС Ρ‚Π΅ΠΌΡ‹:"
563 msgstr "АктивныС Ρ‚Π΅ΠΌΡ‹:"
564
564
565 msgid "No active threads today."
565 msgid "No active threads today."
566 msgstr "БСгодня Π½Π΅Ρ‚ Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ."
566 msgstr "БСгодня Π½Π΅Ρ‚ Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹Ρ… Ρ‚Π΅ΠΌ."
567
567
568 msgid "Insert URLs on separate lines."
568 msgid "Insert URLs on separate lines."
569 msgstr "ВставляйтС ссылки Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Ρ… строках."
569 msgstr "ВставляйтС ссылки Π½Π° ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Ρ… строках."
570
571 msgid "You can post no more than %(files)d file."
572 msgid_plural "You can post no more than %(files)d files."
573 msgstr[0] "Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ Π½Π΅ Π±ΠΎΠ»Π΅Π΅ %(files)d Ρ„Π°ΠΉΠ»Π°."
574 msgstr[1] "Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ Π½Π΅ Π±ΠΎΠ»Π΅Π΅ %(files)d Ρ„Π°ΠΉΠ»ΠΎΠ²."
575 msgstr[2] "Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ Π½Π΅ Π±ΠΎΠ»Π΅Π΅ %(files)d Ρ„Π°ΠΉΠ»ΠΎΠ²."
@@ -1,173 +1,190
1 var ITEM_FILE_SOURCE = 'fileSource';
1 var ITEM_FILE_SOURCE = 'fileSource';
2 var URL_STICKERS = '/api/stickers'
2 var URL_STICKERS = '/api/stickers';
3 var MIN_INPUT_LENGTH = 3;
3 var MIN_INPUT_LENGTH = 3;
4 var URL_DELIMITER = '\n';
4
5
5 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
6 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
6
7
7 $('body').on('change', 'input[name=image]', function(event) {
8 $('body').on('change', 'input[name=image]', function(event) {
8 var file = event.target.files[0];
9 var file = event.target.files[0];
9
10
10 if(file.type.match('image.*')) {
11 if(file.type.match('image.*')) {
11 var fileReader = new FileReader();
12 var fileReader = new FileReader();
12
13
13 fileReader.addEventListener("load", function(event) {
14 fileReader.addEventListener("load", function(event) {
14 var wrapper = $('.file_wrap');
15 var wrapper = $('.file_wrap');
15
16
16 wrapper.find('.file-thumb').remove();
17 wrapper.find('.file-thumb').remove();
17 wrapper.append(
18 wrapper.append(
18 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
19 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
19 );
20 );
20 });
21 });
21
22
22 fileReader.readAsDataURL(file);
23 fileReader.readAsDataURL(file);
23 }
24 }
24 });
25 });
25
26
26 var form = $('#form');
27 var form = $('#form');
27 $('textarea').keypress(function(event) {
28 $('textarea').keypress(function(event) {
28 if ((event.which == 10 || event.which == 13) && event.ctrlKey) {
29 if ((event.which == 10 || event.which == 13) && event.ctrlKey) {
29 form.find('input[type=submit]').click();
30 form.find('input[type=submit]').click();
30 }
31 }
31 });
32 });
32
33
33 $('#preview-button').click(function() {
34 $('#preview-button').click(function() {
34 var data = {
35 var data = {
35 raw_text: $('textarea').val()
36 raw_text: $('textarea').val()
36 }
37 }
37
38
38 var diffUrl = '/api/preview/';
39 var diffUrl = '/api/preview/';
39
40
40 $.post(diffUrl,
41 $.post(diffUrl,
41 data,
42 data,
42 function(data) {
43 function(data) {
43 var previewTextBlock = $('#preview-text');
44 var previewTextBlock = $('#preview-text');
44 previewTextBlock.html(data);
45 previewTextBlock.html(data);
45 previewTextBlock.show();
46 previewTextBlock.show();
46
47
47 addScriptsToPost(previewTextBlock);
48 addScriptsToPost(previewTextBlock);
48 })
49 })
49 });
50 });
50
51
51 /**
52 /**
52 * Show text in the errors row of the form.
53 * Show text in the errors row of the form.
53 * @param form
54 * @param form
54 * @param text
55 * @param text
55 */
56 */
56 function showAsErrors(form, text) {
57 function showAsErrors(form, text) {
57 form.children('.form-errors').remove();
58 form.children('.form-errors').remove();
58
59
59 if (text.length > 0) {
60 if (text.length > 0) {
60 var errorList = $('<div class="form-errors">' + text + '<div>');
61 var errorList = $('<div class="form-errors">' + text + '<div>');
61 errorList.appendTo(form);
62 errorList.appendTo(form);
62 }
63 }
63 }
64 }
64
65
65 function addHiddenInput(form, name, value) {
66 function addHiddenInput(form, name, value) {
66 form.find('input[name=' + name + ']').val(value);
67 form.find('input[name=' + name + ']').val(value);
67 }
68 }
68
69
69 function selectFileChoice() {
70 function selectFileChoice() {
70 var file_input = $('#id_file');
71 var file_input = $('#id_file');
71 var url_input = $('#id_file_url');
72 var url_input = $('#id_file_url');
72
73
73 var file_input_row = file_input.parent().parent();
74 var file_input_row = file_input.parent().parent();
74 var url_input_row = url_input.parent().parent();
75 var url_input_row = url_input.parent().parent();
75
76
76 file_input_row.toggle();
77 file_input_row.toggle();
77 url_input_row.toggle();
78 url_input_row.toggle();
78 url_input.val('');
79 url_input.val('');
79 file_input.val('');
80 file_input.val('');
80
81
81 var source;
82 var source;
82 if (file_input_row.is(':visible')) {
83 if (file_input_row.is(':visible')) {
83 source = 'file';
84 source = 'file';
84 } else {
85 } else {
85 source = 'url';
86 source = 'url';
86 }
87 }
87 localStorage.setItem(ITEM_FILE_SOURCE, source);
88 localStorage.setItem(ITEM_FILE_SOURCE, source);
88 }
89 }
89
90
90 $(document).ready(function() {
91 $(document).ready(function() {
91 var powDifficulty = parseInt($('body').attr('data-pow-difficulty'));
92 var powDifficulty = parseInt($('body').attr('data-pow-difficulty'));
92 if (powDifficulty > 0 && typeof SharedWorker != 'undefined') {
93 if (powDifficulty > 0 && typeof SharedWorker != 'undefined') {
93 var worker = new SharedWorker($('.post-form').attr('data-pow-script'));
94 var worker = new SharedWorker($('.post-form').attr('data-pow-script'));
94 worker.port.onmessage = function(e) {
95 worker.port.onmessage = function(e) {
95 var form = $('#form');
96 var form = $('#form');
96 addHiddenInput(form, 'timestamp', e.data.timestamp);
97 addHiddenInput(form, 'timestamp', e.data.timestamp);
97 addHiddenInput(form, 'iteration', e.data.iteration);
98 addHiddenInput(form, 'iteration', e.data.iteration);
98 addHiddenInput(form, 'guess', e.data.guess);
99 addHiddenInput(form, 'guess', e.data.guess);
99
100
100 form.submit();
101 form.submit();
101 $('.post-form-w').unblock();
102 $('.post-form-w').unblock();
102 };
103 };
103 worker.onerror = function(event){
104 worker.onerror = function(event){
104 throw new Error(event.message + " (" + event.filename + ":" + event.lineno + ")");
105 throw new Error(event.message + " (" + event.filename + ":" + event.lineno + ")");
105 };
106 };
106 worker.port.start();
107 worker.port.start();
107
108
108 var form = $('#form');
109 var form = $('#form');
109 var submitButton = form.find('input[type=submit]');
110 var submitButton = form.find('input[type=submit]');
110 submitButton.click(function() {
111 submitButton.click(function() {
111 showAsErrors(form, gettext('Computing PoW...'));
112 showAsErrors(form, gettext('Computing PoW...'));
112 $('.post-form-w').block({ message: gettext('Computing PoW...') })
113 $('.post-form-w').block({ message: gettext('Computing PoW...') })
113
114
114 var msg = $('textarea').val().trim();
115 var msg = $('textarea').val().trim();
115
116
116 var data = {
117 var data = {
117 msg: msg,
118 msg: msg,
118 difficulty: parseInt($('body').attr('data-pow-difficulty')),
119 difficulty: parseInt($('body').attr('data-pow-difficulty')),
119 hasher: $('.post-form').attr('data-hasher')
120 hasher: $('.post-form').attr('data-hasher')
120 };
121 };
121 worker.port.postMessage(data);
122 worker.port.postMessage(data);
122
123
123 return false;
124 return false;
124 });
125 });
125 }
126 }
126
127
127 var $fileSourceButton = $('#file-source-button');
128 var $fileSourceButton = $('#file-source-button');
128 if (window.localStorage) {
129 if (window.localStorage) {
129 var source = localStorage.getItem(ITEM_FILE_SOURCE);
130 var source = localStorage.getItem(ITEM_FILE_SOURCE);
130 if (source == null) {
131 if (source == null) {
131 source = 'file';
132 source = 'file';
132 }
133 }
133 if (source == 'file') {
134 if (source == 'file') {
134 $('#id_file_url').parent().parent().hide();
135 $('#id_file_url').parent().parent().hide();
135 } else {
136 } else {
136 $('#id_file').parent().parent().hide();
137 $('#id_file').parent().parent().hide();
137 }
138 }
138
139
139 $fileSourceButton.click(function() {
140 $fileSourceButton.click(function() {
140 selectFileChoice();
141 selectFileChoice();
141 });
142 });
142 } else {
143 } else {
143 $fileSourceButton.hide();
144 $fileSourceButton.hide();
144 }
145 }
145
146
146 $('#id_file_url').autocomplete({
147 // Stickers autocomplete
148 function split( val ) {
149 return val.split(URL_DELIMITER);
150 }
151
152 function extractLast( term ) {
153 return split(term).pop();
154 }
155
156 $('#id_file_1').autocomplete({
147 source: function( request, response ) {
157 source: function( request, response ) {
148 $.getJSON(URL_STICKERS, {
158 $.getJSON(URL_STICKERS, {
149 term: request.term
159 term: extractLast( request.term )
150 }, response);
160 }, response);
151 },
161 },
152 search: function() {
162 search: function() {
153 // custom minLength
163 // custom minLength
154 var term = this.value;
164 var term = extractLast( this.value );
155 if (term.length < MIN_INPUT_LENGTH) {
165 if (term.length < MIN_INPUT_LENGTH) {
156 return false;
166 return false;
157 }
167 }
158 },
168 },
159 focus: function() {
169 focus: function() {
160 // prevent value inserted on focus
170 // prevent value inserted on focus
161 return false;
171 return false;
162 },
172 },
163 select: function( event, ui ) {
173 select: function( event, ui ) {
164 this.value = ui.item.alias;
174 var terms = split( this.value );
175 // remove the current input
176 terms.pop();
177 // add the selected item
178 terms.push( ui.item.alias );
179 // add placeholder to get the comma-and-space at the end
180 terms.push("");
181 this.value = terms.join(URL_DELIMITER);
165 return false;
182 return false;
166 }
183 }
167 })
184 })
168 .autocomplete( "instance" )._renderItem = function( ul, item ) {
185 .autocomplete( "instance" )._renderItem = function( ul, item ) {
169 return $( "<li>" )
186 return $( "<li>" )
170 .append( "<div>" + '<img src="' + item.thumb + '">' + '<br />' + item.alias + "</div>" )
187 .append( "<div>" + '<img src="' + item.thumb + '">' + '<br />' + item.alias + "</div>" )
171 .appendTo( ul );
188 .appendTo( ul );
172 };
189 };
173 });
190 });
General Comments 0
You need to be logged in to leave comments. Login now