learning-website-django1-7 Exercise 1: The Languages App and Its Markup ==================================================================== Add a Django app for what only the languages site needs: which language is which, how the courses are grouped, and a pass over each page that marks the language of every line being taught. Django 6.1.2. Register the app in INSTALLED_APPS ("apps.site_languages") and create apps/site_languages/__init__.py (empty). Save as apps/site_languages/config.py: """What the languages site knows about each language: its folder, name, language code and colours.""" # "accent" is the language's own colour (borders, backgrounds). "text" is the colour to use for text on the # dark surface: for Japanese the original red is only 3.86 against it (WCAG AA needs 4.5), so text uses a # lighter red of the same hue. LANGUAGES = [ {"key": "france", "name": "French", "code": "fr", "accent": "#4f8ef7", "text": "#4f8ef7", "lessons_folder": "france/french-lessons"}, {"key": "germany", "name": "German", "code": "de", "accent": "#e0a82e", "text": "#e0a82e", "lessons_folder": None}, {"key": "hungary", "name": "Hungarian", "code": "hu", "accent": "#5aa469", "text": "#5aa469", "lessons_folder": "hungary/hungarian-lessons"}, {"key": "japan", "name": "Japanese", "code": "ja", "accent": "#d64550", "text": "#e8707a", "lessons_folder": "japan/japanese-language"}, ] # the class a lesson uses for a line in the language being taught -> the HTML lang code for that text CLASS_TO_LANG = {"hu": "hu", "de": "de", "fr": "fr", "jp": "ja"} Save as apps/site_languages/markup.py: """Mark the text in the language being taught with a lang attribute. Lesson fragments wrap each line in the language being taught in , and so on. Adding lang="hu" / lang="ja" to those elements helps in three ways: a screen reader pronounces the text correctly, the browser picks the right Japanese glyph forms (the same kanji can be drawn differently for Chinese and Japanese), and spell checking and hyphenation use the right language. It is applied once, by the importer, and is safe to run twice.""" import re from .config import CLASS_TO_LANG TAG = re.compile(r"<([a-zA-Z][a-zA-Z0-9]*)(\s[^<>]*?)?>") CLASS = re.compile(r'\sclass\s*=\s*"([^"]*)"') def add_language_attributes(fragment): def fix(match): attrs = match.group(2) or "" if "lang=" in attrs: # already marked: leave it alone return match.group(0) found = CLASS.search(attrs) if not found: return match.group(0) for token in found.group(1).split(): # whole class names only: "hu", not "hu-lesson" if token in CLASS_TO_LANG: return f'<{match.group(1)} lang="{CLASS_TO_LANG[token]}"{attrs}>' return match.group(0) return TAG.sub(fix, fragment) Save as apps/site_languages/apps.py: from django.apps import AppConfig class SiteLanguagesConfig(AppConfig): name = "apps.site_languages" verbose_name = "The languages site" def ready(self): # the importer applies these to every page of the languages site (Chapter 4's pipeline, extended) from apps.content.fragments import register_transform from .markup import add_language_attributes register_transform("languages", add_language_attributes) A hook for site-specific changes to a page body. In apps/content/fragments.py the end of the file becomes: SITE_TRANSFORMS = {} def register_transform(site, function): SITE_TRANSFORMS.setdefault(site, []) if function not in SITE_TRANSFORMS[site]: # registering twice must not apply it twice SITE_TRANSFORMS[site].append(function) def prepare_fragment(raw, path, site=None): course_url_path = "/" + path.rsplit("/", 1)[0] if "/" in path else "" fragment = rewrite_solution_links(extract_fragment(raw), course_url_path) for transform in SITE_TRANSFORMS.get(site, []): fragment = transform(fragment) return fragment and the importer passes the site (apps/content/importer.py): fragment = prepare_fragment(raw, rel, parsed.site) How the real pages mark the language being taught ------------------------------------------------- Before writing the markup pass, count the classes in the real pages (561 pages on the languages site). Each line in the language being taught is wrapped in a span or div with a class named after the language: class "de" 1,285 times on 48 pages; "hu" 1,274 on 63; "jp" 687 on 35; "fr" 363 on 15 (and "en" for the English line, 3,570 times). The lesson wrappers are named .hu-lesson, .de-lesson, .jp-lesson (95 pages) and .fr-lesson, plus four more for the Japanese culture courses (jfilmtv1-lesson, jlit1-lesson, jmanga1-lesson and jmusic1-lesson), which have no colour of their own in tokens.css. Why a lang attribute matters ---------------------------- - A screen reader switches to the right pronunciation for lang="hu" text. - The same kanji are drawn with different glyph shapes for Chinese and Japanese. lang="ja" tells the browser to use the Japanese forms. (HTML's code for Japanese is ja, not jp, which is why the class is mapped.) - Spell checking and hyphenation use the right language. The markup pass looks at whole class names only ("hu", not "hu-lesson"), skips a tag that already has lang=, handles attributes spread over several lines, and is safe to run twice. The tests cover each of those, plus text that merely mentions a class (an escaped example, or a CSS rule). WHY THIS WORKS AS AN ANSWER --------------------------- The shared apps stay free of language code: the languages app plugs a function into the importer, and a different site could do the same. The counts from the real pages decided what the markup pass had to handle before any code was written.