##// END OF EJS Templates
Checking file uniqueness is not an image download mode
neko259 -
r1959:09e0382a default
parent child Browse files
Show More
@@ -1,590 +1,591 b''
1 1 import hashlib
2 2 import logging
3 3 import re
4 4 import time
5 5
6 6 import pytz
7 7
8 8 from PIL import Image
9 9
10 10 from django import forms
11 11 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
12 12 from django.forms.utils import ErrorList
13 13 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
14 14 from django.core.files.images import get_image_dimensions
15 15 from django.core.cache import cache
16 16
17 17 import boards.settings as board_settings
18 18 import neboard
19 19 from boards import utils
20 20 from boards.abstracts.constants import REGEX_TAGS
21 21 from boards.abstracts.sticker_factory import get_attachment_by_alias
22 22 from boards.abstracts.settingsmanager import get_settings_manager
23 23 from boards.forms.fields import UrlFileField
24 24 from boards.mdx_neboard import formatters
25 25 from boards.models import Attachment
26 26 from boards.models import Tag
27 27 from boards.models.attachment import StickerPack
28 28 from boards.models.attachment.downloaders import download, REGEX_MAGNET
29 29 from boards.models.post import TITLE_MAX_LENGTH
30 30 from boards.utils import validate_file_size, get_file_mimetype, \
31 31 FILE_EXTENSION_DELIMITER
32 32 from boards.models.attachment.viewers import FILE_TYPES_IMAGE
33 33 from neboard import settings
34 34
35 35 SECTION_FORMS = 'Forms'
36 36
37 37 POW_HASH_LENGTH = 16
38 38 POW_LIFE_MINUTES = 5
39 39
40 40 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
41 41 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
42 42
43 43 VETERAN_POSTING_DELAY = 5
44 44
45 45 ATTRIBUTE_PLACEHOLDER = 'placeholder'
46 46 ATTRIBUTE_ROWS = 'rows'
47 47
48 48 LAST_POST_TIME = 'last_post_time'
49 49 LAST_LOGIN_TIME = 'last_login_time'
50 50 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
51 51 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
52 52
53 53 LABEL_TITLE = _('Title')
54 54 LABEL_TEXT = _('Text')
55 55 LABEL_TAG = _('Tag')
56 56 LABEL_SEARCH = _('Search')
57 57 LABEL_FILE = _('File')
58 58 LABEL_DUPLICATES = _('Check for duplicates')
59 59 LABEL_URL = _('Do not download URLs')
60 60
61 61 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
62 62 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
63 63 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
64 64 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
65 65 ERROR_DUPLICATES = 'Some files are already present on the board.'
66 66
67 67 TAG_MAX_LENGTH = 20
68 68
69 69 TEXTAREA_ROWS = 4
70 70
71 71 TRIPCODE_DELIM = '##'
72 72
73 73 # TODO Maybe this may be converted into the database table?
74 74 MIMETYPE_EXTENSIONS = {
75 75 'image/jpeg': 'jpeg',
76 76 'image/png': 'png',
77 77 'image/gif': 'gif',
78 78 'video/webm': 'webm',
79 79 'application/pdf': 'pdf',
80 80 'x-diff': 'diff',
81 81 'image/svg+xml': 'svg',
82 82 'application/x-shockwave-flash': 'swf',
83 83 'image/x-ms-bmp': 'bmp',
84 84 'image/bmp': 'bmp',
85 85 }
86 86
87 87 DOWN_MODE_DOWNLOAD = 'DOWNLOAD'
88 DOWN_MODE_DOWNLOAD_UNIQUE = 'DOWNLOAD_UNIQUE'
88 89 DOWN_MODE_URL = 'URL'
89 90 DOWN_MODE_TRY = 'TRY'
90 91
91 92
92 93 logger = logging.getLogger('boards.forms')
93 94
94 95
95 96 def get_timezones():
96 97 timezones = []
97 98 for tz in pytz.common_timezones:
98 99 timezones.append((tz, tz),)
99 100 return timezones
100 101
101 102
102 103 class FormatPanel(forms.Textarea):
103 104 """
104 105 Panel for text formatting. Consists of buttons to add different tags to the
105 106 form text area.
106 107 """
107 108
108 109 def render(self, name, value, attrs=None):
109 110 output = '<div id="mark-panel">'
110 111 for formatter in formatters:
111 112 output += '<span class="mark_btn"' + \
112 113 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
113 114 '\', \'' + formatter.format_right + '\')">' + \
114 115 formatter.preview_left + formatter.name + \
115 116 formatter.preview_right + '</span>'
116 117
117 118 output += '</div>'
118 119 output += super(FormatPanel, self).render(name, value, attrs=attrs)
119 120
120 121 return output
121 122
122 123
123 124 class PlainErrorList(ErrorList):
124 125 def __unicode__(self):
125 126 return self.as_text()
126 127
127 128 def as_text(self):
128 129 return ''.join(['(!) %s ' % e for e in self])
129 130
130 131
131 132 class NeboardForm(forms.Form):
132 133 """
133 134 Form with neboard-specific formatting.
134 135 """
135 136 required_css_class = 'required-field'
136 137
137 138 def as_div(self):
138 139 """
139 140 Returns this form rendered as HTML <as_div>s.
140 141 """
141 142
142 143 return self._html_output(
143 144 # TODO Do not show hidden rows in the list here
144 145 normal_row='<div class="form-row">'
145 146 '<div class="form-label">'
146 147 '%(label)s'
147 148 '</div>'
148 149 '<div class="form-input">'
149 150 '%(field)s'
150 151 '</div>'
151 152 '</div>'
152 153 '<div class="form-row">'
153 154 '%(help_text)s'
154 155 '</div>',
155 156 error_row='<div class="form-row">'
156 157 '<div class="form-label"></div>'
157 158 '<div class="form-errors">%s</div>'
158 159 '</div>',
159 160 row_ender='</div>',
160 161 help_text_html='%s',
161 162 errors_on_separate_row=True)
162 163
163 164 def as_json_errors(self):
164 165 errors = []
165 166
166 167 for name, field in list(self.fields.items()):
167 168 if self[name].errors:
168 169 errors.append({
169 170 'field': name,
170 171 'errors': self[name].errors.as_text(),
171 172 })
172 173
173 174 return errors
174 175
175 176
176 177 class PostForm(NeboardForm):
177 178
178 179 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
179 180 label=LABEL_TITLE,
180 181 widget=forms.TextInput(
181 182 attrs={ATTRIBUTE_PLACEHOLDER: 'Title{}tripcode'.format(TRIPCODE_DELIM)}))
182 183 text = forms.CharField(
183 184 widget=FormatPanel(attrs={
184 185 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
185 186 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
186 187 }),
187 188 required=False, label=LABEL_TEXT)
188 189 download_mode = forms.ChoiceField(
189 190 choices=(
190 (DOWN_MODE_TRY, _('Download or add URL')),
191 (DOWN_MODE_DOWNLOAD, _('Download or fail')),
191 (DOWN_MODE_TRY, _('Download or insert as URLs')),
192 (DOWN_MODE_DOWNLOAD, _('Download')),
193 (DOWN_MODE_DOWNLOAD_UNIQUE, _('Download and check for uniqueness')),
192 194 (DOWN_MODE_URL, _('Insert as URLs')),
193 195 ),
194 196 initial=DOWN_MODE_TRY,
195 label=_('URL download mode'))
197 label=_('File process mode'))
196 198 file = UrlFileField(required=False, label=LABEL_FILE)
197 199
198 200 # This field is for spam prevention only
199 201 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
200 202 widget=forms.TextInput(attrs={
201 203 'class': 'form-email'}))
202 204 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
203 check_duplicates = forms.BooleanField(required=False, label=LABEL_DUPLICATES)
204 205
205 206 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
206 207 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
207 208 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
208 209
209 210 session = None
210 211 need_to_ban = False
211 212
212 213 def clean_title(self):
213 214 title = self.cleaned_data['title']
214 215 if title:
215 216 if len(title) > TITLE_MAX_LENGTH:
216 217 raise forms.ValidationError(_('Title must have less than %s '
217 218 'characters') %
218 219 str(TITLE_MAX_LENGTH))
219 220 return title
220 221
221 222 def clean_text(self):
222 223 text = self.cleaned_data['text'].strip()
223 224 if text:
224 225 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
225 226 if len(text) > max_length:
226 227 raise forms.ValidationError(_('Text must have less than %s '
227 228 'characters') % str(max_length))
228 229 return text
229 230
230 231 def clean_file(self):
231 232 return self._clean_files(self.cleaned_data['file'])
232 233
233 234 def clean(self):
234 235 cleaned_data = super(PostForm, self).clean()
235 236
236 237 if cleaned_data['email']:
237 238 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
238 239 self.need_to_ban = True
239 240 raise forms.ValidationError('A human cannot enter a hidden field')
240 241
241 242 if not self.errors:
242 243 self._clean_text_file()
243 244
244 245 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
245 246 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
246 247
247 248 settings_manager = get_settings_manager(self)
248 249 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting('confirmed_user')):
249 250 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
250 251 if pow_difficulty > 0:
251 252 # PoW-based
252 253 if cleaned_data['timestamp'] \
253 254 and cleaned_data['iteration'] and cleaned_data['guess'] \
254 255 and not settings_manager.get_setting('confirmed_user'):
255 256 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
256 257 else:
257 258 # Time-based
258 259 self._validate_posting_speed()
259 260 settings_manager.set_setting('confirmed_user', True)
260 if self.cleaned_data['check_duplicates']:
261 if self.cleaned_data['download_mode'] == DOWN_MODE_DOWNLOAD_UNIQUE:
261 262 self._check_file_duplicates(self.get_files())
262 263
263 264 return cleaned_data
264 265
265 266 def get_files(self):
266 267 """
267 268 Gets file from form or URL.
268 269 """
269 270
270 271 files = []
271 272 for file in self.cleaned_data['file']:
272 273 if isinstance(file, UploadedFile):
273 274 files.append(file)
274 275
275 276 return files
276 277
277 278 def get_file_urls(self):
278 279 files = []
279 280 for file in self.cleaned_data['file']:
280 281 if type(file) == str:
281 282 files.append(file)
282 283
283 284 return files
284 285
285 286 def get_tripcode(self):
286 287 title = self.cleaned_data['title']
287 288 if title is not None and TRIPCODE_DELIM in title:
288 289 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
289 290 tripcode = hashlib.md5(code.encode()).hexdigest()
290 291 else:
291 292 tripcode = ''
292 293 return tripcode
293 294
294 295 def get_title(self):
295 296 title = self.cleaned_data['title']
296 297 if title is not None and TRIPCODE_DELIM in title:
297 298 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
298 299 else:
299 300 return title
300 301
301 302 def get_images(self):
302 303 return self.cleaned_data.get('stickers', [])
303 304
304 305 def is_subscribe(self):
305 306 return self.cleaned_data['subscribe']
306 307
307 308 def _update_file_extension(self, file):
308 309 if file:
309 310 mimetype = get_file_mimetype(file)
310 311 extension = MIMETYPE_EXTENSIONS.get(mimetype)
311 312 if extension:
312 313 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
313 314 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
314 315
315 316 file.name = new_filename
316 317 else:
317 318 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
318 319
319 320 def _clean_files(self, inputs):
320 321 files = []
321 322
322 323 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
323 324 if len(inputs) > max_file_count:
324 325 raise forms.ValidationError(
325 326 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
326 327 max_file_count) % {'files': max_file_count})
327 328 for file_input in inputs:
328 329 if isinstance(file_input, UploadedFile):
329 330 files.append(self._clean_file_file(file_input))
330 331 else:
331 332 files.append(self._clean_file_url(file_input))
332 333
333 334 for file in files:
334 335 self._validate_image_dimensions(file)
335 336
336 337 return files
337 338
338 339 def _validate_image_dimensions(self, file):
339 340 if isinstance(file, UploadedFile):
340 341 mimetype = get_file_mimetype(file)
341 342 if mimetype.split('/')[-1] in FILE_TYPES_IMAGE:
342 343 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
343 344 try:
344 345 print(get_image_dimensions(file))
345 346 except Exception:
346 347 raise forms.ValidationError('Possible decompression bomb or large image.')
347 348
348 349 def _clean_file_file(self, file):
349 350 validate_file_size(file.size)
350 351 self._update_file_extension(file)
351 352
352 353 return file
353 354
354 355 def _clean_file_url(self, url):
355 356 file = None
356 357
357 358 if url:
358 359 mode = self.cleaned_data['download_mode']
359 360 if mode == DOWN_MODE_URL:
360 361 return url
361 362
362 363 try:
363 364 image = get_attachment_by_alias(url, self.session)
364 365 if image is not None:
365 366 if 'stickers' not in self.cleaned_data:
366 367 self.cleaned_data['stickers'] = []
367 368 self.cleaned_data['stickers'].append(image)
368 369 return
369 370
370 371 if file is None:
371 372 file = self._get_file_from_url(url)
372 373 if not file:
373 374 raise forms.ValidationError(_('Invalid URL'))
374 375 else:
375 376 validate_file_size(file.size)
376 377 self._update_file_extension(file)
377 378 except forms.ValidationError as e:
378 379 # Assume we will get the plain URL instead of a file and save it
379 380 if mode == DOWN_MODE_TRY and (REGEX_URL.match(url) or REGEX_MAGNET.match(url)):
380 381 logger.info('Error in forms: {}'.format(e))
381 382 return url
382 383 else:
383 384 raise e
384 385
385 386 return file
386 387
387 388 def _clean_text_file(self):
388 389 text = self.cleaned_data.get('text')
389 390 file = self.get_files()
390 391 file_url = self.get_file_urls()
391 392 images = self.get_images()
392 393
393 394 if (not text) and (not file) and (not file_url) and len(images) == 0:
394 395 error_message = _('Either text or file must be entered.')
395 396 self._add_general_error(error_message)
396 397
397 398 def _get_cache_key(self, key):
398 399 return '{}_{}'.format(self.session.session_key, key)
399 400
400 401 def _set_session_cache(self, key, value):
401 402 cache.set(self._get_cache_key(key), value)
402 403
403 404 def _get_session_cache(self, key):
404 405 return cache.get(self._get_cache_key(key))
405 406
406 407 def _get_last_post_time(self):
407 408 last = self._get_session_cache(LAST_POST_TIME)
408 409 if last is None:
409 410 last = self.session.get(LAST_POST_TIME)
410 411 return last
411 412
412 413 def _validate_posting_speed(self):
413 414 can_post = True
414 415
415 416 posting_delay = board_settings.get_int(SECTION_FORMS, 'PostingDelay')
416 417
417 418 if board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed'):
418 419 now = time.time()
419 420
420 421 current_delay = 0
421 422
422 423 if LAST_POST_TIME not in self.session:
423 424 self.session[LAST_POST_TIME] = now
424 425
425 426 need_delay = True
426 427 else:
427 428 last_post_time = self._get_last_post_time()
428 429 current_delay = int(now - last_post_time)
429 430
430 431 need_delay = current_delay < posting_delay
431 432
432 433 self._set_session_cache(LAST_POST_TIME, now)
433 434
434 435 if need_delay:
435 436 delay = posting_delay - current_delay
436 437 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
437 438 delay) % {'delay': delay}
438 439 self._add_general_error(error_message)
439 440
440 441 can_post = False
441 442
442 443 if can_post:
443 444 self.session[LAST_POST_TIME] = now
444 445 else:
445 446 # Reset the time since posting failed
446 447 self._set_session_cache(LAST_POST_TIME, self.session[LAST_POST_TIME])
447 448
448 449 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
449 450 """
450 451 Gets an file file from URL.
451 452 """
452 453
453 454 try:
454 455 return download(url)
455 456 except forms.ValidationError as e:
456 457 raise e
457 458 except Exception as e:
458 459 raise forms.ValidationError(e)
459 460
460 461 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
461 462 payload = timestamp + message.replace('\r\n', '\n')
462 463 difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
463 464 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
464 465 if len(target) < POW_HASH_LENGTH:
465 466 target = '0' * (POW_HASH_LENGTH - len(target)) + target
466 467
467 468 computed_guess = hashlib.sha256((payload + iteration).encode())\
468 469 .hexdigest()[0:POW_HASH_LENGTH]
469 470 if guess != computed_guess or guess > target:
470 471 self._add_general_error(_('Invalid PoW.'))
471 472
472 473 def _check_file_duplicates(self, files):
473 474 for file in files:
474 475 file_hash = utils.get_file_hash(file)
475 476 if Attachment.objects.get_existing_duplicate(file_hash, file):
476 477 self._add_general_error(_(ERROR_DUPLICATES))
477 478
478 479 def _add_general_error(self, message):
479 480 self.add_error('text', forms.ValidationError(message))
480 481
481 482
482 483 class ThreadForm(PostForm):
483 484
484 485 tags = forms.CharField(
485 486 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
486 487 max_length=100, label=_('Tags'), required=True)
487 488 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
488 489 stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False)
489 490
490 491 def clean_tags(self):
491 492 tags = self.cleaned_data['tags'].strip()
492 493
493 494 if not tags or not REGEX_TAGS.match(tags):
494 495 raise forms.ValidationError(
495 496 _('Inappropriate characters in tags.'))
496 497
497 498 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
498 499 .strip().lower()
499 500
500 501 required_tag_exists = False
501 502 tag_set = set()
502 503 for tag_string in tags.split():
503 504 tag_name = tag_string.strip().lower()
504 505 if tag_name == default_tag_name:
505 506 required_tag_exists = True
506 507 tag, created = Tag.objects.get_or_create_with_alias(
507 508 name=tag_name, required=True)
508 509 else:
509 510 tag, created = Tag.objects.get_or_create_with_alias(name=tag_name)
510 511 tag_set.add(tag)
511 512
512 513 # If this is a new tag, don't check for its parents because nobody
513 514 # added them yet
514 515 if not created:
515 516 tag_set |= set(tag.get_all_parents())
516 517
517 518 for tag in tag_set:
518 519 if tag.required:
519 520 required_tag_exists = True
520 521 break
521 522
522 523 # Use default tag if no section exists
523 524 if not required_tag_exists:
524 525 default_tag, created = Tag.objects.get_or_create_with_alias(
525 526 name=default_tag_name, required=True)
526 527 tag_set.add(default_tag)
527 528
528 529 return tag_set
529 530
530 531 def clean(self):
531 532 cleaned_data = super(ThreadForm, self).clean()
532 533
533 534 return cleaned_data
534 535
535 536 def is_monochrome(self):
536 537 return self.cleaned_data['monochrome']
537 538
538 539 def clean_stickerpack(self):
539 540 stickerpack = self.cleaned_data['stickerpack']
540 541 if stickerpack:
541 542 tripcode = self.get_tripcode()
542 543 if not tripcode:
543 544 raise forms.ValidationError(_(
544 545 'Tripcode should be specified to own a stickerpack.'))
545 546 title = self.get_title()
546 547 if not title:
547 548 raise forms.ValidationError(_(
548 549 'Title should be specified as a stickerpack name.'))
549 550 if not REGEX_TAGS.match(title):
550 551 raise forms.ValidationError(_('Inappropriate sticker pack name.'))
551 552
552 553 existing_pack = StickerPack.objects.filter(name=title).first()
553 554 if existing_pack:
554 555 if existing_pack.tripcode != tripcode:
555 556 raise forms.ValidationError(_(
556 557 'A sticker pack with this name already exists and is'
557 558 ' owned by another tripcode.'))
558 559 if not existing_pack.tripcode:
559 560 raise forms.ValidationError(_(
560 561 'This sticker pack can only be updated by an '
561 562 'administrator.'))
562 563
563 564 return stickerpack
564 565
565 566 def is_stickerpack(self):
566 567 return self.cleaned_data['stickerpack']
567 568
568 569
569 570 class SettingsForm(NeboardForm):
570 571
571 572 theme = forms.ChoiceField(
572 573 choices=board_settings.get_list_dict('View', 'Themes'),
573 574 label=_('Theme'))
574 575 image_viewer = forms.ChoiceField(
575 576 choices=board_settings.get_list_dict('View', 'ImageViewers'),
576 577 label=_('Image view mode'))
577 578 username = forms.CharField(label=_('User name'), required=False)
578 579 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
579 580
580 581 def clean_username(self):
581 582 username = self.cleaned_data['username']
582 583
583 584 if username and not REGEX_USERNAMES.match(username):
584 585 raise forms.ValidationError(_('Inappropriate characters.'))
585 586
586 587 return username
587 588
588 589
589 590 class SearchForm(NeboardForm):
590 591 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
1 NO CONTENT: modified file, binary diff hidden
@@ -1,642 +1,645 b''
1 1 # SOME DESCRIPTIVE TITLE.
2 2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 3 # This file is distributed under the same license as the PACKAGE package.
4 4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 5 #
6 6 msgid ""
7 7 msgstr ""
8 8 "Project-Id-Version: PACKAGE VERSION\n"
9 9 "Report-Msgid-Bugs-To: \n"
10 10 "POT-Creation-Date: 2015-10-09 23:21+0300\n"
11 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 14 "Language: ru\n"
15 15 "MIME-Version: 1.0\n"
16 16 "Content-Type: text/plain; charset=UTF-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20 20
21 21 #: admin.py:22
22 22 msgid "{} posters were banned"
23 23 msgstr ""
24 24
25 25 #: authors.py:9
26 26 msgid "author"
27 27 msgstr "автор"
28 28
29 29 #: authors.py:10
30 30 msgid "developer"
31 31 msgstr "разработчик"
32 32
33 33 #: authors.py:11
34 34 msgid "javascript developer"
35 35 msgstr "разработчик javascript"
36 36
37 37 #: authors.py:12
38 38 msgid "designer"
39 39 msgstr "дизайнер"
40 40
41 41 #: forms.py:30
42 42 msgid "Type message here. Use formatting panel for more advanced usage."
43 43 msgstr ""
44 44 "Вводите сообщение сюда. Используйте панель для более сложного форматирования."
45 45
46 46 #: forms.py:31
47 47 msgid "music images i_dont_like_tags"
48 48 msgstr "музыка картинки теги_не_нужны"
49 49
50 50 #: forms.py:33
51 51 msgid "Title"
52 52 msgstr "Заголовок"
53 53
54 54 #: forms.py:34
55 55 msgid "Text"
56 56 msgstr "Текст"
57 57
58 58 #: forms.py:35
59 59 msgid "Tag"
60 60 msgstr "Метка"
61 61
62 62 #: forms.py:36 templates/boards/base.html:40 templates/search/search.html:7
63 63 msgid "Search"
64 64 msgstr "Поиск"
65 65
66 66 #: forms.py:48
67 67 msgid "File 1"
68 68 msgstr "Файл 1"
69 69
70 70 #: forms.py:48
71 71 msgid "File 2"
72 72 msgstr "Файл 2"
73 73
74 74 #: forms.py:142
75 75 msgid "File URL"
76 76 msgstr "URL файла"
77 77
78 78 #: forms.py:148
79 79 msgid "e-mail"
80 80 msgstr ""
81 81
82 82 #: forms.py:151
83 83 msgid "Additional threads"
84 84 msgstr "Дополнительные темы"
85 85
86 86 #: forms.py:162
87 87 #, python-format
88 88 msgid "Title must have less than %s characters"
89 89 msgstr "Заголовок должен иметь меньше %s символов"
90 90
91 91 #: forms.py:172
92 92 #, python-format
93 93 msgid "Text must have less than %s characters"
94 94 msgstr "Текст должен быть короче %s символов"
95 95
96 96 #: forms.py:192
97 97 msgid "Invalid URL"
98 98 msgstr "Неверный URL"
99 99
100 100 #: forms.py:213
101 101 msgid "Invalid additional thread list"
102 102 msgstr "Неверный список дополнительных тем"
103 103
104 104 #: forms.py:258
105 105 msgid "Either text or file must be entered."
106 106 msgstr "Текст или файл должны быть введены."
107 107
108 108 #: forms.py:317 templates/boards/all_threads.html:153
109 109 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
110 110 msgid "Tags"
111 111 msgstr "Метки"
112 112
113 113 #: forms.py:324
114 114 msgid "Inappropriate characters in tags."
115 115 msgstr "Недопустимые символы в метках."
116 116
117 117 #: forms.py:344
118 118 msgid "Need at least one section."
119 119 msgstr "Нужен хотя бы один раздел."
120 120
121 121 #: forms.py:356
122 122 msgid "Theme"
123 123 msgstr "Тема"
124 124
125 125 #: forms.py:357
126 126 msgid "Image view mode"
127 127 msgstr "Режим просмотра изображений"
128 128
129 129 #: forms.py:358
130 130 msgid "User name"
131 131 msgstr "Имя пользователя"
132 132
133 133 #: forms.py:359
134 134 msgid "Time zone"
135 135 msgstr "Часовой пояс"
136 136
137 137 #: forms.py:365
138 138 msgid "Inappropriate characters."
139 139 msgstr "Недопустимые символы."
140 140
141 141 #: templates/boards/404.html:6
142 142 msgid "Not found"
143 143 msgstr "Не найдено"
144 144
145 145 #: templates/boards/404.html:12
146 146 msgid "This page does not exist"
147 147 msgstr "Этой страницы не существует"
148 148
149 149 #: templates/boards/all_threads.html:35
150 150 msgid "Details"
151 151 msgstr "Подробности"
152 152
153 153 #: templates/boards/all_threads.html:69
154 154 msgid "Edit tag"
155 155 msgstr "Изменить метку"
156 156
157 157 #: templates/boards/all_threads.html:76
158 158 #, python-format
159 159 msgid "%(count)s active thread"
160 160 msgid_plural "%(count)s active threads"
161 161 msgstr[0] "%(count)s активная тема"
162 162 msgstr[1] "%(count)s активные темы"
163 163 msgstr[2] "%(count)s активных тем"
164 164
165 165 #: templates/boards/all_threads.html:76
166 166 #, python-format
167 167 msgid "%(count)s thread in bumplimit"
168 168 msgid_plural "%(count)s threads in bumplimit"
169 169 msgstr[0] "%(count)s тема в бамплимите"
170 170 msgstr[1] "%(count)s темы в бамплимите"
171 171 msgstr[2] "%(count)s тем в бамплимите"
172 172
173 173 #: templates/boards/all_threads.html:77
174 174 #, python-format
175 175 msgid "%(count)s archived thread"
176 176 msgid_plural "%(count)s archived thread"
177 177 msgstr[0] "%(count)s архивная тема"
178 178 msgstr[1] "%(count)s архивные темы"
179 179 msgstr[2] "%(count)s архивных тем"
180 180
181 181 #: templates/boards/all_threads.html:78 templates/boards/post.html:102
182 182 #, python-format
183 183 #| msgid "%(count)s message"
184 184 #| msgid_plural "%(count)s messages"
185 185 msgid "%(count)s message"
186 186 msgid_plural "%(count)s messages"
187 187 msgstr[0] "%(count)s сообщение"
188 188 msgstr[1] "%(count)s сообщения"
189 189 msgstr[2] "%(count)s сообщений"
190 190
191 191 #: templates/boards/all_threads.html:95 templates/boards/feed.html:30
192 192 #: templates/boards/notifications.html:17 templates/search/search.html:26
193 193 msgid "Previous page"
194 194 msgstr "Предыдущая страница"
195 195
196 196 #: templates/boards/all_threads.html:109
197 197 #, python-format
198 198 msgid "Skipped %(count)s reply. Open thread to see all replies."
199 199 msgid_plural "Skipped %(count)s replies. Open thread to see all replies."
200 200 msgstr[0] "Пропущен %(count)s ответ. Откройте тред, чтобы увидеть все ответы."
201 201 msgstr[1] ""
202 202 "Пропущено %(count)s ответа. Откройте тред, чтобы увидеть все ответы."
203 203 msgstr[2] ""
204 204 "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
205 205
206 206 #: templates/boards/all_threads.html:127 templates/boards/feed.html:40
207 207 #: templates/boards/notifications.html:27 templates/search/search.html:37
208 208 msgid "Next page"
209 209 msgstr "Следующая страница"
210 210
211 211 #: templates/boards/all_threads.html:132
212 212 msgid "No threads exist. Create the first one!"
213 213 msgstr "Нет тем. Создайте первую!"
214 214
215 215 #: templates/boards/all_threads.html:138
216 216 msgid "Create new thread"
217 217 msgstr "Создать новую тему"
218 218
219 219 #: templates/boards/all_threads.html:143 templates/boards/preview.html:16
220 220 #: templates/boards/thread_normal.html:51
221 221 msgid "Post"
222 222 msgstr "Отправить"
223 223
224 224 #: templates/boards/all_threads.html:144 templates/boards/preview.html:6
225 225 #: templates/boards/staticpages/help.html:21
226 226 #: templates/boards/thread_normal.html:52
227 227 msgid "Preview"
228 228 msgstr "Предпросмотр"
229 229
230 230 #: templates/boards/all_threads.html:149
231 231 msgid "Tags must be delimited by spaces. Text or image is required."
232 232 msgstr ""
233 233 "Метки должны быть разделены пробелами. Текст или изображение обязательны."
234 234
235 235 #: templates/boards/all_threads.html:152 templates/boards/thread_normal.html:58
236 236 msgid "Text syntax"
237 237 msgstr "Синтаксис текста"
238 238
239 239 #: templates/boards/all_threads.html:166 templates/boards/feed.html:53
240 240 msgid "Pages:"
241 241 msgstr "Страницы: "
242 242
243 243 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
244 244 msgid "Authors"
245 245 msgstr "Авторы"
246 246
247 247 #: templates/boards/authors.html:26
248 248 msgid "Distributed under the"
249 249 msgstr "Распространяется под"
250 250
251 251 #: templates/boards/authors.html:28
252 252 msgid "license"
253 253 msgstr "лицензией"
254 254
255 255 #: templates/boards/authors.html:30
256 256 msgid "Repository"
257 257 msgstr "Репозиторий"
258 258
259 259 #: templates/boards/base.html:14 templates/boards/base.html.py:41
260 260 msgid "Feed"
261 261 msgstr "Лента"
262 262
263 263 #: templates/boards/base.html:31
264 264 msgid "All threads"
265 265 msgstr "Все темы"
266 266
267 267 #: templates/boards/base.html:37
268 268 msgid "Add tags"
269 269 msgstr "Добавить метки"
270 270
271 271 #: templates/boards/base.html:39
272 272 msgid "Tag management"
273 273 msgstr "Управление метками"
274 274
275 275 #: templates/boards/base.html:39
276 276 msgid "tags"
277 277 msgstr "метки"
278 278
279 279 #: templates/boards/base.html:40
280 280 msgid "search"
281 281 msgstr "поиск"
282 282
283 283 #: templates/boards/base.html:41 templates/boards/feed.html:11
284 284 msgid "feed"
285 285 msgstr "лента"
286 286
287 287 #: templates/boards/base.html:42 templates/boards/random.html:6
288 288 msgid "Random images"
289 289 msgstr "Случайные изображения"
290 290
291 291 #: templates/boards/base.html:42
292 292 msgid "random"
293 293 msgstr "случайные"
294 294
295 295 #: templates/boards/base.html:44
296 296 msgid "favorites"
297 297 msgstr "избранное"
298 298
299 299 #: templates/boards/base.html:48 templates/boards/base.html.py:49
300 300 #: templates/boards/notifications.html:8
301 301 msgid "Notifications"
302 302 msgstr "Уведомления"
303 303
304 304 #: templates/boards/base.html:56 templates/boards/settings.html:8
305 305 msgid "Settings"
306 306 msgstr "Настройки"
307 307
308 308 #: templates/boards/base.html:59
309 309 msgid "Loading..."
310 310 msgstr "Загрузка..."
311 311
312 312 #: templates/boards/base.html:71
313 313 msgid "Admin"
314 314 msgstr "Администрирование"
315 315
316 316 #: templates/boards/base.html:73
317 317 #, python-format
318 318 msgid "Speed: %(ppd)s posts per day"
319 319 msgstr "Скорость: %(ppd)s сообщений в день"
320 320
321 321 #: templates/boards/base.html:75
322 322 msgid "Up"
323 323 msgstr "Вверх"
324 324
325 325 #: templates/boards/feed.html:45
326 326 msgid "No posts exist. Create the first one!"
327 327 msgstr "Нет сообщений. Создайте первое!"
328 328
329 329 #: templates/boards/post.html:33
330 330 msgid "Open"
331 331 msgstr "Открыть"
332 332
333 333 #: templates/boards/post.html:35 templates/boards/post.html.py:46
334 334 msgid "Reply"
335 335 msgstr "Ответить"
336 336
337 337 #: templates/boards/post.html:41
338 338 msgid " in "
339 339 msgstr " в "
340 340
341 341 #: templates/boards/post.html:51
342 342 msgid "Edit"
343 343 msgstr "Изменить"
344 344
345 345 #: templates/boards/post.html:53
346 346 msgid "Edit thread"
347 347 msgstr "Изменить тему"
348 348
349 349 #: templates/boards/post.html:91
350 350 msgid "Replies"
351 351 msgstr "Ответы"
352 352
353 353 #: templates/boards/post.html:103
354 354 #, python-format
355 355 msgid "%(count)s image"
356 356 msgid_plural "%(count)s images"
357 357 msgstr[0] "%(count)s изображение"
358 358 msgstr[1] "%(count)s изображения"
359 359 msgstr[2] "%(count)s изображений"
360 360
361 361 #: templates/boards/rss/post.html:5
362 362 msgid "Post image"
363 363 msgstr "Изображение сообщения"
364 364
365 365 #: templates/boards/settings.html:15
366 366 msgid "You are moderator."
367 367 msgstr "Вы модератор."
368 368
369 369 #: templates/boards/settings.html:19
370 370 msgid "Hidden tags:"
371 371 msgstr "Скрытые метки:"
372 372
373 373 #: templates/boards/settings.html:25
374 374 msgid "No hidden tags."
375 375 msgstr "Нет скрытых меток."
376 376
377 377 #: templates/boards/settings.html:34
378 378 msgid "Save"
379 379 msgstr "Сохранить"
380 380
381 381 #: templates/boards/staticpages/banned.html:6
382 382 msgid "Banned"
383 383 msgstr "Заблокирован"
384 384
385 385 #: templates/boards/staticpages/banned.html:11
386 386 msgid "Your IP address has been banned. Contact the administrator"
387 387 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
388 388
389 389 #: templates/boards/staticpages/help.html:6
390 390 #: templates/boards/staticpages/help.html:10
391 391 msgid "Syntax"
392 392 msgstr "Синтаксис"
393 393
394 394 #: templates/boards/staticpages/help.html:11
395 395 msgid "Italic text"
396 396 msgstr "Курсивный текст"
397 397
398 398 #: templates/boards/staticpages/help.html:12
399 399 msgid "Bold text"
400 400 msgstr "Полужирный текст"
401 401
402 402 #: templates/boards/staticpages/help.html:13
403 403 msgid "Spoiler"
404 404 msgstr "Спойлер"
405 405
406 406 #: templates/boards/staticpages/help.html:14
407 407 msgid "Link to a post"
408 408 msgstr "Ссылка на сообщение"
409 409
410 410 #: templates/boards/staticpages/help.html:15
411 411 msgid "Strikethrough text"
412 412 msgstr "Зачеркнутый текст"
413 413
414 414 #: templates/boards/staticpages/help.html:16
415 415 msgid "Comment"
416 416 msgstr "Комментарий"
417 417
418 418 #: templates/boards/staticpages/help.html:17
419 419 #: templates/boards/staticpages/help.html:18
420 420 msgid "Quote"
421 421 msgstr "Цитата"
422 422
423 423 #: templates/boards/staticpages/help.html:21
424 424 msgid "You can try pasting the text and previewing the result here:"
425 425 msgstr "Вы можете попробовать вставить текст и проверить результат здесь:"
426 426
427 427 #: templates/boards/thread.html:14
428 428 msgid "Normal"
429 429 msgstr "Нормальный"
430 430
431 431 #: templates/boards/thread.html:15
432 432 msgid "Gallery"
433 433 msgstr "Галерея"
434 434
435 435 #: templates/boards/thread.html:16
436 436 msgid "Tree"
437 437 msgstr "Дерево"
438 438
439 439 #: templates/boards/thread.html:35
440 440 msgid "message"
441 441 msgid_plural "messages"
442 442 msgstr[0] "сообщение"
443 443 msgstr[1] "сообщения"
444 444 msgstr[2] "сообщений"
445 445
446 446 #: templates/boards/thread.html:38
447 447 msgid "image"
448 448 msgid_plural "images"
449 449 msgstr[0] "изображение"
450 450 msgstr[1] "изображения"
451 451 msgstr[2] "изображений"
452 452
453 453 #: templates/boards/thread.html:40
454 454 msgid "Last update: "
455 455 msgstr "Последнее обновление: "
456 456
457 457 #: templates/boards/thread_gallery.html:36
458 458 msgid "No images."
459 459 msgstr "Нет изображений."
460 460
461 461 #: templates/boards/thread_normal.html:30
462 462 msgid "posts to bumplimit"
463 463 msgstr "сообщений до бамплимита"
464 464
465 465 #: templates/boards/thread_normal.html:44
466 466 msgid "Reply to thread"
467 467 msgstr "Ответить в тему"
468 468
469 469 #: templates/boards/thread_normal.html:44
470 470 msgid "to message "
471 471 msgstr "на сообщение"
472 472
473 473 #: templates/boards/thread_normal.html:59
474 474 msgid "Reset form"
475 475 msgstr "Сбросить форму"
476 476
477 477 #: templates/search/search.html:17
478 478 msgid "Ok"
479 479 msgstr "Ок"
480 480
481 481 #: utils.py:120
482 482 #, python-format
483 483 msgid "File must be less than %s but is %s."
484 484 msgstr "Файл должен быть менее %s, но его размер %s."
485 485
486 486 msgid "Please wait %(delay)d second before sending message"
487 487 msgid_plural "Please wait %(delay)d seconds before sending message"
488 488 msgstr[0] "Пожалуйста подождите %(delay)d секунду перед отправкой сообщения"
489 489 msgstr[1] "Пожалуйста подождите %(delay)d секунды перед отправкой сообщения"
490 490 msgstr[2] "Пожалуйста подождите %(delay)d секунд перед отправкой сообщения"
491 491
492 492 msgid "New threads"
493 493 msgstr "Новые темы"
494 494
495 495 #, python-format
496 496 msgid "Max file size is %(size)s."
497 497 msgstr "Максимальный размер файла %(size)s."
498 498
499 499 msgid "Size of media:"
500 500 msgstr "Размер медиа:"
501 501
502 502 msgid "Statistics"
503 503 msgstr "Статистика"
504 504
505 505 msgid "Invalid PoW."
506 506 msgstr "Неверный PoW."
507 507
508 508 msgid "Stale PoW."
509 509 msgstr "PoW устарел."
510 510
511 511 msgid "Show"
512 512 msgstr "Показывать"
513 513
514 514 msgid "Hide"
515 515 msgstr "Скрывать"
516 516
517 517 msgid "Add to favorites"
518 518 msgstr "Добавить в избранное"
519 519
520 520 msgid "Remove from favorites"
521 521 msgstr "Убрать из избранного"
522 522
523 523 msgid "Monochrome"
524 524 msgstr "Монохромный"
525 525
526 526 msgid "Subsections: "
527 527 msgstr "Подразделы: "
528 528
529 529 msgid "Change file source"
530 530 msgstr "Изменить источник файла"
531 531
532 532 msgid "interesting"
533 533 msgstr "интересное"
534 534
535 535 msgid "images"
536 536 msgstr "изображения"
537 537
538 538 msgid "Delete post"
539 539 msgstr "Удалить пост"
540 540
541 541 msgid "Delete thread"
542 542 msgstr "Удалить тему"
543 543
544 544 msgid "Messages per day/week/month:"
545 545 msgstr "Сообщений за день/неделю/месяц:"
546 546
547 547 msgid "Subscribe to thread"
548 548 msgstr "Подписаться на тему"
549 549
550 550 msgid "Active threads:"
551 551 msgstr "Активные темы:"
552 552
553 553 msgid "No active threads today."
554 554 msgstr "Сегодня нет активных тем."
555 555
556 556 msgid "Insert URLs on separate lines."
557 557 msgstr "Вставляйте ссылки на отдельных строках."
558 558
559 559 msgid "You can post no more than %(files)d file."
560 560 msgid_plural "You can post no more than %(files)d files."
561 561 msgstr[0] "Вы можете отправить не более %(files)d файла."
562 562 msgstr[1] "Вы можете отправить не более %(files)d файлов."
563 563 msgstr[2] "Вы можете отправить не более %(files)d файлов."
564 564
565 565 #, python-format
566 566 msgid "Max file number is %(max_files)s."
567 567 msgstr "Максимальное количество файлов %(max_files)s."
568 568
569 569 msgid "Moderation"
570 570 msgstr "Модерация"
571 571
572 572 msgid "Check for duplicates"
573 573 msgstr "Проверять на дубликаты"
574 574
575 575 msgid "Some files are already present on the board."
576 576 msgstr "Некоторые файлы уже присутствуют на борде."
577 577
578 578 msgid "Do not download URLs"
579 579 msgstr "Не загружать ссылки"
580 580
581 581 msgid "Ban and delete"
582 582 msgstr "Забанить и удалить"
583 583
584 584 msgid "Are you sure?"
585 585 msgstr "Вы уверены?"
586 586
587 587 msgid "Ban"
588 588 msgstr "Забанить"
589 589
590 msgid "URL download mode"
591 msgstr "Режим загрузки ссылок"
590 msgid "File process mode"
591 msgstr "Режим обработки файлов"
592
593 msgid "Download or insert as URLs"
594 msgstr "Загрузить или вставить как ссылки"
592 595
593 msgid "Download or add URL"
594 msgstr "Загрузить или добавить ссылку"
596 msgid "Download"
597 msgstr "Загрузить"
595 598
596 msgid "Download or fail"
597 msgstr "Загрузить или отказать"
599 msgid "Download and check for uniqueness"
600 msgstr "Загрузить и проверить на уникальность"
598 601
599 602 msgid "Insert as URLs"
600 msgstr "Вставлять как ссылки"
603 msgstr "Вставить как ссылки"
601 604
602 605 msgid "Help"
603 606 msgstr "Справка"
604 607
605 608 msgid "View available stickers:"
606 609 msgstr "Просмотреть доступные стикеры:"
607 610
608 611 msgid "Stickers"
609 612 msgstr "Стикеры"
610 613
611 614 msgid "Available by addresses:"
612 615 msgstr "Доступно по адресам:"
613 616
614 617 msgid "Local stickers"
615 618 msgstr "Локальные стикеры"
616 619
617 620 msgid "Global stickers"
618 621 msgstr "Глобальные стикеры"
619 622
620 623 msgid "Remove sticker"
621 624 msgstr "Удалить стикер"
622 625
623 626 msgid "Sticker Pack"
624 627 msgstr "Набор Стикеров"
625 628
626 629 msgid "Tripcode should be specified to own a stickerpack."
627 630 msgstr "Для владения набором стикеров необходимо указать трипкод."
628 631
629 632 msgid "Title should be specified as a stickerpack name."
630 633 msgstr "Заголовок должен быть указан в качестве имени набора стикеров."
631 634
632 635 msgid "A sticker pack with this name already exists and is owned by another tripcode."
633 636 msgstr "Набор стикеров с данным именем уже существует и принадлежит другому трипкоду."
634 637
635 638 msgid "This sticker pack can only be updated by an administrator."
636 639 msgstr "Этот набор стикеров может быть изменён только администратором."
637 640
638 641 msgid "To add a sticker, create a stickerpack thread using the title as a pack name, and a tripcode to own the pack. Then, add posts with title as a sticker name, and the same tripcode, to the thread. Their attachments would become stickers."
639 642 msgstr "Чтобы добавить стикер, создайте тему-набор с заголовком в качестве названия набора стикеров, и трипкодом для подтверждения владения набором. Затем, добавляйте сообщения с заголовком в качестве имени стикера, и тем же трипкодомм. Их вложения станут стикерами."
640 643
641 644 msgid "Inappropriate sticker pack name."
642 645 msgstr "Недопустимое имя набора стикеров."
1 NO CONTENT: modified file, binary diff hidden
@@ -1,642 +1,645 b''
1 1 # SOME DESCRIPTIVE TITLE.
2 2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 3 # This file is distributed under the same license as the PACKAGE package.
4 4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 5 #
6 6 msgid ""
7 7 msgstr ""
8 8 "Project-Id-Version: PACKAGE VERSION\n"
9 9 "Report-Msgid-Bugs-To: \n"
10 10 "POT-Creation-Date: 2015-10-09 23:21+0300\n"
11 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 14 "Language: ru\n"
15 15 "MIME-Version: 1.0\n"
16 16 "Content-Type: text/plain; charset=UTF-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20 20
21 21 #: admin.py:22
22 22 msgid "{} posters were banned"
23 23 msgstr "{} постерів заблоковано"
24 24
25 25 #: authors.py:9
26 26 msgid "author"
27 27 msgstr "автор"
28 28
29 29 #: authors.py:10
30 30 msgid "developer"
31 31 msgstr "розробник"
32 32
33 33 #: authors.py:11
34 34 msgid "javascript developer"
35 35 msgstr "javascript-розробник"
36 36
37 37 #: authors.py:12
38 38 msgid "designer"
39 39 msgstr "дизайнер"
40 40
41 41 #: forms.py:30
42 42 msgid "Type message here. Use formatting panel for more advanced usage."
43 43 msgstr ""
44 44 "Введіть сюди повідомлення. Користайте панель для складнішого форматування."
45 45
46 46 #: forms.py:31
47 47 msgid "music images i_dont_like_tags"
48 48 msgstr "музика зображення мітки_не_потрібні"
49 49
50 50 #: forms.py:33
51 51 msgid "Title"
52 52 msgstr "Заголовок"
53 53
54 54 #: forms.py:34
55 55 msgid "Text"
56 56 msgstr "Текст"
57 57
58 58 #: forms.py:35
59 59 msgid "Tag"
60 60 msgstr "Мітка"
61 61
62 62 #: forms.py:36 templates/boards/base.html:40 templates/search/search.html:7
63 63 msgid "Search"
64 64 msgstr "Пошук"
65 65
66 66 #: forms.py:48
67 67 msgid "File 1"
68 68 msgstr "Файл 1"
69 69
70 70 #: forms.py:48
71 71 msgid "File 2"
72 72 msgstr "Файл 2"
73 73
74 74 #: forms.py:142
75 75 msgid "File URL"
76 76 msgstr "URL файлу"
77 77
78 78 #: forms.py:148
79 79 msgid "e-mail"
80 80 msgstr ""
81 81
82 82 #: forms.py:151
83 83 msgid "Additional threads"
84 84 msgstr "Додаткові нитки"
85 85
86 86 #: forms.py:162
87 87 #, python-format
88 88 msgid "Title must have less than %s characters"
89 89 msgstr "Заголовок має містити менше %s символів"
90 90
91 91 #: forms.py:172
92 92 #, python-format
93 93 msgid "Text must have less than %s characters"
94 94 msgstr "Текст має бути коротше %s символів"
95 95
96 96 #: forms.py:192
97 97 msgid "Invalid URL"
98 98 msgstr "Хибний URL"
99 99
100 100 #: forms.py:213
101 101 msgid "Invalid additional thread list"
102 102 msgstr "Хибний перелік додаткових ниток"
103 103
104 104 #: forms.py:258
105 105 msgid "Either text or file must be entered."
106 106 msgstr "Слід додати текст або файл."
107 107
108 108 #: forms.py:317 templates/boards/all_threads.html:153
109 109 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
110 110 msgid "Tags"
111 111 msgstr "Мітки"
112 112
113 113 #: forms.py:324
114 114 msgid "Inappropriate characters in tags."
115 115 msgstr "Неприйнятні символи у мітках."
116 116
117 117 #: forms.py:344
118 118 msgid "Need at least one section."
119 119 msgstr "Мусить бути хоча б один розділ."
120 120
121 121 #: forms.py:356
122 122 msgid "Theme"
123 123 msgstr "Тема"
124 124
125 125 #: forms.py:357
126 126 msgid "Image view mode"
127 127 msgstr "Режим перегляду зображень"
128 128
129 129 #: forms.py:358
130 130 msgid "User name"
131 131 msgstr "Им'я користувача"
132 132
133 133 #: forms.py:359
134 134 msgid "Time zone"
135 135 msgstr "Часовий пояс"
136 136
137 137 #: forms.py:365
138 138 msgid "Inappropriate characters."
139 139 msgstr "Неприйнятні символи."
140 140
141 141 #: templates/boards/404.html:6
142 142 msgid "Not found"
143 143 msgstr "Загубилося"
144 144
145 145 #: templates/boards/404.html:12
146 146 msgid "This page does not exist"
147 147 msgstr "Нема правдоньки на світі, ой нема…"
148 148
149 149 #: templates/boards/all_threads.html:35
150 150 msgid "Details"
151 151 msgstr "Деталі"
152 152
153 153 #: templates/boards/all_threads.html:69
154 154 msgid "Edit tag"
155 155 msgstr "Змінити мітку"
156 156
157 157 #: templates/boards/all_threads.html:76
158 158 #, python-format
159 159 msgid "%(count)s active thread"
160 160 msgid_plural "%(count)s active threads"
161 161 msgstr[0] "%(count)s активна нитка"
162 162 msgstr[1] "%(count)s активні нитки"
163 163 msgstr[2] "%(count)s активних ниток"
164 164
165 165 #: templates/boards/all_threads.html:76
166 166 #, python-format
167 167 msgid "%(count)s thread in bumplimit"
168 168 msgid_plural "%(count)s threads in bumplimit"
169 169 msgstr[0] "%(count)s нитка в бампляматі"
170 170 msgstr[1] "%(count)s нитки в бампляматі"
171 171 msgstr[2] "%(count)s ниток у бампляматі"
172 172
173 173 #: templates/boards/all_threads.html:77
174 174 #, python-format
175 175 msgid "%(count)s archived thread"
176 176 msgid_plural "%(count)s archived thread"
177 177 msgstr[0] "%(count)s архівна нитка"
178 178 msgstr[1] "%(count)s архівні нитки"
179 179 msgstr[2] "%(count)s архівних ниток"
180 180
181 181 #: templates/boards/all_threads.html:78 templates/boards/post.html:102
182 182 #, python-format
183 183 #| msgid "%(count)s message"
184 184 #| msgid_plural "%(count)s messages"
185 185 msgid "%(count)s message"
186 186 msgid_plural "%(count)s messages"
187 187 msgstr[0] "%(count)s повідомлення"
188 188 msgstr[1] "%(count)s повідомлення"
189 189 msgstr[2] "%(count)s повідомлень"
190 190
191 191 #: templates/boards/all_threads.html:95 templates/boards/feed.html:30
192 192 #: templates/boards/notifications.html:17 templates/search/search.html:26
193 193 msgid "Previous page"
194 194 msgstr "Попєрєдня сторінка"
195 195
196 196 #: templates/boards/all_threads.html:109
197 197 #, python-format
198 198 msgid "Skipped %(count)s reply. Open thread to see all replies."
199 199 msgid_plural "Skipped %(count)s replies. Open thread to see all replies."
200 200 msgstr[0] "Пропущено %(count)s відповідь. Розгорніть нитку, щоб побачити всі відповіді."
201 201 msgstr[1] ""
202 202 "Пропущено %(count)s відповіді. Розгорніть нитку, щоб побачити всі відповіді."
203 203 msgstr[2] ""
204 204 "Пропущено %(count)s відповідей. Розгорніть нитку, щоб побачити всі відповіді."
205 205
206 206 #: templates/boards/all_threads.html:127 templates/boards/feed.html:40
207 207 #: templates/boards/notifications.html:27 templates/search/search.html:37
208 208 msgid "Next page"
209 209 msgstr "Наступна сторінка"
210 210
211 211 #: templates/boards/all_threads.html:132
212 212 msgid "No threads exist. Create the first one!"
213 213 msgstr "Нема правдоньки на світі. Зачнімо її!"
214 214
215 215 #: templates/boards/all_threads.html:138
216 216 msgid "Create new thread"
217 217 msgstr "Сплести нову нитку"
218 218
219 219 #: templates/boards/all_threads.html:143 templates/boards/preview.html:16
220 220 #: templates/boards/thread_normal.html:51
221 221 msgid "Post"
222 222 msgstr "Надіслати"
223 223
224 224 #: templates/boards/all_threads.html:144 templates/boards/preview.html:6
225 225 #: templates/boards/staticpages/help.html:21
226 226 #: templates/boards/thread_normal.html:52
227 227 msgid "Preview"
228 228 msgstr "Поперегляд"
229 229
230 230 #: templates/boards/all_threads.html:149
231 231 msgid "Tags must be delimited by spaces. Text or image is required."
232 232 msgstr ""
233 233 "Мітки розмежувати пробілами. Текст чи зображення є обов'язковими."
234 234
235 235 #: templates/boards/all_threads.html:152 templates/boards/thread_normal.html:58
236 236 msgid "Text syntax"
237 237 msgstr "Синтаксис тексту"
238 238
239 239 #: templates/boards/all_threads.html:166 templates/boards/feed.html:53
240 240 msgid "Pages:"
241 241 msgstr "Сторінки:"
242 242
243 243 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
244 244 msgid "Authors"
245 245 msgstr "Автори"
246 246
247 247 #: templates/boards/authors.html:26
248 248 msgid "Distributed under the"
249 249 msgstr "Розповсюджується під ліцензією"
250 250
251 251 #: templates/boards/authors.html:28
252 252 msgid "license"
253 253 msgstr ""
254 254
255 255 #: templates/boards/authors.html:30
256 256 msgid "Repository"
257 257 msgstr "Репозиторій"
258 258
259 259 #: templates/boards/base.html:14 templates/boards/base.html.py:41
260 260 msgid "Feed"
261 261 msgstr "Стрічка"
262 262
263 263 #: templates/boards/base.html:31
264 264 msgid "All threads"
265 265 msgstr "Усі нитки"
266 266
267 267 #: templates/boards/base.html:37
268 268 msgid "Add tags"
269 269 msgstr "Додати мітки"
270 270
271 271 #: templates/boards/base.html:39
272 272 msgid "Tag management"
273 273 msgstr "Керування мітками"
274 274
275 275 #: templates/boards/base.html:39
276 276 msgid "tags"
277 277 msgstr "мітки"
278 278
279 279 #: templates/boards/base.html:40
280 280 msgid "search"
281 281 msgstr "пошук"
282 282
283 283 #: templates/boards/base.html:41 templates/boards/feed.html:11
284 284 msgid "feed"
285 285 msgstr "стрічка"
286 286
287 287 #: templates/boards/base.html:42 templates/boards/random.html:6
288 288 msgid "Random images"
289 289 msgstr "Випадкові зображення"
290 290
291 291 #: templates/boards/base.html:42
292 292 msgid "random"
293 293 msgstr "випадкові"
294 294
295 295 #: templates/boards/base.html:44
296 296 msgid "favorites"
297 297 msgstr "улюблене"
298 298
299 299 #: templates/boards/base.html:48 templates/boards/base.html.py:49
300 300 #: templates/boards/notifications.html:8
301 301 msgid "Notifications"
302 302 msgstr "Сповіщення"
303 303
304 304 #: templates/boards/base.html:56 templates/boards/settings.html:8
305 305 msgid "Settings"
306 306 msgstr "Налаштування"
307 307
308 308 #: templates/boards/base.html:59
309 309 msgid "Loading..."
310 310 msgstr "Завантаження..."
311 311
312 312 #: templates/boards/base.html:71
313 313 msgid "Admin"
314 314 msgstr "Адміністрування"
315 315
316 316 #: templates/boards/base.html:73
317 317 #, python-format
318 318 msgid "Speed: %(ppd)s posts per day"
319 319 msgstr "Хуткість: %(ppd)s повідомлень на день"
320 320
321 321 #: templates/boards/base.html:75
322 322 msgid "Up"
323 323 msgstr "Догори"
324 324
325 325 #: templates/boards/feed.html:45
326 326 msgid "No posts exist. Create the first one!"
327 327 msgstr "Ще нема повідомлень. Зачнімо!"
328 328
329 329 #: templates/boards/post.html:33
330 330 msgid "Open"
331 331 msgstr "Відкрити"
332 332
333 333 #: templates/boards/post.html:35 templates/boards/post.html.py:46
334 334 msgid "Reply"
335 335 msgstr "Відповісти"
336 336
337 337 #: templates/boards/post.html:41
338 338 msgid " in "
339 339 msgstr " у "
340 340
341 341 #: templates/boards/post.html:51
342 342 msgid "Edit"
343 343 msgstr "Змінити"
344 344
345 345 #: templates/boards/post.html:53
346 346 msgid "Edit thread"
347 347 msgstr "Змінити нитку"
348 348
349 349 #: templates/boards/post.html:91
350 350 msgid "Replies"
351 351 msgstr "Відповіді"
352 352
353 353 #: templates/boards/post.html:103
354 354 #, python-format
355 355 msgid "%(count)s image"
356 356 msgid_plural "%(count)s images"
357 357 msgstr[0] "%(count)s зображення"
358 358 msgstr[1] "%(count)s зображення"
359 359 msgstr[2] "%(count)s зображень"
360 360
361 361 #: templates/boards/rss/post.html:5
362 362 msgid "Post image"
363 363 msgstr "Зображення повідомлення"
364 364
365 365 #: templates/boards/settings.html:15
366 366 msgid "You are moderator."
367 367 msgstr "Ви модератор."
368 368
369 369 #: templates/boards/settings.html:19
370 370 msgid "Hidden tags:"
371 371 msgstr "Приховані мітки:"
372 372
373 373 #: templates/boards/settings.html:25
374 374 msgid "No hidden tags."
375 375 msgstr "Нема прихованих міток."
376 376
377 377 #: templates/boards/settings.html:34
378 378 msgid "Save"
379 379 msgstr "Зберегти"
380 380
381 381 #: templates/boards/staticpages/banned.html:6
382 382 msgid "Banned"
383 383 msgstr "Заблоковано"
384 384
385 385 #: templates/boards/staticpages/banned.html:11
386 386 msgid "Your IP address has been banned. Contact the administrator"
387 387 msgstr "Вашу IP-адресу заблоковано. Зателефонуйте до спортлото"
388 388
389 389 #: templates/boards/staticpages/help.html:6
390 390 #: templates/boards/staticpages/help.html:10
391 391 msgid "Syntax"
392 392 msgstr "Синтаксис"
393 393
394 394 #: templates/boards/staticpages/help.html:11
395 395 msgid "Italic text"
396 396 msgstr "Курсивний текст"
397 397
398 398 #: templates/boards/staticpages/help.html:12
399 399 msgid "Bold text"
400 400 msgstr "Напівогрядний текст"
401 401
402 402 #: templates/boards/staticpages/help.html:13
403 403 msgid "Spoiler"
404 404 msgstr "Спойлер"
405 405
406 406 #: templates/boards/staticpages/help.html:14
407 407 msgid "Link to a post"
408 408 msgstr "Посилання на повідомлення"
409 409
410 410 #: templates/boards/staticpages/help.html:15
411 411 msgid "Strikethrough text"
412 412 msgstr "Закреслений текст"
413 413
414 414 #: templates/boards/staticpages/help.html:16
415 415 msgid "Comment"
416 416 msgstr "Коментар"
417 417
418 418 #: templates/boards/staticpages/help.html:17
419 419 #: templates/boards/staticpages/help.html:18
420 420 msgid "Quote"
421 421 msgstr "Цитата"
422 422
423 423 #: templates/boards/staticpages/help.html:21
424 424 msgid "You can try pasting the text and previewing the result here:"
425 425 msgstr "Можете спробувати вставити текст і перевірити результат тут:"
426 426
427 427 #: templates/boards/thread.html:14
428 428 msgid "Normal"
429 429 msgstr "Звичайний"
430 430
431 431 #: templates/boards/thread.html:15
432 432 msgid "Gallery"
433 433 msgstr "Галерея"
434 434
435 435 #: templates/boards/thread.html:16
436 436 msgid "Tree"
437 437 msgstr "Віник"
438 438
439 439 #: templates/boards/thread.html:35
440 440 msgid "message"
441 441 msgid_plural "messages"
442 442 msgstr[0] "повідомлення"
443 443 msgstr[1] "повідомлення"
444 444 msgstr[2] "повідомлень"
445 445
446 446 #: templates/boards/thread.html:38
447 447 msgid "image"
448 448 msgid_plural "images"
449 449 msgstr[0] "зображення"
450 450 msgstr[1] "зображення"
451 451 msgstr[2] "зображень"
452 452
453 453 #: templates/boards/thread.html:40
454 454 msgid "Last update: "
455 455 msgstr "Останнє оновлення: "
456 456
457 457 #: templates/boards/thread_gallery.html:36
458 458 msgid "No images."
459 459 msgstr "Нема зображень."
460 460
461 461 #: templates/boards/thread_normal.html:30
462 462 msgid "posts to bumplimit"
463 463 msgstr "повідомлень до бамплямату"
464 464
465 465 #: templates/boards/thread_normal.html:44
466 466 msgid "Reply to thread"
467 467 msgstr "Відповісти до нитки"
468 468
469 469 #: templates/boards/thread_normal.html:44
470 470 msgid "to message "
471 471 msgstr "на повідомлення"
472 472
473 473 #: templates/boards/thread_normal.html:59
474 474 msgid "Reset form"
475 475 msgstr "Скинути форму"
476 476
477 477 #: templates/search/search.html:17
478 478 msgid "Ok"
479 479 msgstr "Файно"
480 480
481 481 #: utils.py:120
482 482 #, python-format
483 483 msgid "File must be less than %s but is %s."
484 484 msgstr "Файл мусить бути менше %s, але його розмір %s."
485 485
486 486 msgid "Please wait %(delay)d second before sending message"
487 487 msgid_plural "Please wait %(delay)d seconds before sending message"
488 488 msgstr[0] "Зачекайте, будь ласка, %(delay)d секунду перед надсиланням повідомлення"
489 489 msgstr[1] "Зачекайте, будь ласка, %(delay)d секунди перед надсиланням повідомлення"
490 490 msgstr[2] "Зачекайте, будь ласка, %(delay)d секунд перед надсиланням повідомлення"
491 491
492 492 msgid "New threads"
493 493 msgstr "Нові нитки"
494 494
495 495 #, python-format
496 496 msgid "Max file size is %(size)s."
497 497 msgstr "Максимальний розмір файлу %(size)s."
498 498
499 499 msgid "Size of media:"
500 500 msgstr "Розмір посередника:"
501 501
502 502 msgid "Statistics"
503 503 msgstr "Статистика"
504 504
505 505 msgid "Invalid PoW."
506 506 msgstr "Хибний PoW."
507 507
508 508 msgid "Stale PoW."
509 509 msgstr "PoW застарів."
510 510
511 511 msgid "Show"
512 512 msgstr "Показувати"
513 513
514 514 msgid "Hide"
515 515 msgstr "Ховати"
516 516
517 517 msgid "Add to favorites"
518 518 msgstr "Я це люблю"
519 519
520 520 msgid "Remove from favorites"
521 521 msgstr "Вже не люблю"
522 522
523 523 msgid "Monochrome"
524 524 msgstr "Без барв"
525 525
526 526 msgid "Subsections: "
527 527 msgstr "Підрозділи: "
528 528
529 529 msgid "Change file source"
530 530 msgstr "Змінити джерело файлу"
531 531
532 532 msgid "interesting"
533 533 msgstr "цікаве"
534 534
535 535 msgid "images"
536 536 msgstr "пічкури"
537 537
538 538 msgid "Delete post"
539 539 msgstr "Видалити повідомлення"
540 540
541 541 msgid "Delete thread"
542 542 msgstr "Вирвати нитку"
543 543
544 544 msgid "Messages per day/week/month:"
545 545 msgstr "Повідомлень за день/тиждень/тижмісяць:"
546 546
547 547 msgid "Subscribe to thread"
548 548 msgstr "Стежити за ниткою"
549 549
550 550 msgid "Active threads:"
551 551 msgstr "Активні нитки:"
552 552
553 553 msgid "No active threads today."
554 554 msgstr "Щось усі замовкли."
555 555
556 556 msgid "Insert URLs on separate lines."
557 557 msgstr "Вставляйте посилання окремими рядками."
558 558
559 559 msgid "You can post no more than %(files)d file."
560 560 msgid_plural "You can post no more than %(files)d files."
561 561 msgstr[0] "Ви можете надіслати не більше %(files)d файлу."
562 562 msgstr[1] "Вы можете надіслати не більше %(files)d файлів."
563 563 msgstr[2] "Вы можете надіслати не більше %(files)d файлів."
564 564
565 565 #, python-format
566 566 msgid "Max file number is %(max_files)s."
567 567 msgstr "Максимальна кількість файлів %(max_files)s."
568 568
569 569 msgid "Moderation"
570 570 msgstr "Модерація"
571 571
572 572 msgid "Check for duplicates"
573 573 msgstr "Перевіряти на дублікати"
574 574
575 575 msgid "Some files are already present on the board."
576 576 msgstr "Деякі файли вже є на дошці."
577 577
578 578 msgid "Do not download URLs"
579 579 msgstr "Не завантажувати посилання"
580 580
581 581 msgid "Ban and delete"
582 582 msgstr "Заблокувати й видалити"
583 583
584 584 msgid "Are you sure?"
585 585 msgstr "Чи ви певні?"
586 586
587 587 msgid "Ban"
588 588 msgstr "Заблокувати"
589 589
590 msgid "URL download mode"
591 msgstr "Режим завантаження посилань"
590 msgid "File process mode"
591 msgstr "Режим обробки файлів"
592
593 msgid "Download or insert as URLs"
594 msgstr "Завантажити або вставити як посилання"
592 595
593 msgid "Download or add URL"
594 msgstr "Завантажити або додати посилання"
596 msgid "Download"
597 msgstr "Завантажити"
595 598
596 msgid "Download or fail"
597 msgstr "Завантажити або відмовити"
599 msgid "Download and check for uniqueness"
600 msgstr "Завантажити та перевірити на унікальність"
598 601
599 602 msgid "Insert as URLs"
600 msgstr "Вставляти як посилання"
603 msgstr "Вставити як посилання"
601 604
602 605 msgid "Help"
603 606 msgstr "Справка"
604 607
605 608 msgid "View available stickers:"
606 609 msgstr "Передивитися доступні стікери:"
607 610
608 611 msgid "Stickers"
609 612 msgstr "Стікери"
610 613
611 614 msgid "Available by addresses:"
612 615 msgstr "Доступно за адресами:"
613 616
614 617 msgid "Local stickers"
615 618 msgstr "Локальні стікери"
616 619
617 620 msgid "Global stickers"
618 621 msgstr "Глобальні стікери"
619 622
620 623 msgid "Remove sticker"
621 624 msgstr "Видалити стікер"
622 625
623 626 msgid "Sticker Pack"
624 627 msgstr "Набір Стікерів"
625 628
626 629 msgid "Tripcode should be specified to own a stickerpack."
627 630 msgstr "Для володіння набором стікерів необхідно вказати тріпкод."
628 631
629 632 msgid "Title should be specified as a stickerpack name."
630 633 msgstr "Заголовок повинен бути вказаний в якості імені набору стікерів."
631 634
632 635 msgid "A sticker pack with this name already exists and is owned by another tripcode."
633 636 msgstr "Набір стікерів з вказаним іменем вже є в наявності та належить іншому тріпкоду."
634 637
635 638 msgid "This sticker pack can only be updated by an administrator."
636 639 msgstr "Цей набір стікерів може бути змінений лише адміністратором."
637 640
638 641 msgid "To add a sticker, create a stickerpack thread using the title as a pack name, and a tripcode to own the pack. Then, add posts with title as a sticker name, and the same tripcode, to the thread. Their attachments would become stickers."
639 642 msgstr "Щоб додати стікер, створіть тему-набір з заголовком у якості набору стікерів, та тріпкодом для підтвердження володіння наборомт. Потім, додавайте повідомлення з заголовком у якості імені стікеру та з тим самим тріпкодом. Їхні вкладення станут стікерами."
640 643
641 644 msgid "Inappropriate sticker pack name."
642 645 msgstr "Неприпустиме ім'я набору стікерів."
General Comments 0
You need to be logged in to leave comments. Login now