{"id":329992,"date":"2022-02-22T15:04:45","date_gmt":"2022-02-22T15:04:45","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=329992"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=329992","title":{"rendered":"<span>\u041a\u0430\u043a \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u0439\u0442 \u043d\u0430 Django. \u0427\u0430\u0441\u0442\u044c 2. JavaScript \u0438 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445<\/span>"},"content":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 <a href=\"https:\/\/habr.com\/ru\/company\/pvs-studio\/blog\/648717\/\">\u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u043c\u044b \u043f\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u043b\u0438\u0441\u044c \u0441 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0434\u043b\u044f Django \u0438 \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f-\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430. \u0421\u0430\u043c\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0438\u0437\u0443\u0447\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0430\u0439\u0442\u0430, \u043d\u0430\u043f\u0438\u0441\u0430\u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0432 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f JavaScript.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/21b\/8e7\/f13\/21b8e7f13de0c95bcfd26f8048e974b3.png\" width=\"780\" height=\"440\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/21b\/8e7\/f13\/21b8e7f13de0c95bcfd26f8048e974b3.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0442\u0435\u043a\u0441\u0442\u0430<\/h2>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u0438\u043d\u043e\u0441\u0442\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 (\u0438\u043b\u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442), \u044d\u0442\u043e, \u043c\u044f\u0433\u043a\u043e \u0433\u043e\u0432\u043e\u0440\u044f, \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u0438\u044f\u0442\u043d\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043b\u0443\u0447\u0448\u0435 \u0442\u0430\u043a\u0438\u0435 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0432\u043e\u0432\u0440\u0435\u043c\u044f \u0438\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u043e\u0442 \u043d\u0438\u0445.<\/p>\n<p>\u041d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u043d\u0430 \u0434\u0432\u0430 \u0442\u0438\u043f\u0430:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u043e\u0448\u0438\u0431\u043e\u043a \u0432\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u0430\u0445.<\/p>\n<\/li>\n<\/ol>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445<\/h3>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u043f\u0435\u0440\u0432\u043e\u043c\u0443 \u0442\u0435\u0441\u0442\u0443. \u0415\u0433\u043e \u043f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">from requests import get from bs4 import BeautifulSoup from bs4.element import Tag  from django.test import TestCase  # \u0410 CODE_OF_FIRST_RUSSIAN_LETTER = 1040  # \u044f CODE_OF_LAST_RUSSIAN_LETTER = 1103  CODES_OF_RUSSIAN_SYMBOLS = list(     range(         CODE_OF_FIRST_RUSSIAN_LETTER,         CODE_OF_LAST_RUSSIAN_LETTER + 1     ) ) + [1025, 1105] # \u0401, \u0451  ENGLISH_PAGES = (     'https:\/\/pvs-studio.com\/ru\/',     'https:\/\/pvs-studio.com\/ru\/blog\/posts\/',     'https:\/\/pvs-studio.com\/ru\/for-clients\/',     'https:\/\/pvs-studio.com\/ru\/docs\/',     # ... )  def is_russian_symbol(symbol: str) -> bool:     \"\"\"True if symbol is Russian\"\"\"      return ord(symbol) in CODES_OF_RUSSIAN_SYMBOLS  def get_page_words(content: Tag) -> tuple:     \"\"\"Return page's words\"\"\"      page_text = content.get_text()     page_text_without_extra_spaces = \" \".join(page_text.split())     page_words = page_text_without_extra_spaces.split()      return tuple(page_words)  class CorrectTranslationTests(TestCase):     \"\"\"Test correct translation\"\"\"      def test_russian_symbols_presence(self):         \"\"\"Test English pages for presence of Russian symbols\"\"\"          for page in ENGLISH_PAGES:             page_content = BeautifulSoup(get(page).content, 'html.parser')             main_div_content = page_content.find(                 'div', {\"class\": 'b-content'}             )              if main_div_content:                 page_words = get_page_words(main_div_content)                  for word in page_words:                     for symbol in word:                         error_message = f'\\n\"{symbol}\" ({word}) on {page}\\n'                          with self.subTest(error_message):                             self.assertFalse(is_russian_symbol(symbol))<\/code><\/pre>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0435\u0433\u043e \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e.<\/p>\n<h4>\u0418\u043c\u043f\u043e\u0440\u0442\u044b<\/h4>\n<p>\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043d\u0430 \u043d\u0435\u0451 GET-\u0437\u0430\u043f\u0440\u043e\u0441. \u0421 \u044d\u0442\u0438\u043c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u00a0<em>get<\/em>.<\/p>\n<pre><code class=\"python\">from requests import get<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f\u00a0<a href=\"https:\/\/www.crummy.com\/software\/BeautifulSoup\/bs4\/doc\/\"><em>Beautiful Soup<\/em><\/a>. \u0414\u043b\u044f type hinting \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c\u00a0<em>Tag<\/em>.<\/p>\n<pre><code class=\"python\">from bs4 import BeautifulSoup from bs4.element import Tag<\/code><\/pre>\n<p>\u0418 \u0442\u0430\u043a\u0436\u0435 \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u00a0<em>TestCase<\/em>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b.<\/p>\n<pre><code class=\"python\">from django.test import TestCase<\/code><\/pre>\n<h4>\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b<\/h4>\n<p>\u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u0430 \u0435\u0441\u0442\u044c \u0435\u0433\u043e \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0439 \u043a\u043e\u0434. \u0412 Python \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438\u00a0<em>ord<\/em>. \u041a\u043e\u0434\u044b \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0438\u043c\u0435\u044e\u0442 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u043e\u0442 1040 (\u0410) \u0434\u043e 1103 (\u044f). \u0422\u0430\u043a\u0436\u0435 \u043a \u043d\u0438\u043c \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f 1025 (\u0401) \u0438 1105 (\u0451). \u0418\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u0438 \u043a\u043e\u0434\u044b \u043c\u044b \u0437\u0430\u043d\u043e\u0441\u0438\u043c \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <em>CODES_OF_RUSSIAN_SYMBOLS<\/em>. \u0421 \u0435\u0451 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0431\u0443\u0434\u0435\u0442 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043f\u043e\u0438\u0441\u043a \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432.<\/p>\n<pre><code class=\"python\"># \u0410 CODE_OF_FIRST_RUSSIAN_LETTER = 1040  # \u044f CODE_OF_LAST_RUSSIAN_LETTER = 1103  CODES_OF_RUSSIAN_SYMBOLS = list(     range(         CODE_OF_FIRST_RUSSIAN_LETTER,         CODE_OF_LAST_RUSSIAN_LETTER + 1     ) ) + [1025, 1105] # \u0401, \u0451<\/code><\/pre>\n<p>\u0412\u00a0<em>ENGLISH_PAGES<\/em>\u00a0\u043c\u044b \u0437\u0430\u043d\u043e\u0441\u0438\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c:<\/p>\n<pre><code class=\"python\">ENGLISH_PAGES = (     'https:\/\/pvs-studio.com\/ru\/',     'https:\/\/pvs-studio.com\/ru\/blog\/posts\/',     'https:\/\/pvs-studio.com\/ru\/for-clients\/',     'https:\/\/pvs-studio.com\/ru\/docs\/',     # ... )<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f is_russian_symbol<\/h4>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u0441\u0438\u043c\u0432\u043e\u043b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u00a0<em>is_russian_symbol<\/em>. \u041e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043e\u0434 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0435\u0433\u043e \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439\u00a0<em>CODES_OF_RUSSIAN_SYMBOLS<\/em>.<\/p>\n<pre><code class=\"python\">def is_russian_symbol(symbol: str) -> bool:     \"\"\"True if symbol is Russian\"\"\"      return ord(symbol) in CODES_OF_RUSSIAN_SYMBOLS<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f get_page_words<\/h4>\n<p>\u041a\u043e\u043d\u0442\u0435\u043d\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043f\u043e\u043c\u0438\u043c\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u0435\u0449\u0451 \u0438 \u043d\u0435\u043d\u0443\u0436\u043d\u044b\u0435 \u043d\u0430\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0435\u0433\u0438. \u0427\u0442\u043e\u0431\u044b \u0443\u0431\u0440\u0430\u0442\u044c \u0438\u0445, \u0443\u00a0<em>BeautifulSoup<\/em>\u00a0\u0435\u0441\u0442\u044c \u043a\u0440\u0443\u0442\u043e\u0439 \u043c\u0435\u0442\u043e\u0434\u00a0<em>get_text<\/em>. \u041e\u043d \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043a\u0441\u0442 \u0438\u0437 HTML-\u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 (1). \u041c\u044b \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0448\u044c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434, \u043d\u043e \u0442\u0435\u043a\u0441\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043d \u0432\u044b\u0434\u0430\u0451\u0442, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u043f\u043e\u0434\u0440\u044f\u0434 \u0438\u0434\u0443\u0449\u0438\u0445 \u043f\u0440\u043e\u0431\u0435\u043b\u043e\u0432. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438\u0445 \u043c\u044b \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u043d\u0430 \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e\u00a0<em>split<\/em>\u00a0\u0438\u00a0<em>join<\/em>\u00a0(2). \u041f\u043e\u043b\u0443\u0447\u0438\u0432 \u0441\u0442\u0440\u043e\u043a\u0443, \u043c\u044b \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u043c \u0435\u0451 \u043d\u0430 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043b\u043e\u0432 (3), \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0438\u0442\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u043d\u0435\u043c\u0443.<\/p>\n<pre><code class=\"python\">def get_page_words(content: Tag) -> tuple:     \"\"\"Return page's words\"\"\"      page_text = content.get_text() # (1)     page_text_without_extra_spaces = \" \".join(page_text.split()) # (2)     page_words = page_text_without_extra_spaces.split() # (3)      return tuple(page_words)<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0442\u0435\u0441\u0442\u0430<\/h4>\n<p>\u0422\u0435\u0441\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0442\u0430\u043a:<\/p>\n<ol>\n<li>\n<p>\u041e\u0431\u0445\u043e\u0434\u0438\u0442 \u0432\u0441\u0435 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b;<\/p>\n<\/li>\n<li>\n<p>\u0418\u0437 \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0432\u0435\u0441\u044c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 (\u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0431\u044b\u043b \u0432\u00a0<a href=\"https:\/\/habr.com\/ru\/company\/pvs-studio\/blog\/648717\/\">\u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>);<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430 \u043d\u0430\u0448\u0435\u043c \u0441\u0430\u0439\u0442\u0435 \u0431<strong>\u043e<\/strong>\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0442\u0435\u043a\u0441\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0438\u0434\u0438\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432\u00a0<em>div<\/em>\u00a0\u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c\u00a0<em>b-content<\/em>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0435\u0441\u0442 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0438\u0437 \u043d\u0435\u0433\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430\u00a0<em>find<\/em>. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0431\u043b\u043e\u043a\u0438\u00a0<em>div<\/em>\u00a0\u043c\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e;<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0432\u0441\u0435 \u0441\u043b\u043e\u0432\u0430;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0441\u043b\u043e\u0432\u0443 \u0438 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0441\u0438\u043c\u0432\u043e\u043b\u0443;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0447\u0442\u043e \u0441\u0438\u043c\u0432\u043e\u043b \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0443\u0441\u0441\u043a\u0438\u043c.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">class CorrectTranslationTests(TestCase):     \"\"\"Test correct translation\"\"\"      def test_russian_symbols_presence(self):         \"\"\"Test English pages for presence of Russian symbols\"\"\"          for page in ENGLISH_PAGES: # (1)             # (2)             page_content = BeautifulSoup(get(page).content, 'html.parser')             main_div_content = page_content.find(                 'div', {\"class\": 'b-content'}             ) # (3)              if main_div_content:                 page_words = get_page_words(main_div_content) # (4)                  for word in page_words: # (5)                     for symbol in word: # (5)                         error_message = f'\\n\"{symbol}\" ({word}) on {page}\\n'                          with self.subTest(error_message):                             # (6)                             self.assertFalse(is_russian_symbol(symbol))<\/code><\/pre>\n<h4>\u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u0430<\/h4>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0432 \u0441\u0435\u0439\u0447\u0430\u0441 \u0442\u0435\u0441\u0442, \u043c\u044b \u043d\u0435 \u0443\u0432\u0438\u0434\u0438\u043c \u043e\u0448\u0438\u0431\u043e\u043a. \u0427\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0435\u0433\u043e \u0434\u043b\u044f\u00a0<a href=\"https:\/\/pvs-studio.com\/ru\/\">\u0440\u0443\u0441\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b<\/a>.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/09f\/2ca\/872\/09f2ca87232d6ebcebb9118504c6d3cc.png\" width=\"475\" height=\"415\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/09f\/2ca\/872\/09f2ca87232d6ebcebb9118504c6d3cc.png\"\/><figcaption><\/figcaption><\/figure>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u043e\u0448\u0438\u0431\u043e\u043a \u0432 django.po \u0444\u0430\u0439\u043b\u0430\u0445<\/h3>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0443\u0432\u0438\u0434\u0435\u0432 \u044d\u0442\u043e\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a, \u0432\u044b \u0437\u0430\u0434\u0430\u043b\u0438\u0441\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u043c: \u00ab\u0417\u0430\u0447\u0435\u043c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b\u00a0<em>django.po<\/em>, \u0435\u0441\u043b\u0438 \u043c\u044b \u0443\u0436\u0435 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445?\u00bb. \u041d\u0430 \u044d\u0442\u043e \u0435\u0441\u0442\u044c \u043c\u0438\u043d\u0438\u043c\u0443\u043c 2 \u043f\u0440\u0438\u0447\u0438\u043d\u044b:<\/p>\n<ol>\n<li>\n<p>\u0422\u0435\u0441\u0442\u00a0<em>django.po<\/em>\u00a0\u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043e\u0442\u043b\u043e\u0432\u0438\u0442\u044c \u0447\u0430\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043e\u043a \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0431\u044b\u0441\u0442\u0440\u043e. \u0410 \u0437\u043d\u0430\u0447\u0438\u0442, \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0444\u0440\u0430\u0437\u044b \u2013 \u043f\u0443\u0441\u0442\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 (\u0442. \u0435. msgstr \u0445\u0440\u0430\u043d\u0438\u0442 \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443), \u0442\u0435\u0441\u0442 \u0444\u0430\u0439\u043b\u0430\u00a0<em>django.po<\/em>\u00a0\u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u044d\u0442\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c, \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0442\u0435\u0441\u0442\u0430, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0430\u043d\u0435\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u0441\u0435\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c 3 \u0443\u0441\u043b\u043e\u0432\u0438\u044f. \u0410 \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u043e, \u0447\u0442\u043e \u0432 \u0444\u0430\u0439\u043b\u0435\u00a0<em>django.po<\/em>\u00a0\u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c:<\/p>\n<ol>\n<li>\n<p>\u0421\u0442\u0440\u043e\u043a\u0438 \u0441 \u043f\u0443\u0441\u0442\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c\u00a0<em>msgstr<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0440\u043e\u043a\u0438, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0439\u0441\u044f \u0441 \u00abmsgid\u00bb;<\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0440\u043e\u043a\u0438, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0439\u0441\u044f \u0441 \u00ab#, fuzzy\u00bb.<\/p>\n<\/li>\n<\/ol>\n<p>\u041d\u0435\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u044d\u0442\u0438\u0445 \u043f\u0443\u043d\u043a\u0442\u043e\u0432 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u044f \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0441 \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438.<\/p>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0442\u0435\u0441\u0442\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u043e\u0432 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">from django.test import TestCase  MY_PROJECT_LOCALE_PATH = 'my_project\/locale\/'  DJANGO_PO_PATH = 'LC_MESSAGES\/django.po'  RU_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'ru\/' + DJANGO_PO_PATH  EN_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'en\/' + DJANGO_PO_PATH  def is_msgstr_empty(line_with_msgstr: str, next_line: str) -> bool:     \"\"\"True if msgstr is empty\"\"\"      return 'msgstr \"\"' in line_with_msgstr and next_line == '\\n'  def get_msgstr_errors(file: list) -> tuple:     \"\"\"Return numbers of lines where msgstr is empty\"\"\"      errors = []      for number in range(len(file) - 1):         line = file[number]         next_line = file[number + 1]          if is_msgstr_empty(line, next_line):             line_number = number + 1              errors.append(line_number)      return tuple(errors)  def get_fuzzy_and_msgid_errors(file: list) -> tuple:     \"\"\"Return numbers of lines that starts with '#, fuzzy' or #~ msgid\"\"\"      errors = []      for line_number, line in enumerate(file, 1):         if line.startswith('#, fuzzy') or line.startswith('#~ msgid'):             errors.append(line_number)      return tuple(errors)  def get_locale_files_errors() -> dict:     \"\"\"Return errors for ru and en django.po files\"\"\"      with open(RU_DJANGO_PO_PATH, 'r') as file:         ru_file = list(file).copy()      with open(EN_DJANGO_PO_PATH, 'r') as file:         en_file = list(file).copy()      errors = {         'ru': get_fuzzy_and_msgid_errors(ru_file) + get_msgstr_errors(ru_file),         'en': get_fuzzy_and_msgid_errors(en_file) + get_msgstr_errors(en_file),     }      return errors  class LocaleTests(TestCase):     \"\"\"Tests for locale files\"\"\"      def test_locale_files(self):         \"\"\"Test en and ru django.po files\"\"\"          for language, errors in get_locale_files_errors().items():             with self.subTest(f'Errors for {language}: {sorted(errors)}'):                 self.assertEqual(len(errors), 0)<\/code><\/pre>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u0447\u0442\u043e \u0432 \u043d\u0451\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442.<\/p>\n<h4>\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b<\/h4>\n<p>\u0412 \u043d\u0438\u0445 \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u0438 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 \u043f\u0443\u0442\u0438 \u043a\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u0430\u043c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0435\u0440\u0432\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:\u00a0<em>pvs\/locale\/ru\/LC_MESSAGES\/django.po<\/em>.<\/p>\n<pre><code class=\"python\">MY_PROJECT_LOCALE_PATH = 'my_project\/locale\/'  DJANGO_PO_PATH = 'LC_MESSAGES\/django.po'  RU_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'ru\/' + DJANGO_PO_PATH  EN_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'en\/' + DJANGO_PO_PATH<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f is_msgstr_empty<\/h4>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u0442\u044c \u043b\u0438 \u0432 \u0441\u0442\u0440\u043e\u043a\u0435\u00a0<em>msgstr<\/em>\u00a0\u0441 \u043f\u0443\u0441\u0442\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c.<\/p>\n<pre><code class=\"python\">def is_msgstr_empty(line_with_msgstr: str, next_line: str) -> bool:     \"\"\"True if msgstr is empty\"\"\"      return 'msgstr \"\"' in line_with_msgstr and next_line == '\\n'<\/code><\/pre>\n<h5>\u0418 \u0442\u0443\u0442 \u0432\u0440\u043e\u0434\u0435 \u0431\u044b \u0432\u0441\u0451 \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u043d\u043e \u0434\u043b\u044f \u0447\u0435\u0433\u043e \u0447\u0430\u0441\u0442\u044c \u00aband next_line == &#8216;\\n&#8217;\u00bb? \u0412\u0441\u0451 \u043f\u0440\u043e\u0441\u0442\u043e. \u0415\u0441\u043b\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435\u00a0msgstr\u00a0\u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u0434\u043b\u0438\u043d\u043d\u044b\u043c, Django \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0451\u0442 \u0435\u0433\u043e \u043d\u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443. \u0412 \u0438\u0442\u043e\u0433\u0435, \u0441\u0442\u0440\u043e\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0447\u0430\u0441\u0442\u044c \u00abmsgstr &#171;&#187;\u00bb, \u043d\u043e \u043e\u043d\u0430 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u043e\u0448\u0438\u0431\u043e\u0447\u043d\u043e\u0439, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0443\u043a\u0430\u0437\u0430\u043d \u043d\u0438\u0436\u0435.<\/h5>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fb7\/6de\/ac4\/fb76deac4a1969bba63e4b8ae8ddfec9.png\" width=\"484\" height=\"110\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fb7\/6de\/ac4\/fb76deac4a1969bba63e4b8ae8ddfec9.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0415\u0441\u043b\u0438 \u0436\u0435 \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u043c, \u0442\u043e \u043e\u043d \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f \u043d\u0430 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u0441\u00a0<em>msgstr<\/em>. \u0410 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0432\u043d\u0430 \u00ab<em>\\n<\/em>\u00bb.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/b2d\/003\/2d6\/b2d0032d67fcf42d5a50a152255d5cc5.jpg\" width=\"301\" height=\"68\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b2d\/003\/2d6\/b2d0032d67fcf42d5a50a152255d5cc5.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f get_msgstr_errors<\/h4>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u0444\u0443\u043d\u043a\u0446\u0438\u0438\u00a0<em>get_msgstr_errors<\/em>. \u041e\u043d\u0430 \u0432\u044b\u0434\u0430\u0451\u0442 \u043d\u043e\u043c\u0435\u0440\u0430 \u0441\u0442\u0440\u043e\u043a, \u0433\u0434\u0435\u00a0<em>msgstr<\/em>\u00a0\u0438\u043c\u0435\u0435\u0442 \u043f\u0443\u0441\u0442\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. \u041f\u0440\u0438\u043d\u0446\u0438\u043f \u0440\u0430\u0431\u043e\u0442\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e \u0441\u0442\u0440\u043e\u043a\u0430\u043c \u0444\u0430\u0439\u043b\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043d\u043e\u0441\u0438\u0442 \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e\u00a0<em>line<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0417\u0430\u043d\u043e\u0441\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e\u00a0<em>next_line<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0421 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0440\u0430\u043d\u0435\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u0438 \u0442\u0435\u043a\u0443\u0449\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u043f\u0443\u0441\u0442\u043e\u0439\u00a0<em>msgstr<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u0434\u0430, \u0442\u043e \u0437\u0430\u043d\u043e\u0441\u0438\u0442 \u043d\u043e\u043c\u0435\u0440 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u043e\u0431\u0449\u0438\u0439 \u0441\u043f\u0438\u0441\u043e\u043a.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">def get_msgstr_errors(file: list) -> tuple:     \"\"\"Return numbers of lines where msgstr is empty\"\"\"      errors = []      for number in range(len(file) - 1): # (1)         line = file[number] # (2)         next_line = file[number + 1] # (3)          if is_msgstr_empty(line, next_line): # (4)             line_number = number + 1              errors.append(line_number) # (5)      return tuple(errors)<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f get_fuzzy_and_msgid_errors<\/h4>\n<p>\u0418\u0434\u0451\u043c \u0434\u0430\u043b\u044c\u0448\u0435. \u0424\u0443\u043d\u043a\u0446\u0438\u044f <em>get_fuzzy_and_msgid_errors<\/em> \u043e\u0431\u0445\u043e\u0434\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0447\u0442\u043e \u043e\u043d\u0438 \u043d\u0435 \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0442\u0441\u044f \u0441 \u00ab#, fuzzy\u00bb \u0438\u043b\u0438 \u0441 \u00ab#~ msgid\u00bb. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u043d\u0435 \u0442\u0430\u043a \u2014 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u043e\u043c\u0435\u0440 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<pre><code class=\"python\">def get_fuzzy_and_msgid_errors(file: list) -> tuple:     \"\"\"Return numbers of lines that starts with '#, fuzzy' or #~ msgid\"\"\"      errors = []      for line_number, line in enumerate(file, 1):         if line.startswith('#, fuzzy') or line.startswith('#~ msgid'):             errors.append(line_number)      return tuple(errors)<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f get_locale_files_errors<\/h4>\n<p><em>get_locale_files_errors<\/em>\u00a0\u0432\u044b\u0434\u0430\u0451\u0442 \u0441\u043b\u043e\u0432\u0430\u0440\u044c \u0441 \u044f\u0437\u044b\u043a\u043e\u043c \u0444\u0430\u0439\u043b\u0430\u00a0<em>django.po<\/em>\u00a0\u0438 \u0435\u0433\u043e \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438.<\/p>\n<pre><code class=\"python\">def get_locale_files_errors() -> dict:     \"\"\"Return errors for ru and en django.po files\"\"\"      with open(RU_DJANGO_PO_PATH, 'r') as file:         ru_file = list(file).copy()      with open(EN_DJANGO_PO_PATH, 'r') as file:         en_file = list(file).copy()      errors = {         'ru': get_fuzzy_and_msgid_errors(ru_file) + get_msgstr_errors(ru_file),         'en': get_fuzzy_and_msgid_errors(en_file) + get_msgstr_errors(en_file),     }      return errors<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0442\u0435\u0441\u0442\u0430<\/h4>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u0441\u0430\u043c\u043e\u043c\u0443 \u0442\u0435\u0441\u0442\u0443. \u0412\u043e\u0442 \u0447\u0442\u043e \u043e\u043d \u0434\u0435\u043b\u0430\u0435\u0442:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e \u0441\u043b\u043e\u0432\u0430\u0440\u044e \u0441 \u044f\u0437\u044b\u043a\u043e\u043c \u0438 \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u0430;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0447\u0442\u043e \u0447\u0438\u0441\u043b\u043e \u043e\u0448\u0438\u0431\u043e\u043a \u0440\u0430\u0432\u043d\u043e 0.<\/p>\n<pre><code class=\"python\">class LocaleTests(TestCase):     \"\"\"Tests for locale files\"\"\"      def test_locale_files(self):         \"\"\"Test en and ru django.po files\"\"\"          for language, errors in get_locale_files_errors().items(): # (1)             with self.subTest(f'Errors for {language}: {sorted(errors)}'):                 self.assertEqual(len(errors), 0) # (2)<\/code><\/pre>\n<\/li>\n<\/ol>\n<h4>\u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u0430<\/h4>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0442\u0435\u0441\u0442 \u0434\u043b\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u0430:<\/p>\n<pre><code class=\"python\"># ...  msgid \"\u0424\u0440\u0430\u0437\u0430 \u0441 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u043c \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u043e\u043c\" msgstr \"Phrase with correct translation\"  msgid \"\u0414\u043b\u044f \u044d\u0442\u043e\u0439 \u0444\u0440\u0430\u0437\u044b \u043d\u0435\u0442 \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430\" msgstr \"\"  #, fuzzy #| msgid \"\u0424\u0440\u0430\u0437\u0430 1. \u0420\u0430\u043d\u044c\u0448\u0435 \u043e\u043d\u0430 \u0431\u044b\u043b\u0430 \u0442\u0430\u043a\u043e\u0439\" msgid \"\u0424\u0440\u0430\u0437\u0430 2. \u0422\u0435\u043f\u0435\u0440\u044c \u043e\u043d\u0430 \u043f\u043e\u043c\u0435\u043d\u044f\u043b\u0430\u0441\u044c \u043d\u0430 \u0442\u0430\u043a\u0443\u044e\" msgstr \"Phrase 1. It used to be like this\"  #~ msgid \"\u042d\u0442\u0430 \u0444\u0440\u0430\u0437\u0430 \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f\" #~ msgstr \"This phrase is not used anywhere\"<\/code><\/pre>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c63\/54b\/495\/c6354b4957b9947a89d0a58a7df11e08.png\" width=\"489\" height=\"132\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c63\/54b\/495\/c6354b4957b9947a89d0a58a7df11e08.png\"\/><figcaption><\/figcaption><\/figure>\n<h2>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c JavaScript<\/h2>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0434\u0432\u0443\u0445 \u043d\u0430\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u044f \u043a\u043e\u0440\u043e\u0442\u043a\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c JS. \u0414\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\u00a0<a href=\"https:\/\/learn.javascript.ru\/testing\">\u044d\u0442\u0443 \u0441\u0442\u0430\u0442\u044c\u044e<\/a>\u00a0\u043e\u0442\u00a0<a href=\"https:\/\/learn.javascript.ru\/\">learn.javascript.ru<\/a>.<\/p>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u043e\u0432. \u041d\u0430\u043c \u043d\u0443\u0436\u0435\u043d\u00a0<em>scripts.js<\/em>\u00a0\u0441 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u043c\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438 \u0438\u00a0<em>tests.html<\/em>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c\u0441\u044f \u0442\u0435\u0441\u0442\u044b.<\/p>\n<p>\u0414\u043b\u044f JS \u0442\u0435\u0441\u0442\u043e\u0432 \u0443 \u043d\u0430\u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u00a0<a href=\"https:\/\/mochajs.org\/\">mocha<\/a>. \u041e\u043d \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043a\u0430\u043a \u0432\u00a0<a href=\"https:\/\/mochajs.org\/#command-line-usage\">\u043a\u043e\u043d\u0441\u043e\u043b\u0438<\/a>, \u0442\u0430\u043a \u0438 \u0432\u00a0<a href=\"https:\/\/mochajs.org\/#running-mocha-in-the-browser\">\u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435<\/a>. \u041c\u044b \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c\u0441\u044f \u043d\u0430 \u0432\u0442\u043e\u0440\u043e\u043c \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0435.<\/p>\n<h3>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/h3>\n<p>\u0414\u043b\u044f \u044d\u0442\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u044f \u0432\u0437\u044f\u043b \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438. \u0412\u043e\u0442 \u043e\u043d\u0438:<\/p>\n<pre><code class=\"javascript\">function getFileExtension(file){     return file.substr(file.lastIndexOf('.') + 1); }  function isExtensionValid(extension){     return extension !== 'exe' &amp;&amp; extension !== 'i'; }<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u0432\u044b\u0434\u0430\u0451\u0442 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430. \u0412\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0432\u0430\u043b\u0438\u0434\u043d\u043e \u043b\u0438 \u043e\u043d\u043e. \u041c\u044b \u043d\u0435 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u043c \u0444\u0430\u0439\u043b\u044b \u0441 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c \u00abi\u00bb \u043b\u0438\u0431\u043e \u00abexe\u00bb, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0430\u043a\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u0447\u0438\u0442\u0430\u0442\u044c\u0441\u044f \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c\u0438. \u041e\u0431\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0444\u043e\u0440\u043c\u044b \u0444\u0438\u0434\u0431\u0435\u043a\u0430.<\/p>\n<h3>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 tests.html<\/h3>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0444\u0430\u0439\u043b\u0430\u00a0<em>tests.html<\/em>\u00a0\u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"xml\">&lt;!DOCTYPE html> &lt;html lang=\"en\">     &lt;head>         &lt;meta charset=\"UTF-8\">         &lt;meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">         &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">          &lt;title>JavaScript tests&lt;\/title>          &lt;!-- Our scripts -->         &lt;script src=\"scripts.js\">&lt;\/script>          &lt;!-- Load Mocha and it's styles-->         &lt;script              src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mocha\/2.1.0\/mocha.js\">         &lt;\/script>         &lt;link rel=\"stylesheet\"             href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mocha\/2.1.0\/mocha.css\">          &lt;!-- Setup mocha-->         &lt;script>mocha.setup('bdd');&lt;\/script>          &lt;!-- Load chai and add assert -->         &lt;script             src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/chai\/2.0.0\/chai.js\">         &lt;\/script>         &lt;script>const assert = chai.assert;&lt;\/script>     &lt;\/head>      &lt;body>         &lt;script>             \/\/ Constants for testing              \/\/ For getFileExtension test             const filesAndExtensions = {                 'file.doc': 'doc',                 'file.pdf': 'pdf',                 'test.i': 'i',                 'test.exe': 'exe',             };              \/\/ For isExtensionValid test             const validExtensions = ['doc', 'docx', 'pdf', 'xls',];             const invalidExtensions = ['i', 'exe'];              \/\/ Tests              describe(\"getFileExtension\", function() {                 for (let [file, expected_extension] of                 Object.entries(filesAndExtensions)) {                     it(`${file} has ${expected_extension} extension`,                     function() {                             assert.equal(                             getFileExtension(file), expected_extension                         );                     });                 }             });              describe(\"isExtensionValid\", function() {                 describe(\"The extension is not .i or .exe\", function() {                     validExtensions.forEach(extension => {                         it(`${extension} is valid extension`, function() {                             assert.isTrue(isExtensionValid(extension));                         });                     });                 });                  describe(\"The extension is .i or .exe\", function() {                     invalidExtensions.forEach(extension => {                         it(`${extension} is invalid extension`, function() {                             assert.isFalse(isExtensionValid(extension));                         });                     });                 });             });          &lt;\/script>          &lt;!-- All tests results will be in this div -->         &lt;div id=\"mocha\">&lt;\/div>          &lt;script>mocha.run();&lt;\/script>     &lt;\/body> &lt;\/html><\/code><\/pre>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c, \u0447\u0442\u043e \u0442\u0443\u0442 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442.<\/p>\n<h4>\u0422\u0435\u0433 head<\/h4>\n<p>\u041d\u0430\u0447\u043d\u0451\u043c \u0441 \u0442\u0435\u0433\u0430\u00a0<em>head<\/em>. \u0412 \u043d\u0451\u043c \u043c\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f:<\/p>\n<ol>\n<li>\n<p>\u041f\u043e\u0434\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c mocha \u0438 \u0435\u0451 \u0441\u0442\u0438\u043b\u0438. \u0418\u043c\u0435\u043d\u043d\u043e \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c mocha, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044f, \u043a\u0430\u043a\u0443\u044e \u043c\u0435\u0442\u043e\u0434\u0438\u043a\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. BDD \u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u043b\u043d\u043e \u043e\u043f\u0438\u0441\u0430\u0442\u044c, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u0442\u0435\u0441\u0442. \u041e\u0431\u043e \u0432\u0441\u0435\u0445 \u043c\u0435\u0442\u043e\u0434\u0438\u043a\u0430\u0445 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0447\u0438\u0442\u0430\u0442\u044c\u00a0<a href=\"https:\/\/mochajs.org\/#interfaces\">\u0442\u0443\u0442<\/a>;<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u0434\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443\u00a0<a href=\"https:\/\/www.chaijs.com\/\">chai<\/a>. \u0412 \u043d\u0435\u0439 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0442\u0435\u0441\u0442\u043e\u0432 (<a href=\"https:\/\/www.chaijs.com\/guide\/styles\/#should\">should<\/a>,\u00a0<a href=\"https:\/\/www.chaijs.com\/guide\/styles\/#assert\">assert<\/a>\u00a0\u0438\u00a0<a href=\"https:\/\/www.chaijs.com\/guide\/styles\/#expect\">expect<\/a>). \u0425\u043e\u0442\u044f \u0434\u043b\u044f BDD \u0431\u043e\u043b\u044c\u0448\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0442\u00a0<em>should<\/em>\u00a0\u0438\u00a0<em>expect<\/em>, \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u043a\u043e\u043c\u044b\u0439 \u043d\u0430\u043c \u0438\u0437 Python \u043c\u0435\u0442\u043e\u0434\u00a0<em>assert<\/em>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0437\u0430\u043d\u043e\u0441\u0438\u043c \u0435\u0433\u043e \u0432 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0443.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"xml\">&lt;head>     &lt;meta charset=\"UTF-8\">     &lt;meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">     &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">      &lt;title>JavaScript tests&lt;\/title>      &lt;!-- (1) -->     &lt;!-- Our scripts -->     &lt;script src=\"scripts.js\">&lt;\/script>      &lt;!-- (2) -->     &lt;!-- Load Mocha and it's styles-->     &lt;script         src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mocha\/2.1.0\/mocha.js\">     &lt;\/script>     &lt;link rel=\"stylesheet\"         href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mocha\/2.1.0\/mocha.css\">      &lt;!-- (3) -->     &lt;!-- Setup mocha-->     &lt;script>mocha.setup('bdd');&lt;\/script>      &lt;!-- (4) -->     &lt;!-- Load chai and add assert -->     &lt;script         src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/chai\/2.0.0\/chai.js\">     &lt;\/script>     &lt;script>const assert = chai.assert;&lt;\/script> &lt;\/head><\/code><\/pre>\n<h4>\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432<\/h4>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a\u00a0<em>body<\/em>\u00a0\u0438 \u0435\u0433\u043e \u0442\u0435\u0433\u0443\u00a0<em>script<\/em>. \u041f\u0435\u0440\u0432\u043e\u0435, \u0447\u0442\u043e \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c, \u2014 \u044d\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<pre><code class=\"javascript\">\/\/ Constants for testing  \/\/ For getFileExtension test const filesAndExtensions = {     'file.doc': 'doc',     'file.pdf': 'pdf',     'test.i': 'i',     'test.exe': 'exe', };  \/\/ For isExtensionValid test const validExtensions = ['doc', 'docx', 'pdf', 'xls',]; const invalidExtensions = ['i', 'exe'];<\/code><\/pre>\n<h4>\u0422\u0435\u0441\u0442\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438 getFileExtension<\/h4>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442 \u0438\u0434\u0443\u0442 \u0442\u0435\u0441\u0442\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438\u00a0<em>getFileExtension<\/em>. \u0412\u043e\u0442 \u043a\u0430\u043a \u043e\u043d\u0438 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f:<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0431\u043b\u043e\u043a \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c\u00a0<em>getFileExtension<\/em>.\u0424\u0443\u043d\u043a\u0446\u0438\u044f\u00a0<em>describe<\/em>\u00a0\u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044f\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430, \u0432\u044b\u043d\u043e\u0441\u044f \u0438\u0445 \u0432 \u0440\u0430\u0437\u043d\u044b\u0435 \u0431\u043b\u043e\u043a\u0438. \u0422.\u0435. \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f\u00a0<em>getFileExtension<\/em>\u00a0\u0438 \u0434\u043b\u044f\u00a0<em>isExtensionValid<\/em>. \u0422\u043e\u0433\u0434\u0430 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u043e\u043d\u0438 \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u0440\u043e\u0437\u043d\u044c. \u041f\u0435\u0440\u0432\u044b\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u00a0<em>describe<\/em>\u00a0\u2014 \u044d\u0442\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0431\u043b\u043e\u043a\u0430. \u041e\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043b\u044e\u0431\u044b\u043c, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u0412\u0442\u043e\u0440\u043e\u0439 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442 \u2013 \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0449\u0430\u044f \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0412 \u0446\u0438\u043a\u043b\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043c\u0441\u044f \u043f\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c\u00a0<em>filesAndExtensions<\/em>\u00a0\u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0435\u0433\u043e \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435\u043c;<\/p>\n<\/li>\n<li>\n<p>\u0412 \u0444\u0443\u043d\u043a\u0446\u0438\u044e\u00a0<em>it<\/em>\u00a0(\u043e\u043d\u0430 \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u043e\u0434\u0438\u043d \u0442\u0435\u0441\u0442) \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u043c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u0430 \u0438 \u0441\u0430\u043c \u0442\u0435\u0441\u0442 \u0432 \u0432\u0438\u0434\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438;<\/p>\n<\/li>\n<li>\n<p>\u0423\u00a0<em>assert<\/em>-\u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u00a0<em>equal<\/em>. \u0415\u0433\u043e \u0441\u0443\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u044b \u0442\u0430\u043a\u0430\u044f \u0436\u0435 \u043a\u0430\u043a \u0434\u043b\u044f\u00a0<em>assertEqual<\/em>\u00a0\u0432 Python. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u044b \u0441\u0440\u0430\u0432\u043d\u0438\u0432\u0430\u0435\u043c \u0434\u0432\u0430 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f: \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e\u00a0<em>getFileExtension<\/em>\u00a0\u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0435.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"javascript\">describe(\"getFileExtension\", function() { \/\/ (1)     \/\/ (2)         for (let [file, expected_extension] of Object.entries(filesAndExtensions)) {         it(`${file} has ${expected_extension} extension`, function() { \/\/ (3)             assert.equal(getFileExtension(file), expected_extension); \/\/ (4)         });     } });<\/code><\/pre>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/83d\/e01\/869\/83de0186907239fb5613f76363e92982.jpg\" width=\"200\" height=\"111\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/83d\/e01\/869\/83de0186907239fb5613f76363e92982.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0441\u0435 4 \u0442\u0435\u0441\u0442\u0430 \u043f\u0440\u043e\u0448\u043b\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e.<\/p>\n<h4>\u0422\u0435\u0441\u0442\u044b \u0444\u0443\u043d\u043a\u0446\u0438\u0438 isExtensionValid<\/h4>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u0438\u00a0<em>isExtensionValid<\/em>. \u0412 \u043d\u0438\u0445 \u043c\u044b:<\/p>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c\u00a0<em>describe<\/em>\u00a0\u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c\u00a0<em>isExtensionValid<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437\u0434\u0435\u043b\u044f\u0435\u043c \u043d\u0430\u0448\u00a0<em>describe<\/em>\u00a0\u0435\u0449\u0451 \u043d\u0430 \u0434\u0432\u0430 \u0431\u043b\u043e\u043a\u0430, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0443 \u043d\u0430\u0441 \u0434\u0432\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0442\u0438\u043f\u0430 \u0442\u0435\u0441\u0442\u043e\u0432: \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0445 \u0438 \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u0445 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0439. \u0412 \u043f\u0435\u0440\u0432\u043e\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u00a0<em>isExtensionValid<\/em>\u00a0\u0434\u043e\u043b\u0436\u043d\u0430 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c\u00a0<em>true<\/em>, \u0432\u043e \u0432\u0442\u043e\u0440\u043e\u043c \u2013\u00a0<em>false<\/em>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0437\u0443\u043c\u043d\u043e \u0440\u0430\u0437\u0433\u0440\u0430\u043d\u0438\u0447\u0438\u0442\u044c \u044d\u0442\u0438 \u0442\u0435\u0441\u0442\u044b. \u0411\u043b\u0430\u0433\u043e\u00a0<em>describe<\/em>\u00a0\u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0431\u043b\u043e\u043a\u0438;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0445\u043e\u0434\u0438\u043c\u0441\u044f \u043f\u043e \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u043c \u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0434\u043b\u044f \u043d\u0438\u0445 \u0442\u0435\u0441\u0442, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0449\u0438\u0439 \u0447\u0442\u043e\u00a0<em>isExtensionValid<\/em>\u00a0\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u00a0<em>true<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0445\u043e\u0434\u0438\u043c\u0441\u044f \u043f\u043e \u043d\u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u044b\u043c \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f\u043c, \u0442\u0430\u043a \u0436\u0435 \u0441\u043e\u0437\u0434\u0430\u0451\u043c \u0434\u043b\u044f \u043d\u0438\u0445 \u0442\u0435\u0441\u0442, \u043d\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u043e\u043d \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0447\u0442\u043e\u00a0<em>isExtensionValid<\/em>\u00a0\u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u00a0<em>false.<\/em><\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"javascript\">describe(\"isExtensionValid\", function() { \/\/ (1)     describe(\"The extension is not .i or .exe\", function() { \/\/ (2)         \/\/ (3)         validExtensions.forEach(extension => {             it(`${extension} is valid extension`, function() {                 assert.isTrue(isExtensionValid(extension));             });         });     });      describe(\"The extension is .i or .exe\", function() { \/\/ (2)         \/\/ (4)         invalidExtensions.forEach(extension => {             it(`${extension} is invalid extension`, function() {                 assert.isFalse(isExtensionValid(extension));             });         });     }); });<\/code><\/pre>\n<p>\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u044d\u0442\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/3a8\/143\/c03\/3a8143c03c181712f5acad959338958b.jpg\" width=\"239\" height=\"194\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3a8\/143\/c03\/3a8143c03c181712f5acad959338958b.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<h4>\u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432<\/h4>\n<p>\u0412\u0441\u0435 \u0442\u0435\u0441\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u0432 \u0431\u043b\u043e\u043a\u0435\u00a0<em>div<\/em>\u00a0\u0441\u00a0<em>id<\/em>\u00a0<em>mocha<\/em>. \u0410 \u0434\u043b\u044f \u0438\u0445 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443\u00a0<em>run<\/em>.<\/p>\n<pre><code class=\"xml\">&lt;!-- All tests results will be in this div --> &lt;div id=\"mocha\">&lt;\/div>  &lt;script>mocha.run();&lt;\/script><\/code><\/pre>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u00a0<em>tests.html<\/em>\u00a0\u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435, \u0432\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0435 \u0442\u0430\u043a\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u0443:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/e7b\/6cb\/68e\/e7b6cb68eeff6f8800f130890f046fa6.jpg\" width=\"510\" height=\"383\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/e7b\/6cb\/68e\/e7b6cb68eeff6f8800f130890f046fa6.jpg\" data-blurred=\"true\"\/><figcaption><\/figcaption><\/figure>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u043d\u0430\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0441\u043f\u0440\u0430\u0432\u0430 \u0432\u0432\u0435\u0440\u0445\u0443 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u043d\u0435\u043b\u044c. \u0412 \u043d\u0435\u0439 \u043c\u043e\u0436\u043d\u043e \u0443\u0432\u0438\u0434\u0435\u0442\u044c:<\/p>\n<ol>\n<li>\n<p>\u0427\u0438\u0441\u043b\u043e \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0427\u0438\u0441\u043b\u043e \u0443\u043f\u0430\u0432\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0412\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0442\u0435\u0441\u0442\u043e\u0432;<\/p>\n<\/li>\n<li>\n<p>\u0421\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u0441\u0442\u043e\u0432 (\u0432 \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u0430\u0445) \u0443\u0436\u0435 \u0443\u0441\u043f\u0435\u043b\u0438 \u043f\u0440\u043e\u0433\u043d\u0430\u0442\u044c\u0441\u044f.<\/p>\n<\/li>\n<\/ol>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u0442\u0435\u0441\u0442 \u043c\u043e\u0436\u043d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043d\u043e\u043f\u043a\u0438, \u043d\u0430\u0445\u043e\u0434\u044f\u0449\u0435\u0439\u0441\u044f \u0441\u043f\u0440\u0430\u0432\u0430 \u043e\u0442 \u0435\u0433\u043e \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u043d\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0431\u043b\u043e\u043a\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043d\u0430\u00a0<em>getFileExtension<\/em>, \u043e\u0442\u043a\u0440\u043e\u0435\u0442\u0441\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u0442\u043e\u043b\u044c\u043a\u043e \u044d\u0442\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430. \u0422\u0430\u043a\u0436\u0435, \u0435\u0441\u043b\u0438 \u043d\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 \u0442\u0435\u0441\u0442, \u043c\u043e\u0436\u043d\u043e \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0435\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/61f\/d0b\/c05\/61fd0bc05f01a176eca7eef6e5c8215b.png\" width=\"495\" height=\"179\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/61f\/d0b\/c05\/61fd0bc05f01a176eca7eef6e5c8215b.png\"\/><figcaption><\/figcaption><\/figure>\n<p>\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432, \u0432\u044b \u0443\u0432\u0438\u0434\u0438\u0442\u0435, \u0447\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a.<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4f0\/303\/91b\/4f030391b3cb2fe5746c155e45d6fb27.png\" width=\"496\" height=\"381\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4f0\/303\/91b\/4f030391b3cb2fe5746c155e45d6fb27.png\"\/><figcaption><\/figcaption><\/figure>\n<h3>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0418\u0442\u0430\u043a, \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u044b \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043b\u0438 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f JavaScript \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0440\u0443\u0441\u0441\u043a\u043e\u0433\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445. \u0415\u0441\u043b\u0438 \u0432\u044b \u0435\u0449\u0451 \u043d\u0435 \u0447\u0438\u0442\u0430\u043b\u0438\u00a0<a href=\"https:\/\/habr.com\/ru\/company\/pvs-studio\/blog\/648717\/\">\u043f\u0435\u0440\u0432\u0443\u044e \u0447\u0430\u0441\u0442\u044c<\/a>, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c. \u0412 \u043d\u0435\u0439 \u043c\u043d\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0433\u043e \u0438 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0433\u043e. \u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0430\u0432\u0442\u043e\u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u044b\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b. \u0414\u043b\u044f \u0444\u0438\u0434\u0431\u0435\u043a\u0430 \u0438\u043b\u0438 \u043a\u0440\u0438\u0442\u0438\u043a\u0438 \u043f\u0438\u0448\u0438\u0442\u0435 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043b\u0438\u0431\u043e \u0432\u00a0<a href=\"https:\/\/www.instagram.com\/stepanov.programmer\/\">\u043c\u043e\u0439 \u0438\u043d\u0441\u0442\u0430\u0433\u0440\u0430\u043c<\/a>. \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0438 \u0434\u043e \u0432\u0441\u0442\u0440\u0435\u0447\u0438 \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u0441\u0442\u0430\u0442\u044c\u0435)<\/p>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"v-portal\" style=\"display:none;\"><\/div>\n<\/div>\n<p> <!----> <!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/company\/pvs-studio\/blog\/652951\/\"> https:\/\/habr.com\/ru\/company\/pvs-studio\/blog\/652951\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 <a href=\"https:\/\/habr.com\/ru\/company\/pvs-studio\/blog\/648717\/\">\u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a> \u043c\u044b \u043f\u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u043b\u0438\u0441\u044c \u0441 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0434\u043b\u044f Django \u0438 \u0441\u043e\u0437\u0434\u0430\u043b\u0438 \u043b\u0438\u0447\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f-\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0430. \u0421\u0430\u043c\u043e\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u044c \u0438\u0437\u0443\u0447\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441\u0430\u0439\u0442\u0430, \u043d\u0430\u043f\u0438\u0441\u0430\u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u0438 \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u0432 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f JavaScript.<\/p>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<h2>\u0422\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0442\u0435\u043a\u0441\u0442\u0430<\/h2>\n<p>\u041a\u043e\u0433\u0434\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u0438\u043d\u043e\u0441\u0442\u0440\u0430\u043d\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u0442\u0435\u043a\u0441\u0442 (\u0438\u043b\u0438 \u043d\u0430\u043e\u0431\u043e\u0440\u043e\u0442), \u044d\u0442\u043e, \u043c\u044f\u0433\u043a\u043e \u0433\u043e\u0432\u043e\u0440\u044f, \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u0438\u044f\u0442\u043d\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043b\u0443\u0447\u0448\u0435 \u0442\u0430\u043a\u0438\u0435 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438 \u0432\u043e\u0432\u0440\u0435\u043c\u044f \u0438\u0437\u0431\u0430\u0432\u0438\u0442\u044c\u0441\u044f \u043e\u0442 \u043d\u0438\u0445.<\/p>\n<p>\u041d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u043d\u0430 \u0434\u0432\u0430 \u0442\u0438\u043f\u0430:<\/p>\n<ol>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u043e\u0448\u0438\u0431\u043e\u043a \u0432\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u0430\u0445.<\/p>\n<\/li>\n<\/ol>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445<\/h3>\n<p>\u041f\u0435\u0440\u0435\u0439\u0434\u0451\u043c \u043a \u043f\u0435\u0440\u0432\u043e\u043c\u0443 \u0442\u0435\u0441\u0442\u0443. \u0415\u0433\u043e \u043f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">from requests import get from bs4 import BeautifulSoup from bs4.element import Tag  from django.test import TestCase  # \u0410 CODE_OF_FIRST_RUSSIAN_LETTER = 1040  # \u044f CODE_OF_LAST_RUSSIAN_LETTER = 1103  CODES_OF_RUSSIAN_SYMBOLS = list(     range(         CODE_OF_FIRST_RUSSIAN_LETTER,         CODE_OF_LAST_RUSSIAN_LETTER + 1     ) ) + [1025, 1105] # \u0401, \u0451  ENGLISH_PAGES = (     'https:\/\/pvs-studio.com\/ru\/',     'https:\/\/pvs-studio.com\/ru\/blog\/posts\/',     'https:\/\/pvs-studio.com\/ru\/for-clients\/',     'https:\/\/pvs-studio.com\/ru\/docs\/',     # ... )  def is_russian_symbol(symbol: str) -> bool:     \"\"\"True if symbol is Russian\"\"\"      return ord(symbol) in CODES_OF_RUSSIAN_SYMBOLS  def get_page_words(content: Tag) -> tuple:     \"\"\"Return page's words\"\"\"      page_text = content.get_text()     page_text_without_extra_spaces = \" \".join(page_text.split())     page_words = page_text_without_extra_spaces.split()      return tuple(page_words)  class CorrectTranslationTests(TestCase):     \"\"\"Test correct translation\"\"\"      def test_russian_symbols_presence(self):         \"\"\"Test English pages for presence of Russian symbols\"\"\"          for page in ENGLISH_PAGES:             page_content = BeautifulSoup(get(page).content, 'html.parser')             main_div_content = page_content.find(                 'div', {\"class\": 'b-content'}             )              if main_div_content:                 page_words = get_page_words(main_div_content)                  for word in page_words:                     for symbol in word:                         error_message = f'\\n\"{symbol}\" ({word}) on {page}\\n'                          with self.subTest(error_message):                             self.assertFalse(is_russian_symbol(symbol))<\/code><\/pre>\n<p>\u0420\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u0435\u0433\u043e \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u043e.<\/p>\n<h4>\u0418\u043c\u043f\u043e\u0440\u0442\u044b<\/h4>\n<p>\u0414\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043d\u0430 \u043d\u0435\u0451 GET-\u0437\u0430\u043f\u0440\u043e\u0441. \u0421 \u044d\u0442\u0438\u043c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434\u00a0<em>get<\/em>.<\/p>\n<pre><code class=\"python\">from requests import get<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f\u00a0<a href=\"https:\/\/www.crummy.com\/software\/BeautifulSoup\/bs4\/doc\/\"><em>Beautiful Soup<\/em><\/a>. \u0414\u043b\u044f type hinting \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c\u00a0<em>Tag<\/em>.<\/p>\n<pre><code class=\"python\">from bs4 import BeautifulSoup from bs4.element import Tag<\/code><\/pre>\n<p>\u0418 \u0442\u0430\u043a\u0436\u0435 \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u00a0<em>TestCase<\/em>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b.<\/p>\n<pre><code class=\"python\">from django.test import TestCase<\/code><\/pre>\n<h4>\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b<\/h4>\n<p>\u0423 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u0430 \u0435\u0441\u0442\u044c \u0435\u0433\u043e \u0447\u0438\u0441\u043b\u043e\u0432\u043e\u0439 \u043a\u043e\u0434. \u0412 Python \u044d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u0438\u00a0<em>ord<\/em>. \u041a\u043e\u0434\u044b \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0438\u043c\u0435\u044e\u0442 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u043e\u0442 1040 (\u0410) \u0434\u043e 1103 (\u044f). \u0422\u0430\u043a\u0436\u0435 \u043a \u043d\u0438\u043c \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f 1025 (\u0401) \u0438 1105 (\u0451). \u0418\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u0438 \u043a\u043e\u0434\u044b \u043c\u044b \u0437\u0430\u043d\u043e\u0441\u0438\u043c \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <em>CODES_OF_RUSSIAN_SYMBOLS<\/em>. \u0421 \u0435\u0451 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0431\u0443\u0434\u0435\u0442 \u043e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0442\u044c\u0441\u044f \u043f\u043e\u0438\u0441\u043a \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432.<\/p>\n<pre><code class=\"python\"># \u0410 CODE_OF_FIRST_RUSSIAN_LETTER = 1040  # \u044f CODE_OF_LAST_RUSSIAN_LETTER = 1103  CODES_OF_RUSSIAN_SYMBOLS = list(     range(         CODE_OF_FIRST_RUSSIAN_LETTER,         CODE_OF_LAST_RUSSIAN_LETTER + 1     ) ) + [1025, 1105] # \u0401, \u0451<\/code><\/pre>\n<p>\u0412\u00a0<em>ENGLISH_PAGES<\/em>\u00a0\u043c\u044b \u0437\u0430\u043d\u043e\u0441\u0438\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c:<\/p>\n<pre><code class=\"python\">ENGLISH_PAGES = (     'https:\/\/pvs-studio.com\/ru\/',     'https:\/\/pvs-studio.com\/ru\/blog\/posts\/',     'https:\/\/pvs-studio.com\/ru\/for-clients\/',     'https:\/\/pvs-studio.com\/ru\/docs\/',     # ... )<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f is_russian_symbol<\/h4>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u0441\u0438\u043c\u0432\u043e\u043b \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u00a0<em>is_russian_symbol<\/em>. \u041e\u043d\u0430 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043a\u043e\u0434 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0435\u0433\u043e \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439\u00a0<em>CODES_OF_RUSSIAN_SYMBOLS<\/em>.<\/p>\n<pre><code class=\"python\">def is_russian_symbol(symbol: str) -> bool:     \"\"\"True if symbol is Russian\"\"\"      return ord(symbol) in CODES_OF_RUSSIAN_SYMBOLS<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f get_page_words<\/h4>\n<p>\u041a\u043e\u043d\u0442\u0435\u043d\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043f\u043e\u043c\u0438\u043c\u043e \u0442\u0435\u043a\u0441\u0442\u0430 \u0435\u0449\u0451 \u0438 \u043d\u0435\u043d\u0443\u0436\u043d\u044b\u0435 \u043d\u0430\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0435\u0433\u0438. \u0427\u0442\u043e\u0431\u044b \u0443\u0431\u0440\u0430\u0442\u044c \u0438\u0445, \u0443\u00a0<em>BeautifulSoup<\/em>\u00a0\u0435\u0441\u0442\u044c \u043a\u0440\u0443\u0442\u043e\u0439 \u043c\u0435\u0442\u043e\u0434\u00a0<em>get_text<\/em>. \u041e\u043d \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u043a\u0441\u0442 \u0438\u0437 HTML-\u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 (1). \u041c\u044b \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043b\u0438\u0448\u044c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434, \u043d\u043e \u0442\u0435\u043a\u0441\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u043d \u0432\u044b\u0434\u0430\u0451\u0442, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u0447\u0438\u0441\u043b\u043e \u043f\u043e\u0434\u0440\u044f\u0434 \u0438\u0434\u0443\u0449\u0438\u0445 \u043f\u0440\u043e\u0431\u0435\u043b\u043e\u0432. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438\u0445 \u043c\u044b \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u043d\u0430 \u043e\u0434\u0438\u043d\u043e\u0447\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e\u00a0<em>split<\/em>\u00a0\u0438\u00a0<em>join<\/em>\u00a0(2). \u041f\u043e\u043b\u0443\u0447\u0438\u0432 \u0441\u0442\u0440\u043e\u043a\u0443, \u043c\u044b \u0440\u0430\u0437\u0431\u0438\u0432\u0430\u0435\u043c \u0435\u0451 \u043d\u0430 \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u043b\u043e\u0432 (3), \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0438\u0442\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u043d\u0435\u043c\u0443.<\/p>\n<pre><code class=\"python\">def get_page_words(content: Tag) -> tuple:     \"\"\"Return page's words\"\"\"      page_text = content.get_text() # (1)     page_text_without_extra_spaces = \" \".join(page_text.split()) # (2)     page_words = page_text_without_extra_spaces.split() # (3)      return tuple(page_words)<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0442\u0435\u0441\u0442\u0430<\/h4>\n<p>\u0422\u0435\u0441\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0442\u0430\u043a:<\/p>\n<ol>\n<li>\n<p>\u041e\u0431\u0445\u043e\u0434\u0438\u0442 \u0432\u0441\u0435 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b;<\/p>\n<\/li>\n<li>\n<p>\u0418\u0437 \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u0432\u0435\u0441\u044c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 (\u043f\u043e\u0445\u043e\u0436\u0438\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0431\u044b\u043b \u0432\u00a0<a href=\"https:\/\/habr.com\/ru\/company\/pvs-studio\/blog\/648717\/\">\u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435<\/a>);<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430 \u043d\u0430\u0448\u0435\u043c \u0441\u0430\u0439\u0442\u0435 \u0431<strong>\u043e<\/strong>\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0442\u0435\u043a\u0441\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0438\u0434\u0438\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0432\u00a0<em>div<\/em>\u00a0\u0441 \u043a\u043b\u0430\u0441\u0441\u043e\u043c\u00a0<em>b-content<\/em>. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0442\u0435\u0441\u0442 \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043a\u043e\u043d\u0442\u0435\u043d\u0442 \u0438\u0437 \u043d\u0435\u0433\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430\u00a0<em>find<\/em>. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0431\u043b\u043e\u043a\u0438\u00a0<em>div<\/em>\u00a0\u043c\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e;<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0438\u0437 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0432\u0441\u0435 \u0441\u043b\u043e\u0432\u0430;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0441\u043b\u043e\u0432\u0443 \u0438 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0441\u0438\u043c\u0432\u043e\u043b\u0443;<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0447\u0442\u043e \u0441\u0438\u043c\u0432\u043e\u043b \u043d\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0440\u0443\u0441\u0441\u043a\u0438\u043c.<\/p>\n<\/li>\n<\/ol>\n<pre><code class=\"python\">class CorrectTranslationTests(TestCase):     \"\"\"Test correct translation\"\"\"      def test_russian_symbols_presence(self):         \"\"\"Test English pages for presence of Russian symbols\"\"\"          for page in ENGLISH_PAGES: # (1)             # (2)             page_content = BeautifulSoup(get(page).content, 'html.parser')             main_div_content = page_content.find(                 'div', {\"class\": 'b-content'}             ) # (3)              if main_div_content:                 page_words = get_page_words(main_div_content) # (4)                  for word in page_words: # (5)                     for symbol in word: # (5)                         error_message = f'\\n\"{symbol}\" ({word}) on {page}\\n'                          with self.subTest(error_message):                             # (6)                             self.assertFalse(is_russian_symbol(symbol))<\/code><\/pre>\n<h4>\u0417\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u0430<\/h4>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0432 \u0441\u0435\u0439\u0447\u0430\u0441 \u0442\u0435\u0441\u0442, \u043c\u044b \u043d\u0435 \u0443\u0432\u0438\u0434\u0438\u043c \u043e\u0448\u0438\u0431\u043e\u043a. \u0427\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0435\u0433\u043e \u0434\u043b\u044f\u00a0<a href=\"https:\/\/pvs-studio.com\/ru\/\">\u0440\u0443\u0441\u0441\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b<\/a>.<\/p>\n<figure class=\"\"><figcaption><\/figcaption><\/figure>\n<h3>\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u044f \u043e\u0448\u0438\u0431\u043e\u043a \u0432 django.po \u0444\u0430\u0439\u043b\u0430\u0445<\/h3>\n<p>\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0443\u0432\u0438\u0434\u0435\u0432 \u044d\u0442\u043e\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a, \u0432\u044b \u0437\u0430\u0434\u0430\u043b\u0438\u0441\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u043e\u043c: \u00ab\u0417\u0430\u0447\u0435\u043c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b\u00a0<em>django.po<\/em>, \u0435\u0441\u043b\u0438 \u043c\u044b \u0443\u0436\u0435 \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0440\u0443\u0441\u0441\u043a\u0438\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u043d\u0430 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445?\u00bb. \u041d\u0430 \u044d\u0442\u043e \u0435\u0441\u0442\u044c \u043c\u0438\u043d\u0438\u043c\u0443\u043c 2 \u043f\u0440\u0438\u0447\u0438\u043d\u044b:<\/p>\n<ol>\n<li>\n<p>\u0422\u0435\u0441\u0442\u00a0<em>django.po<\/em>\u00a0\u043f\u043e\u043c\u043e\u0433\u0430\u0435\u0442 \u043e\u0442\u043b\u043e\u0432\u0438\u0442\u044c \u0447\u0430\u0441\u0442\u044c \u043e\u0448\u0438\u0431\u043e\u043a \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0431\u044b\u0441\u0442\u0440\u043e. \u0410 \u0437\u043d\u0430\u0447\u0438\u0442, \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u0442\u044c \u043f\u043e\u0441\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043e\u043c\u043c\u0438\u0442\u0430;<\/p>\n<\/li>\n<li>\n<p>\u0415\u0441\u043b\u0438 \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u0444\u0440\u0430\u0437\u044b \u2013 \u043f\u0443\u0441\u0442\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 (\u0442. \u0435. msgstr \u0445\u0440\u0430\u043d\u0438\u0442 \u043f\u0443\u0441\u0442\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443), \u0442\u0435\u0441\u0442 \u0444\u0430\u0439\u043b\u0430\u00a0<em>django.po<\/em>\u00a0\u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u044d\u0442\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c, \u0432 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0442\u0435\u0441\u0442\u0430, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u043e\u0433\u043e \u0440\u0430\u043d\u0435\u0435.<\/p>\n<\/li>\n<\/ol>\n<p>\u0412\u0441\u0435\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c 3 \u0443\u0441\u043b\u043e\u0432\u0438\u044f. \u0410 \u0438\u043c\u0435\u043d\u043d\u043e \u0442\u043e, \u0447\u0442\u043e \u0432 \u0444\u0430\u0439\u043b\u0435\u00a0<em>django.po<\/em>\u00a0\u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c:<\/p>\n<ol>\n<li>\n<p>\u0421\u0442\u0440\u043e\u043a\u0438 \u0441 \u043f\u0443\u0441\u0442\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c\u00a0<em>msgstr<\/em>;<\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0440\u043e\u043a\u0438, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0439\u0441\u044f \u0441 \u00abmsgid\u00bb;<\/p>\n<\/li>\n<li>\n<p>\u0421\u0442\u0440\u043e\u043a\u0438, \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0439\u0441\u044f \u0441 \u00ab#, fuzzy\u00bb.<\/p>\n<\/li>\n<\/ol>\n<p>\u041d\u0435\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0445\u043e\u0442\u044f \u0431\u044b \u043e\u0434\u043d\u043e\u0433\u043e \u0438\u0437 \u044d\u0442\u0438\u0445 \u043f\u0443\u043d\u043a\u0442\u043e\u0432 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0446\u0438\u044f \u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0441 \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438.<\/p>\n<p>\u041f\u043e\u043b\u043d\u044b\u0439 \u0442\u0435\u0441\u0442\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u043e\u0432 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">from django.test import TestCase  MY_PROJECT_LOCALE_PATH = 'my_project\/locale\/'  DJANGO_PO_PATH = 'LC_MESSAGES\/django.po'  RU_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'ru\/' + DJANGO_PO_PATH  EN_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'en\/' + DJANGO_PO_PATH  def is_msgstr_empty(line_with_msgstr: str, next_line: str) -> bool:     \"\"\"True if msgstr is empty\"\"\"      return 'msgstr \"\"' in line_with_msgstr and next_line == '\\n'  def get_msgstr_errors(file: list) -> tuple:     \"\"\"Return numbers of lines where msgstr is empty\"\"\"      errors = []      for number in range(len(file) - 1):         line = file[number]         next_line = file[number + 1]          if is_msgstr_empty(line, next_line):             line_number = number + 1              errors.append(line_number)      return tuple(errors)  def get_fuzzy_and_msgid_errors(file: list) -> tuple:     \"\"\"Return numbers of lines that starts with '#, fuzzy' or #~ msgid\"\"\"      errors = []      for line_number, line in enumerate(file, 1):         if line.startswith('#, fuzzy') or line.startswith('#~ msgid'):             errors.append(line_number)      return tuple(errors)  def get_locale_files_errors() -> dict:     \"\"\"Return errors for ru and en django.po files\"\"\"      with open(RU_DJANGO_PO_PATH, 'r') as file:         ru_file = list(file).copy()      with open(EN_DJANGO_PO_PATH, 'r') as file:         en_file = list(file).copy()      errors = {         'ru': get_fuzzy_and_msgid_errors(ru_file) + get_msgstr_errors(ru_file),         'en': get_fuzzy_and_msgid_errors(en_file) + get_msgstr_errors(en_file),     }      return errors  class LocaleTests(TestCase):     \"\"\"Tests for locale files\"\"\"      def test_locale_files(self):         \"\"\"Test en and ru django.po files\"\"\"          for language, errors in get_locale_files_errors().items():             with self.subTest(f'Errors for {language}: {sorted(errors)}'):                 self.assertEqual(len(errors), 0)<\/code><\/pre>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u0447\u0442\u043e \u0432 \u043d\u0451\u043c \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442.<\/p>\n<h4>\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b<\/h4>\n<p>\u0412 \u043d\u0438\u0445 \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0440\u0443\u0441\u0441\u043a\u0438\u0439 \u0438 \u0430\u043d\u0433\u043b\u0438\u0439\u0441\u043a\u0438\u0439 \u043f\u0443\u0442\u0438 \u043a\u00a0<em>django.po<\/em>\u00a0\u0444\u0430\u0439\u043b\u0430\u043c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0435\u0440\u0432\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c:\u00a0<em>pvs\/locale\/ru\/LC_MESSAGES\/django.po<\/em>.<\/p>\n<pre><code class=\"python\">MY_PROJECT_LOCALE_PATH = 'my_project\/locale\/'  DJANGO_PO_PATH = 'LC_MESSAGES\/django.po'  RU_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'ru\/' + DJANGO_PO_PATH  EN_DJANGO_PO_PATH = MY_PROJECT_LOCALE_PATH + 'en\/' + DJANGO_PO_PATH<\/code><\/pre>\n<h4>\u0424\u0443\u043d\u043a\u0446\u0438\u044f is_msgstr_empty<\/h4>\n<p>\u041f\u0435\u0440\u0432\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442, \u0435\u0441\u0442\u044c \u043b\u0438 \u0432 \u0441\u0442\u0440\u043e\u043a\u0435\u00a0<em>msgstr<\/em>\u00a0\u0441 \u043f\u0443\u0441\u0442\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c.<\/p>\n<pre><code class=\"python\">def is_msgstr_empty(line_with_msgstr: str, next_line: str) -> bool:     \"\"\"True if msgstr is empty\"\"\"      return 'msgstr \"\"' in line_with_msgstr and next_line == '\\n'<\/code><\/pre>\n<h5>\u0418 \u0442\u0443\u0442 \u0432\u0440\u043e\u0434\u0435 \u0431\u044b \u0432\u0441\u0451 \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u043d\u043e \u0434\u043b\u044f \u0447\u0435\u0433\u043e \u0447\u0430\u0441\u0442\u044c \u00aband next_line == &#8216;\\n&#8217;\u00bb? \u0412\u0441\u0451 \u043f\u0440\u043e\u0441\u0442\u043e. \u0415\u0441\u043b\u0438 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435\u00a0msgstr\u00a0\u0431\u0443\u0434\u0435\u0442 \u043e\u0447\u0435\u043d\u044c \u0434\u043b\u0438\u043d\u043d\u044b\u043c, Django \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0451\u0442 \u0435\u0433\u043e \u043d\u0430 \u0434\u0440\u0443\u0433\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443. \u0412 <\/h><\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-329992","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/329992","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=329992"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/329992\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=329992"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=329992"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=329992"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}