##// END OF EJS Templates
Show the list of required tags when non was entered at thread creation
neko259 -
r1099:e990ce46 default
parent child Browse files
Show More
@@ -1,369 +1,372 b''
1 1 import re
2 2 import time
3 3 import pytz
4 4
5 5 from django import forms
6 6 from django.core.files.uploadedfile import SimpleUploadedFile
7 7 from django.core.exceptions import ObjectDoesNotExist
8 8 from django.forms.util import ErrorList
9 9 from django.utils.translation import ugettext_lazy as _
10 10 import requests
11 11
12 12 from boards.mdx_neboard import formatters
13 13 from boards.models.post import TITLE_MAX_LENGTH
14 14 from boards.models import Tag, Post
15 15 from neboard import settings
16 16 import boards.settings as board_settings
17 17
18 18
19 19 CONTENT_TYPE_IMAGE = (
20 20 'image/jpeg',
21 21 'image/png',
22 22 'image/gif',
23 23 'image/bmp',
24 24 )
25 25
26 26 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
27 27
28 28 VETERAN_POSTING_DELAY = 5
29 29
30 30 ATTRIBUTE_PLACEHOLDER = 'placeholder'
31 31 ATTRIBUTE_ROWS = 'rows'
32 32
33 33 LAST_POST_TIME = 'last_post_time'
34 34 LAST_LOGIN_TIME = 'last_login_time'
35 35 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
36 36 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
37 37
38 38 LABEL_TITLE = _('Title')
39 39 LABEL_TEXT = _('Text')
40 40 LABEL_TAG = _('Tag')
41 41 LABEL_SEARCH = _('Search')
42 42
43 43 TAG_MAX_LENGTH = 20
44 44
45 45 IMAGE_DOWNLOAD_CHUNK_BYTES = 100000
46 46
47 47 HTTP_RESULT_OK = 200
48 48
49 49 TEXTAREA_ROWS = 4
50 50
51 51
52 52 def get_timezones():
53 53 timezones = []
54 54 for tz in pytz.common_timezones:
55 55 timezones.append((tz, tz),)
56 56 return timezones
57 57
58 58
59 59 class FormatPanel(forms.Textarea):
60 60 """
61 61 Panel for text formatting. Consists of buttons to add different tags to the
62 62 form text area.
63 63 """
64 64
65 65 def render(self, name, value, attrs=None):
66 66 output = '<div id="mark-panel">'
67 67 for formatter in formatters:
68 68 output += '<span class="mark_btn"' + \
69 69 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
70 70 '\', \'' + formatter.format_right + '\')">' + \
71 71 formatter.preview_left + formatter.name + \
72 72 formatter.preview_right + '</span>'
73 73
74 74 output += '</div>'
75 75 output += super(FormatPanel, self).render(name, value, attrs=None)
76 76
77 77 return output
78 78
79 79
80 80 class PlainErrorList(ErrorList):
81 81 def __unicode__(self):
82 82 return self.as_text()
83 83
84 84 def as_text(self):
85 85 return ''.join(['(!) %s ' % e for e in self])
86 86
87 87
88 88 class NeboardForm(forms.Form):
89 89 """
90 90 Form with neboard-specific formatting.
91 91 """
92 92
93 93 def as_div(self):
94 94 """
95 95 Returns this form rendered as HTML <as_div>s.
96 96 """
97 97
98 98 return self._html_output(
99 99 # TODO Do not show hidden rows in the list here
100 100 normal_row='<div class="form-row"><div class="form-label">'
101 101 '%(label)s'
102 102 '</div></div>'
103 103 '<div class="form-row"><div class="form-input">'
104 104 '%(field)s'
105 105 '</div></div>'
106 106 '<div class="form-row">'
107 107 '%(help_text)s'
108 108 '</div>',
109 109 error_row='<div class="form-row">'
110 110 '<div class="form-label"></div>'
111 111 '<div class="form-errors">%s</div>'
112 112 '</div>',
113 113 row_ender='</div>',
114 114 help_text_html='%s',
115 115 errors_on_separate_row=True)
116 116
117 117 def as_json_errors(self):
118 118 errors = []
119 119
120 120 for name, field in list(self.fields.items()):
121 121 if self[name].errors:
122 122 errors.append({
123 123 'field': name,
124 124 'errors': self[name].errors.as_text(),
125 125 })
126 126
127 127 return errors
128 128
129 129
130 130 class PostForm(NeboardForm):
131 131
132 132 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
133 133 label=LABEL_TITLE)
134 134 text = forms.CharField(
135 135 widget=FormatPanel(attrs={
136 136 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
137 137 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
138 138 }),
139 139 required=False, label=LABEL_TEXT)
140 140 image = forms.ImageField(required=False, label=_('Image'),
141 141 widget=forms.ClearableFileInput(
142 142 attrs={'accept': 'image/*'}))
143 143 image_url = forms.CharField(required=False, label=_('Image URL'),
144 144 widget=forms.TextInput(
145 145 attrs={ATTRIBUTE_PLACEHOLDER:
146 146 'http://example.com/image.png'}))
147 147
148 148 # This field is for spam prevention only
149 149 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
150 150 widget=forms.TextInput(attrs={
151 151 'class': 'form-email'}))
152 152 threads = forms.CharField(required=False, label=_('Additional threads'),
153 153 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
154 154 '123 456 789'}))
155 155
156 156 session = None
157 157 need_to_ban = False
158 158
159 159 def clean_title(self):
160 160 title = self.cleaned_data['title']
161 161 if title:
162 162 if len(title) > TITLE_MAX_LENGTH:
163 163 raise forms.ValidationError(_('Title must have less than %s '
164 164 'characters') %
165 165 str(TITLE_MAX_LENGTH))
166 166 return title
167 167
168 168 def clean_text(self):
169 169 text = self.cleaned_data['text'].strip()
170 170 if text:
171 171 if len(text) > board_settings.MAX_TEXT_LENGTH:
172 172 raise forms.ValidationError(_('Text must have less than %s '
173 173 'characters') %
174 174 str(board_settings
175 175 .MAX_TEXT_LENGTH))
176 176 return text
177 177
178 178 def clean_image(self):
179 179 image = self.cleaned_data['image']
180 180
181 181 if image:
182 182 self.validate_image_size(image.size)
183 183
184 184 return image
185 185
186 186 def clean_image_url(self):
187 187 url = self.cleaned_data['image_url']
188 188
189 189 image = None
190 190 if url:
191 191 image = self._get_image_from_url(url)
192 192
193 193 if not image:
194 194 raise forms.ValidationError(_('Invalid URL'))
195 195 else:
196 196 self.validate_image_size(image.size)
197 197
198 198 return image
199 199
200 200 def clean_threads(self):
201 201 threads_str = self.cleaned_data['threads']
202 202
203 203 if len(threads_str) > 0:
204 204 threads_id_list = threads_str.split(' ')
205 205
206 206 threads = list()
207 207
208 208 for thread_id in threads_id_list:
209 209 try:
210 210 thread = Post.objects.get(id=int(thread_id))
211 211 if not thread.is_opening():
212 212 raise ObjectDoesNotExist()
213 213 threads.append(thread)
214 214 except (ObjectDoesNotExist, ValueError):
215 215 raise forms.ValidationError(_('Invalid additional thread list'))
216 216
217 217 return threads
218 218
219 219 def clean(self):
220 220 cleaned_data = super(PostForm, self).clean()
221 221
222 222 if not self.session:
223 223 raise forms.ValidationError('Humans have sessions')
224 224
225 225 if cleaned_data['email']:
226 226 self.need_to_ban = True
227 227 raise forms.ValidationError('A human cannot enter a hidden field')
228 228
229 229 if not self.errors:
230 230 self._clean_text_image()
231 231
232 232 if not self.errors and self.session:
233 233 self._validate_posting_speed()
234 234
235 235 return cleaned_data
236 236
237 237 def get_image(self):
238 238 """
239 239 Gets image from file or URL.
240 240 """
241 241
242 242 image = self.cleaned_data['image']
243 243 return image if image else self.cleaned_data['image_url']
244 244
245 245 def _clean_text_image(self):
246 246 text = self.cleaned_data.get('text')
247 247 image = self.get_image()
248 248
249 249 if (not text) and (not image):
250 250 error_message = _('Either text or image must be entered.')
251 251 self._errors['text'] = self.error_class([error_message])
252 252
253 253 def _validate_posting_speed(self):
254 254 can_post = True
255 255
256 256 posting_delay = settings.POSTING_DELAY
257 257
258 258 if board_settings.LIMIT_POSTING_SPEED and LAST_POST_TIME in \
259 259 self.session:
260 260 now = time.time()
261 261 last_post_time = self.session[LAST_POST_TIME]
262 262
263 263 current_delay = int(now - last_post_time)
264 264
265 265 if current_delay < posting_delay:
266 266 error_message = _('Wait %s seconds after last posting') % str(
267 267 posting_delay - current_delay)
268 268 self._errors['text'] = self.error_class([error_message])
269 269
270 270 can_post = False
271 271
272 272 if can_post:
273 273 self.session[LAST_POST_TIME] = time.time()
274 274
275 275 def validate_image_size(self, size: int):
276 276 if size > board_settings.MAX_IMAGE_SIZE:
277 277 raise forms.ValidationError(
278 278 _('Image must be less than %s bytes')
279 279 % str(board_settings.MAX_IMAGE_SIZE))
280 280
281 281 def _get_image_from_url(self, url: str) -> SimpleUploadedFile:
282 282 """
283 283 Gets an image file from URL.
284 284 """
285 285
286 286 img_temp = None
287 287
288 288 try:
289 289 # Verify content headers
290 290 response_head = requests.head(url, verify=False)
291 291 content_type = response_head.headers['content-type'].split(';')[0]
292 292 if content_type in CONTENT_TYPE_IMAGE:
293 293 length_header = response_head.headers.get('content-length')
294 294 if length_header:
295 295 length = int(length_header)
296 296 self.validate_image_size(length)
297 297 # Get the actual content into memory
298 298 response = requests.get(url, verify=False, stream=True)
299 299
300 300 # Download image, stop if the size exceeds limit
301 301 size = 0
302 302 content = b''
303 303 for chunk in response.iter_content(IMAGE_DOWNLOAD_CHUNK_BYTES):
304 304 size += len(chunk)
305 305 self.validate_image_size(size)
306 306 content += chunk
307 307
308 308 if response.status_code == HTTP_RESULT_OK and content:
309 309 # Set a dummy file name that will be replaced
310 310 # anyway, just keep the valid extension
311 311 filename = 'image.' + content_type.split('/')[1]
312 312 img_temp = SimpleUploadedFile(filename, content,
313 313 content_type)
314 314 except Exception:
315 315 # Just return no image
316 316 pass
317 317
318 318 return img_temp
319 319
320 320
321 321 class ThreadForm(PostForm):
322 322
323 323 tags = forms.CharField(
324 324 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
325 325 max_length=100, label=_('Tags'), required=True)
326 326
327 327 def clean_tags(self):
328 328 tags = self.cleaned_data['tags'].strip()
329 329
330 330 if not tags or not REGEX_TAGS.match(tags):
331 331 raise forms.ValidationError(
332 332 _('Inappropriate characters in tags.'))
333 333
334 334 required_tag_exists = False
335 335 for tag in tags.split():
336 336 tag_model = Tag.objects.filter(name=tag.strip().lower(),
337 337 required=True)
338 338 if tag_model.exists():
339 339 required_tag_exists = True
340 340 break
341 341
342 342 if not required_tag_exists:
343 raise forms.ValidationError(_('Need at least 1 required tag.'))
343 all_tags = Tag.objects.filter(required=True)
344 raise forms.ValidationError(
345 _('Need at least one of the tags: ')
346 + ', '.join([tag.name for tag in all_tags]))
344 347
345 348 return tags
346 349
347 350 def clean(self):
348 351 cleaned_data = super(ThreadForm, self).clean()
349 352
350 353 return cleaned_data
351 354
352 355
353 356 class SettingsForm(NeboardForm):
354 357
355 358 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
356 359 username = forms.CharField(label=_('User name'), required=False)
357 360 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
358 361
359 362 def clean_username(self):
360 363 username = self.cleaned_data['username']
361 364
362 365 if username and not REGEX_TAGS.match(username):
363 366 raise forms.ValidationError(_('Inappropriate characters.'))
364 367
365 368 return username
366 369
367 370
368 371 class SearchForm(NeboardForm):
369 372 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
1 NO CONTENT: modified file, binary diff hidden
@@ -1,401 +1,402 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 "POT-Creation-Date: 2015-04-14 13:07+0300\n"
10 "POT-Creation-Date: 2015-04-14 21:16+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:35
42 42 msgid "Type message here. Use formatting panel for more advanced usage."
43 43 msgstr ""
44 44 "Вводите сообщение сюда. Используйте панель для более сложного форматирования."
45 45
46 46 #: forms.py:36
47 47 msgid "music images i_dont_like_tags"
48 48 msgstr "музыка картинки теги_не_нужны"
49 49
50 50 #: forms.py:38
51 51 msgid "Title"
52 52 msgstr "Заголовок"
53 53
54 54 #: forms.py:39
55 55 msgid "Text"
56 56 msgstr "Текст"
57 57
58 58 #: forms.py:40
59 59 msgid "Tag"
60 60 msgstr "Метка"
61 61
62 62 #: forms.py:41 templates/boards/base.html:40 templates/search/search.html:7
63 63 msgid "Search"
64 64 msgstr "Поиск"
65 65
66 66 #: forms.py:140
67 67 msgid "Image"
68 68 msgstr "Изображение"
69 69
70 70 #: forms.py:143
71 71 msgid "Image URL"
72 72 msgstr "URL изображения"
73 73
74 74 #: forms.py:149
75 75 msgid "e-mail"
76 76 msgstr ""
77 77
78 78 #: forms.py:152
79 79 msgid "Additional threads"
80 80 msgstr "Дополнительные темы"
81 81
82 82 #: forms.py:163
83 83 #, python-format
84 84 msgid "Title must have less than %s characters"
85 85 msgstr "Заголовок должен иметь меньше %s символов"
86 86
87 87 #: forms.py:172
88 88 #, python-format
89 89 msgid "Text must have less than %s characters"
90 90 msgstr "Текст должен быть короче %s символов"
91 91
92 92 #: forms.py:194
93 93 msgid "Invalid URL"
94 94 msgstr "Неверный URL"
95 95
96 96 #: forms.py:215
97 97 msgid "Invalid additional thread list"
98 98 msgstr "Неверный список дополнительных тем"
99 99
100 100 #: forms.py:250
101 101 msgid "Either text or image must be entered."
102 102 msgstr "Текст или картинка должны быть введены."
103 103
104 104 #: forms.py:266
105 105 #, python-format
106 106 msgid "Wait %s seconds after last posting"
107 107 msgstr "Подождите %s секунд после последнего постинга"
108 108
109 109 #: forms.py:278
110 110 #, python-format
111 111 msgid "Image must be less than %s bytes"
112 112 msgstr "Изображение должно быть менее %s байт"
113 113
114 114 #: forms.py:325 templates/boards/posting_general.html:148
115 115 #: templates/boards/rss/post.html:10 templates/boards/tags.html:7
116 116 msgid "Tags"
117 117 msgstr "Метки"
118 118
119 119 #: forms.py:332
120 120 msgid "Inappropriate characters in tags."
121 121 msgstr "Недопустимые символы в метках."
122 122
123 #: forms.py:343
124 msgid "Need at least 1 required tag."
125 msgstr "Нужна хотя бы 1 обязательная метка."
123 #: forms.py:345
124 #| msgid "Need at least 1 required tag."
125 msgid "Need at least one of the tags: "
126 msgstr "Нужна хотя бы одна из меток: "
126 127
127 #: forms.py:355
128 #: forms.py:358
128 129 msgid "Theme"
129 130 msgstr "Тема"
130 131
131 #: forms.py:356
132 #: forms.py:359
132 133 msgid "User name"
133 134 msgstr "Имя пользователя"
134 135
135 #: forms.py:357
136 #: forms.py:360
136 137 msgid "Time zone"
137 138 msgstr "Часовой пояс"
138 139
139 #: forms.py:363
140 #: forms.py:366
140 141 msgid "Inappropriate characters."
141 142 msgstr "Недопустимые символы."
142 143
143 144 #: templates/boards/404.html:6
144 145 msgid "Not found"
145 146 msgstr "Не найдено"
146 147
147 148 #: templates/boards/404.html:12
148 149 msgid "This page does not exist"
149 150 msgstr "Этой страницы не существует"
150 151
151 152 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
152 153 msgid "Authors"
153 154 msgstr "Авторы"
154 155
155 156 #: templates/boards/authors.html:26
156 157 msgid "Distributed under the"
157 158 msgstr "Распространяется под"
158 159
159 160 #: templates/boards/authors.html:28
160 161 msgid "license"
161 162 msgstr "лицензией"
162 163
163 164 #: templates/boards/authors.html:30
164 165 msgid "Repository"
165 166 msgstr "Репозиторий"
166 167
167 168 #: templates/boards/base.html:13
168 169 msgid "Feed"
169 170 msgstr "Лента"
170 171
171 172 #: templates/boards/base.html:30
172 173 msgid "All threads"
173 174 msgstr "Все темы"
174 175
175 176 #: templates/boards/base.html:36
176 177 msgid "Add tags"
177 178 msgstr "Добавить метки"
178 179
179 180 #: templates/boards/base.html:38
180 181 msgid "Tag management"
181 182 msgstr "Управление метками"
182 183
183 184 #: templates/boards/base.html:43 templates/boards/base.html.py:44
184 185 #: templates/boards/notifications.html:8
185 186 msgid "Notifications"
186 187 msgstr "Уведомления"
187 188
188 189 #: templates/boards/base.html:51 templates/boards/settings.html:9
189 190 msgid "Settings"
190 191 msgstr "Настройки"
191 192
192 193 #: templates/boards/base.html:64
193 194 msgid "Admin"
194 195 msgstr "Администрирование"
195 196
196 197 #: templates/boards/base.html:66
197 198 #, python-format
198 199 msgid "Speed: %(ppd)s posts per day"
199 200 msgstr "Скорость: %(ppd)s сообщений в день"
200 201
201 202 #: templates/boards/base.html:68
202 203 msgid "Up"
203 204 msgstr "Вверх"
204 205
205 206 #: templates/boards/notifications.html:17
206 207 #: templates/boards/posting_general.html:81 templates/search/search.html:26
207 208 msgid "Previous page"
208 209 msgstr "Предыдущая страница"
209 210
210 211 #: templates/boards/notifications.html:27
211 212 #: templates/boards/posting_general.html:121 templates/search/search.html:37
212 213 msgid "Next page"
213 214 msgstr "Следующая страница"
214 215
215 #: templates/boards/post.html:32
216 #: templates/boards/post.html:25
216 217 msgid "Open"
217 218 msgstr "Открыть"
218 219
219 #: templates/boards/post.html:34 templates/boards/post.html.py:38
220 #: templates/boards/post.html:27 templates/boards/post.html.py:31
220 221 msgid "Reply"
221 222 msgstr "Ответ"
222 223
223 #: templates/boards/post.html:43
224 #: templates/boards/post.html:36
224 225 msgid "Edit"
225 226 msgstr "Изменить"
226 227
227 #: templates/boards/post.html:45
228 #: templates/boards/post.html:38
228 229 msgid "Edit thread"
229 230 msgstr "Изменить тему"
230 231
231 #: templates/boards/post.html:75
232 #: templates/boards/post.html:70
232 233 msgid "Replies"
233 234 msgstr "Ответы"
234 235
235 #: templates/boards/post.html:86 templates/boards/thread.html:31
236 #: templates/boards/post.html:82 templates/boards/thread.html:31
236 237 msgid "messages"
237 238 msgstr "сообщений"
238 239
239 #: templates/boards/post.html:87 templates/boards/thread.html:32
240 #: templates/boards/post.html:83 templates/boards/thread.html:32
240 241 msgid "images"
241 242 msgstr "изображений"
242 243
243 244 #: templates/boards/posting_general.html:65
244 245 msgid "Edit tag"
245 246 msgstr "Изменить метку"
246 247
247 248 #: templates/boards/posting_general.html:68
248 249 #, python-format
249 250 msgid "This tag has %(thread_count)s threads and %(post_count)s posts."
250 251 msgstr "С этой меткой есть %(thread_count)s тем и %(post_count)s сообщений."
251 252
252 253 #: templates/boards/posting_general.html:96
253 254 #, python-format
254 255 msgid "Skipped %(count)s replies. Open thread to see all replies."
255 256 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
256 257
257 258 #: templates/boards/posting_general.html:126
258 259 msgid "No threads exist. Create the first one!"
259 260 msgstr "Нет тем. Создайте первую!"
260 261
261 262 #: templates/boards/posting_general.html:132
262 263 msgid "Create new thread"
263 264 msgstr "Создать новую тему"
264 265
265 266 #: templates/boards/posting_general.html:137 templates/boards/preview.html:16
266 267 #: templates/boards/thread_normal.html:46
267 268 msgid "Post"
268 269 msgstr "Отправить"
269 270
270 271 #: templates/boards/posting_general.html:142
271 272 msgid "Tags must be delimited by spaces. Text or image is required."
272 273 msgstr ""
273 274 "Метки должны быть разделены пробелами. Текст или изображение обязательны."
274 275
275 276 #: templates/boards/posting_general.html:145
276 277 #: templates/boards/thread_normal.html:51
277 278 msgid "Text syntax"
278 279 msgstr "Синтаксис текста"
279 280
280 281 #: templates/boards/posting_general.html:161
281 282 msgid "Pages:"
282 283 msgstr "Страницы: "
283 284
284 285 #: templates/boards/preview.html:6 templates/boards/staticpages/help.html:20
285 286 msgid "Preview"
286 287 msgstr "Предпросмотр"
287 288
288 289 #: templates/boards/rss/post.html:5
289 290 msgid "Post image"
290 291 msgstr "Изображение сообщения"
291 292
292 293 #: templates/boards/settings.html:17
293 294 msgid "You are moderator."
294 295 msgstr "Вы модератор."
295 296
296 297 #: templates/boards/settings.html:21
297 298 msgid "Hidden tags:"
298 299 msgstr "Скрытые метки:"
299 300
300 301 #: templates/boards/settings.html:29
301 302 msgid "No hidden tags."
302 303 msgstr "Нет скрытых меток."
303 304
304 305 #: templates/boards/settings.html:38
305 306 msgid "Save"
306 307 msgstr "Сохранить"
307 308
308 309 #: templates/boards/staticpages/banned.html:6
309 310 msgid "Banned"
310 311 msgstr "Заблокирован"
311 312
312 313 #: templates/boards/staticpages/banned.html:11
313 314 msgid "Your IP address has been banned. Contact the administrator"
314 315 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
315 316
316 317 #: templates/boards/staticpages/help.html:6
317 318 #: templates/boards/staticpages/help.html:10
318 319 msgid "Syntax"
319 320 msgstr "Синтаксис"
320 321
321 322 #: templates/boards/staticpages/help.html:11
322 323 msgid "Italic text"
323 324 msgstr "Курсивный текст"
324 325
325 326 #: templates/boards/staticpages/help.html:12
326 327 msgid "Bold text"
327 328 msgstr "Полужирный текст"
328 329
329 330 #: templates/boards/staticpages/help.html:13
330 331 msgid "Spoiler"
331 332 msgstr "Спойлер"
332 333
333 334 #: templates/boards/staticpages/help.html:14
334 335 msgid "Link to a post"
335 336 msgstr "Ссылка на сообщение"
336 337
337 338 #: templates/boards/staticpages/help.html:15
338 339 msgid "Strikethrough text"
339 340 msgstr "Зачеркнутый текст"
340 341
341 342 #: templates/boards/staticpages/help.html:16
342 343 msgid "Comment"
343 344 msgstr "Комментарий"
344 345
345 346 #: templates/boards/staticpages/help.html:17
346 347 #: templates/boards/staticpages/help.html:18
347 348 msgid "Quote"
348 349 msgstr "Цитата"
349 350
350 351 #: templates/boards/staticpages/help.html:20
351 352 msgid "You can try pasting the text and previewing the result here:"
352 353 msgstr "Вы можете попробовать вставить текст и проверить результат здесь:"
353 354
354 355 #: templates/boards/tags.html:23
355 356 msgid "No tags found."
356 357 msgstr "Метки не найдены."
357 358
358 359 #: templates/boards/tags.html:26
359 360 msgid "All tags"
360 361 msgstr "Все метки"
361 362
362 363 #: templates/boards/thread.html:33
363 364 msgid "Last update: "
364 365 msgstr "Последнее обновление: "
365 366
366 367 #: templates/boards/thread_gallery.html:21
367 368 #: templates/boards/thread_normal.html:16
368 369 msgid "Normal mode"
369 370 msgstr "Нормальный режим"
370 371
371 372 #: templates/boards/thread_gallery.html:22
372 373 #: templates/boards/thread_normal.html:17
373 374 msgid "Gallery mode"
374 375 msgstr "Режим галереи"
375 376
376 377 #: templates/boards/thread_gallery.html:52
377 378 msgid "No images."
378 379 msgstr "Нет изображений."
379 380
380 381 #: templates/boards/thread_normal.html:25
381 382 msgid "posts to bumplimit"
382 383 msgstr "сообщений до бамплимита"
383 384
384 385 #: templates/boards/thread_normal.html:39
385 386 msgid "Reply to thread"
386 387 msgstr "Ответить в тему"
387 388
388 389 #: templates/boards/thread_normal.html:52
389 390 msgid "Close form"
390 391 msgstr "Закрыть форму"
391 392
392 393 #: templates/boards/thread_normal.html:68
393 394 msgid "Update"
394 395 msgstr "Обновить"
395 396
396 397 #: templates/search/search.html:17
397 398 msgid "Ok"
398 399 msgstr "Ок"
399 400
400 401 #~ msgid "tag1 several_words_tag"
401 402 #~ msgstr "метка1 метка_из_нескольких_слов"
General Comments 0
You need to be logged in to leave comments. Login now