Django localization works when you translate the intended user-facing messages and then deliver compiled translations that the application actually selects for each visitor. The quickest dependable workflow is to mark the messages in Python and templates, extract them with makemessages, edit the correct PO catalog without breaking identifiers or placeholders, compile it to MO with compilemessages, and inspect a real page in the target language. If a translation exists only as text in a PO file but is not loaded by the app, the reader has not received it.
If you’re searching for how to translate a Django app, how to fix Django translations not working, or how django-admin makemessages and compilemessages work, the first diagnostic is to trace one message through the system. An untranslated button might be an unmarked string, a missing msgid, a stale compiled file, a wrong locale directory, a lazy translation evaluated too early, or a page that chooses a different language. Treat these as separate possibilities; each has a different owner and repair.
This guide focuses on Django’s gettext-based translation pipeline rather than generic machine translation. It covers marking source strings, PO and MO file roles, language-directory routing, locale selection, contextual msgctxt, plural msgid_plural entries, named Python-format placeholders, template translation tags, lazy gettext, JavaScript domains, fuzzy and obsolete entries, UTF-8 encoding, safety of HTML output, accessibility, testing and release-quality evidence.
Translation series navigation: How People Translate Quickly index. Compare official Django gettext documentation with related guides: Angular XLIFF translation QA, Rails I18n YAML translation QA, Laravel PHP and JSON translation QA.
Identify which text the Django application actually translates
Start with a single element on a live page. Find the Python view, model, form, template or JavaScript code that produces its words. A literal Python string is not automatically extractable simply because it is displayed to a browser. Django uses translation functions such as gettext, plus template tags, to identify text that belongs in a catalog. Some strings in third-party packages may already have their own translations, while content fetched from a database may follow a separate content-management process.
Record the original message, its location, the active language and the visible result. Then check whether an extracted PO entry corresponds to that source message. If it is absent, revise source marking or the extraction configuration before asking linguists to translate it. If the entry exists and is translated, inspect compilation and lookup next.
The practical advantage of tracing one complete message is that it gives the team a reproducible example. Large localization projects become manageable when every failure can be assigned to extraction, translation, compilation, locale selection or rendering rather than described vaguely as Spanish not working.
Understand the difference between gettext and gettext_lazy
Django’s gettext function resolves a message at the point it is called using the active language context. The lazy variant stores a deferred translation object whose text is resolved when it is eventually used as a string. That distinction matters for model field labels, form classes and module-level declarations that may be evaluated before a visitor’s language is known.
Imagine defining a form’s field label at import time. If immediate gettext runs while English is active, the resolved string can be captured too early for a later Arabic or French request. Marking it through gettext_lazy avoids committing to the language until the form is actually rendered, assuming the surrounding code handles the lazy object correctly.
Do not replace every gettext call with gettext_lazy reflexively. Some operations genuinely need an immediate string, especially when handing values to external libraries or APIs. Identify the evaluation moment and use the appropriate translation function. A translation that resolves at the wrong time is an application lifecycle problem, not a problem with a language specialist’s wording.
Mark Python strings in a way extraction can find
Django’s extraction workflow relies on recognizable source patterns. A common approach imports gettext with an underscore alias and wraps a literal message for translation. The extraction tool then identifies the source string and creates a PO entry. If the application interpolates values before passing the string into gettext, the extractor may not recognize the intended stable source message.
Avoid writing a translation call around a Python f-string that has already inserted a runtime variable. Instead mark a fixed source message containing a named placeholder, then substitute the value after translation. This lets the translation catalog contain a repeatable identifier and gives the target language freedom to rearrange the variables.
Review computed messages as well as straightforward labels. Dynamically concatenated fragments, runtime-built translation keys and messages hidden inside helper abstractions may not be discovered by the default extraction. Solve that problem at the code architecture rather than creating a manually edited PO entry that the next extraction pass may discard.
Mark templates, attributes and human-facing controls
Django templates can use translation tags for single strings and block-based translation where a phrase spans text or includes variables. This helps keep whole messages together rather than assembling them from disjointed English fragments. A heading, a button label and the validation message below it may require separate but coordinated translations.
Audit every text-bearing part of a meaningful user journey. This includes accessible names, input hints, tooltip text, error states, conditional messages and labels on destructive actions. A user who relies on an aria-label cannot benefit from a translated visible caption if the assistive label remains in the default language.
The extraction review should check both ordinary template content and attribute values. Context matters: the English word Open on a button may be a verb, while Open in a status column may be an adjective. Provide developer notes or gettext context where needed instead of treating identical source text as identical meaning.
Use makemessages to build a current source catalog
The django-admin makemessages command scans configured source files and updates message catalogs for selected locales. It relies on the GNU gettext utilities installed in the environment. A successful extraction should represent the application’s current marked text, not a copy of strings from a retired release.
Run extraction from the correct project root with the intended settings and file locations. Record the source commit and command. If the project excludes certain generated files or includes additional directories, those options need to be part of the documented workflow. A translator cannot compensate for a source file never scanned.
Inspect the catalog diff after extraction. New entries need translations, modified source messages need meaning review, and obsolete entries should be classified rather than silently carried forward. A small unexplained change set is a cue to confirm whether extraction covered the expected modules.
PO catalogs preserve message identity and context
A PO file records message identifiers through msgid and target-language equivalents through msgstr. In pluralized entries it can also include msgid_plural and multiple indexed msgstr forms. It may contain context, comments, source references and status flags. These are not optional decorative elements to strip from the file merely because they are not shown to readers.
When translating a normal entry, edit the intended target string while preserving the source identifier and any required context. Changing msgid to an equivalent English phrase inside the target catalog is not the same as translating; it can detach the entry from the source lookup. If developers change the original source, let the extraction and merge workflow update identifiers systematically.
Use an editor that understands PO syntax and protects formatting. A generic machine translator applied to the complete file may translate comments, source strings and metadata while leaving the actual runtime target incomplete.
A PO file is not the same thing as a compiled MO file
PO catalogs are human-editable source files. Gettext-based runtimes commonly load compiled binary MO files. Django’s compilemessages command processes the catalogs into these runtime-oriented artifacts. As a result, editing a PO file without compiling and packaging the corresponding MO can leave the displayed website unchanged.
Treat PO approval and MO deployment as separate steps. A language reviewer can correctly sign off the wording while an engineering build fails to include updated compiled files. Verify that the final deployed environment contains the expected artifacts and that a known target message resolves correctly.
Do not guess based on modified timestamps alone. Request a distinctive translated message from the running application. If the old wording appears, inspect compilation, path and application restarts or caching where relevant. The real test is what the locale-aware runtime returns.
Locale directories and LOCALE_PATHS must agree
Django locates translations through configured locale paths and application-specific locale directories. Catalogs are commonly organized beneath a locale identifier and LC_MESSAGES, with names such as django.po and django.mo for the principal Python and template text domain. JavaScript may use a separate domain. A file placed under an unsupported directory name may look fine in version control while remaining invisible to the app.
Map each supported language to its expected resource path. Inspect the project’s LOCALE_PATHS and installed apps, and distinguish a project-wide locale directory from an app-local locale directory. Avoid inventing directory conventions from a human-readable language name.
Test a regional locale with a message unique to that locale. If users expecting Portuguese for Brazil see the default language, investigate the selected language code and catalog path before changing the Portuguese sentence.
LANGUAGE_CODE and LANGUAGES serve different purposes
Django has a default language setting and supports configuring the languages available to users. Other components, including LocaleMiddleware and language-selection controls, help determine which language is active for a request. Setting a default code alone does not guarantee that a specific user’s desired language will be selected on every page.
Trace the actual selection policy. Does the site use a URL prefix, a session choice, a browser language preference or another application rule? How are unsupported languages handled? Record the effective language at the point the template renders. A static file may be correct yet the site may be delivering it to the wrong audience.
Test a complete navigation sequence after a user switches language. Include a deep link, a form submission, an error response and a refreshed page. Inconsistency across those steps is typically a routing or state issue, not a translation of an individual message.
Use translation context to disambiguate short words
The word May can name a month or express permission. The word Order can mean a purchase or an instruction to arrange objects. Translating both senses using one target equivalent can produce incorrect labels despite clean grammar. Django provides contextual translation functions such as pgettext, and PO catalogs record context as msgctxt.
Ask developers to mark ambiguous entries with meaningful context rather than forcing translators to infer purpose from a bare source word. A useful context says month name, menu command, account state or help text. This can be more valuable than a long comment with no indication of what the interface does.
Context is part of message identity. A target may legitimately translate the same English word differently under two msgctxt entries. A CAT tool that merges those entries solely because their msgid values match is discarding necessary information.
Prefer named placeholders when word order can change
Python strings commonly support named percent-style placeholders such as %(user_name)s or %(count)d. Django’s translation guidance recommends named interpolation when more than one value occurs because translators need to rearrange parts of the sentence without swapping the underlying data. The placeholder identifiers are functional and should remain stable unless the code changes deliberately.
For example, a notification may mention a sender and a date. The target language might put the date first, then the sender. Named placeholders allow that reordering while preserving which data belongs to each grammatical role. Positional formats can create unnecessary constraints and accidental identity swaps.
Supply real sample values and a description of what each token means. Automated QA should compare required placeholder names and compatible format types, while linguistic QA approves their placement in the sentence.
Format types are important, not just percent signs
In a Python percent-style placeholder, the format marker can indicate the type of value expected. Changing %(count)d to %(count)s may affect how the runtime formats a value, and deleting the marker can cause an error or show literal syntax. A translator who sees a percent character should not assume it is decorative punctuation.
Compare source and target format specifications with a format-aware validator. Do not merely count the number of percent characters because a literal percentage sign and a variable marker have different roles. A source message about 50% progress may contain both a displayed percent symbol and an inserted numeric value.
Run actual formatting tests with a small, large and unusual input. A string can pass PO syntax checks while displaying the wrong number or placing a filename where a quantity belongs. Rendered-output tests catch problems that text-only validation misses.
The source message must remain stable until values are inserted
One common mistake is translating a Python f-string after it has already been evaluated. By that point a value such as a customer name has replaced the original placeholder, so the source text can vary for every user and is unsuitable as a stable catalog identifier. The extraction tool generally expects a fixed literal in the source.
Change the application code so gettext receives a stable template first, then interpolate values into the translated result. This lets the target language choose natural word order and allows a single catalog entry to serve many user names, dates or amounts.
A translator should not repair this with dozens of target entries containing different sample names. The root problem is the original message design. Fixing the code path improves translation quality and reduces catalog size for every language.
Pluralization needs ngettext, not an English-only if statement
Django supplies ngettext for quantity-dependent translations. The source code provides singular and plural source forms and a count, while the target catalog and its plural rules can support the distinctions that the language requires. A conditional expression that chooses singular only when count equals one can be wrong for languages with different number categories.
Write the message as a coherent statement about a quantity. A source might say one result or several results, but the target language may need different forms for zero, two, few or many according to its rules. The translator should have the complete context and not be restricted to choosing between two English endings.
Test multiple values through the actual gettext runtime. A language may require different noun cases or verb agreement across branches. A syntactically valid PO entry is not enough if the displayed plural form is wrong for ordinary counts.
PO plural entries include language-specific indexed forms
Gettext plural catalogs can have multiple msgstr indexed values whose count and selection depend on the language’s plural rules. The metadata header and the translation framework determine how these indices correspond to quantities. A translator should not simply copy English’s two target values into every locale.
Check the target language’s catalog configuration and use a PO-aware translation tool that presents the required forms clearly. A language specialist should write natural text for each form based on the context. The same English singular/plural pair can yield different grammatical structures in Arabic, Russian, Polish or Japanese.
After editing, compile the catalog and run a selection matrix at zero, one, two, three, five, eleven and other relevant values. Where the output is wrong, inspect the plural rules and target forms before changing unrelated application logic.
Avoid inconsistent placeholders in plural alternatives
An easy defect occurs when the source singular uses a named placeholder for a noun while the plural alternative uses a differently named placeholder. The compiled catalog may reject certain inconsistent format signatures or a rendered branch can fail because the caller did not pass the expected key. Django documentation gives examples of placeholder consistency problems across plural forms.
Prefer the same stable placeholder names for values that represent the same role in every branch. Keep the count separate from the noun’s grammatical form. This lets translators change natural wording without needing the application to choose different dictionaries of input values for singular and plural cases.
Build a small test of the exact ngettext call and its interpolation data. A message that works when count is one may fail at three if only the singular branch was tested. Both branches and all target-language forms need to pass.
Understand lazy plural translation in forms and models
Some validation messages are defined before their numeric count is known. Django provides lazy plural translation patterns that can defer selection until interpolation or rendering. That is useful in class declarations and form configurations, where immediate language or count selection would happen too early.
The translator should preserve the grammatical forms and placeholder contract without knowing the runtime count in advance. Developers must supply the appropriate count and format data when the message is used. A lazy object that is inadvertently converted to a string during application startup may capture the wrong language, defeating its purpose.
Test form errors under different active languages and quantities. This is more reliable than reviewing the class declaration alone. The correct code can be subtle, but the user-facing expectation is straightforward: the form describes the actual problem in the language selected for that request.
Do not send lazy translation objects blindly to external systems
A lazy gettext object is not always equivalent to a normal Python string for arbitrary external APIs. A background service, HTTP client or serializer may expect a concrete string. Resolving the lazy value at an unintended moment can also select the wrong language if the context has changed since the object was created.
For a notification or API payload, decide when the language should be selected and convert the message to a concrete string within that explicit locale context. Avoid passing a lazy proxy into an external service and hoping it resolves under the correct recipient language.
Test queued jobs and emails separately from web page rendering. A site can localize every template correctly while sending default-language notifications because a worker does not have the same language context as the browser request.
Separate translation of Python code from JavaScript code
Django can use a separate JavaScript translation domain and catalog workflow. A UI may have Django-rendered headings alongside client-side alerts produced by JavaScript. Translating only django.po does not guarantee that the JavaScript messages will appear in the target language.
Inventory the actual JavaScript sources and the tools used to extract and deliver those translations. Some applications use Django’s JavaScriptCatalog approach or their own front-end localization framework. Determine the actual route before adding duplicate messages to the wrong catalog.
Test a JavaScript-driven error after selecting the target language. If the server-side page is Spanish but the client-side dialog remains English, trace the JS localization catalog or frontend framework rather than revising the server-side PO entry.
Translator comments improve meaning accuracy
Django’s extraction workflow can carry developer comments and source references that give translators useful context. A short English word such as Draft, Share or Charge may have several possible target equivalents. A comment explaining the action and screen gives the reviewer a much better chance of choosing the intended meaning.
Write context in reader terms. Instead of saying used in view.py line 75, explain that the button shares a document with selected collaborators and does not make it public. This prevents a translation from suggesting a wider permission than the software actually grants.
Preserve comments through CAT and TMS imports. They are not necessarily user-visible content, but their loss can degrade translation quality. Review old comments when the product changes so they do not silently mislead the next editor.
Fuzzy entries are a request for renewed review
PO tools can mark entries as fuzzy when a prior translation may be related to a changed source. This can help reviewers find candidates, but a fuzzy entry is not an approval. A source change might be a harmless wording update or a meaningful change to an action, promise, quantity or warning.
Review the new source against the old intent. For a payment message, an update from payment authorized to payment captured is materially different. For a deletion warning, a newly added permanently changes the consequence. Do not automatically clear fuzzy flags just because the target sentence sounds plausible.
Record the reviewer decision and rerun compilation and runtime tests. A translation memory suggestion should shorten the editor’s work, not bypass the stage where someone confirms that the message still means the same thing.
Obsolete PO entries and catalog hygiene
When source code changes, a PO merge may retain obsolete entries for reference. Historical entries can be useful for translation memory or audit trails, but they should not be counted as current coverage. A project that reports ninety-nine percent complete by counting old unused strings is measuring catalog residue rather than what people actually encounter.
Classify active, new, changed, fuzzy and obsolete messages separately. Remove or archive obsolete entries according to the project’s maintenance policy, with care for supported older branches. Use source revisions and application lookup tests to determine which entries still matter.
Focus translators on active user journeys, especially newly introduced features and modified warnings. Clean catalogs reduce distraction and improve the quality of the changes that matter for the next release.
UTF-8 encoding and byte-order marks deserve checks
Django’s PO workflow expects appropriate Unicode text encoding, and its documentation discusses UTF-8 and avoiding a byte-order mark where unsupported. A text editor may display accents and Asian scripts correctly while saving the file in a form that causes a gettext utility to fail or corrupt text on another machine.
Check the file encoding before translation import and after export. Include accented Latin text, Cyrillic, Arabic, CJK characters and a supplementary Unicode character in a test fixture. Run the actual makemessages and compilemessages tools rather than trusting only a visual preview.
Do not erase difficult characters to make a broken encoding pipeline pass. The proper repair is to preserve the text using the required encoding and confirm it survives parsing, compilation and rendering. Removing accents or changing script is a language error, not a technical fix.
Compile errors should identify the smallest broken message
A gettext compilation step may report malformed PO syntax, missing formatting tokens or inconsistent plural fields. Treat that error as a precise repair task. Record the file, locale, entry and expected structure, and return it to the correct owner rather than asking a language reviewer to scan the entire document.
Use a CI process that runs the same gettext utilities as the deployed project. A target catalog can be valid under one local setup but fail under another toolchain if the workflow relies on nonportable assumptions. Record the installed versions where relevant.
A successful compile confirms some structural integrity. It does not confirm the target sentence tells the truth, that the selected language reaches the app or that the UI remains accessible. Keep technical and linguistic checks distinct.
Translation fallback can hide missing target content
Django may display original source-language strings when the target catalog does not supply a translation. This prevents blank interfaces but can conceal incomplete localization. A page that returns readable English instead of a missing-key marker may pass a shallow smoke test while disappointing the intended reader.
Maintain a coverage report based on target catalogs and active message inventories. Mark fallback separately from approved target text. On important workflows, such as consent, checkout, security notices and error recovery, treat missing translations as high priority.
Test a deliberate missing entry in development to see the runtime behavior and how the monitoring reports it. Then restore the translation. The team should know the difference between an intentionally shared brand term and an English instruction appearing because no one supplied the target.
HTML safety and escaping are not translator shortcuts
A message inserted into an HTML template can include markup-like punctuation, links or user-provided values. The translation may require natural reordering around emphasized words, but the rendering application is still responsible for safe escaping. Do not ask translators to insert raw HTML to bypass a layout limitation without a defined markup contract.
Prefer full, semantically meaningful messages with protected placeholders for dynamic content. If a link must move within a sentence, use a localization-aware component design rather than an English-specific character offset. Test keyboard focus and click behavior after rendering.
A technically valid target that inserts unsafe markup is not a quality translation. The user’s safety and the integrity of the application remain separate release requirements alongside linguistic accuracy.
Accessible text needs the correct language and action
Screen-reader users depend on accessible names, hints and state announcements, not just visible headings. A localized app can look complete yet leave its aria labels or validation errors in English. Include these resources in extraction coverage and attach context explaining what the control does.
Write accessibility copy to convey action and consequence. A trash icon may need Delete selected report, while a generic tooltip might explain the action’s reversibility. Do not force the same short visible label into every accessible slot. Ask a native-language reviewer to listen to the rendered output.
Test dynamic interpolation in announcements. An unresolved %(count)d or a badly placed account name can make a spoken message confusing even when it looks acceptable in a file diff.
Date, time, number and currency localization are separate layers
Translating Your appointment is on %(date)s does not guarantee the date appears in a convention the visitor recognizes. Django supports format localization, and applications may use timezone and formatting utilities. The exact content of the data value and its display format should be specified independently from surrounding prose.
For appointment reminders, test day-month ambiguity and timezone differences. For a payment message, test decimal separators, currency units and negative amounts. For technical identifiers, preserve the value’s exact characters so the reader can quote it to support staff.
A correct translation describes the right event. A correct formatting pipeline presents the right data. They must meet in the rendered sentence, and quality assurance should exercise both with realistic values.
Handle language switching across redirects and sessions
A visitor may select French, fill out a form, encounter an error and return to an English page. The French catalog may be complete. The error could be that the redirect, session or middleware path no longer selects the same language. Capture the effective language at each request and test both success and failure branches.
Avoid working around this by inserting French text directly into the form view or duplicating translations across unrelated catalogs. Repair the language selection mechanism so every approved message is consistently delivered. This supports cleaner updates and reduces translation debt.
Check authentication flows as well, including login, logout and password reset. People often need the clearest instructions exactly when they are moving through a route that differs from ordinary navigation.
Right-to-left languages require layout and data tests
Arabic and Hebrew can contain Latin email addresses, filenames and numbers. Even a correct stored sentence can look disordered when rendered without suitable bidirectional handling. Django’s translation files are not a substitute for direction-aware templates, components and CSS.
Use real input values to test left-to-right data embedded in right-to-left prose. Check copied identifiers, punctuation, links, cursor behavior and visual alignment. Avoid inserting invisible directional marks through mass search-and-replace without diagnosing the responsible component.
Keep translation decisions and rendering decisions explicit. A target-language specialist should approve grammar; engineering should maintain safe presentation and interaction. Collaboration produces a more dependable result than forcing one person to compensate for the other’s layer.
A worked failure: the PO translation is correct but never appears
Suppose a French reviewer fixes an account confirmation and saves django.po. The local website still displays the older sentence. Inspect whether compilemessages ran, whether the corresponding MO artifact was updated and whether that artifact was deployed to the right locale directory. Then request the message under a forced French language context.
If the expected value returns in a controlled test but not on the page, inspect actual request selection or a different message source. If the expected value never returns, fix the catalog path, compiled artifact or entry identity. The target sentence does not need a second rewrite merely because the delivery pipeline failed.
Add a smoke test that fetches a distinctive translated phrase from the deployed application. That one check can protect an entire batch from being invisible after release.
A worked failure: a string is translated before the user’s locale exists
A model field name is declared with immediate gettext while the application imports the model under the default language. Later, an Arabic visitor opens a form and sees the English label. The Arabic catalog may contain an accurate translation, but the label string was already resolved before the request set the active locale.
Check when the label was created and whether gettext_lazy is appropriate. Change the source declaration to defer translation, then render the form under different active languages. The repair belongs to the code’s evaluation timing rather than to the Arabic wording.
A regression test should create the form after forcing different languages and assert the field label changes appropriately. The point is to verify user experience, not just that a lazy object exists in source code.
A worked failure: plural placeholders disagree across forms
A notification’s singular and plural source messages use inconsistent placeholder identifiers. The target catalog has valid words, but compilation reports an incompatible format or a runtime branch fails when given certain counts. The issue is easier to diagnose by comparing each msgid and msgid_plural contract than by translating the sentence again.
Make the placeholder roles consistent at the source where possible, refresh the PO catalog and review the target forms. Test the exact ngettext call for one, two, five and other relevant values with the same input data structure. Confirm both grammatical form and correct inserted quantity.
Document the approved contract so subsequent editing cannot reintroduce it. This illustrates the benefit of a small, repeatable formatting test before releasing a large batch.
Prioritize high-risk messages for human review
A translator may handle hundreds of navigation labels efficiently, but a short sentence can carry major consequences. Payment state, account deletion, eligibility, privacy, permission scope and emergency instructions deserve context-rich review. A missing negative word or swapped actor can change the decision the user makes.
Provide screenshots, preceding actions and actual product behavior. Ask the target-language reviewer to explain what a reader would understand and what action they would take. Compare that answer with the intended behavior before approval. This is more revealing than counting how many words match the English source.
Do not rely solely on machine translation for consequential claims, even when the catalog compiles perfectly. Technical checks protect code structure; qualified human review protects meaning.
Create a practical CI localization gate
For every translation update, run source extraction in the configured environment, validate PO structure and expected placeholders, compile target MO files, verify target languages are in the planned inventory and execute representative application lookups. Include an explicit test for context-distinguished messages and a quantity matrix for plural translations.
Run a small set of browser tests covering a user journey with a form, error, confirmation and navigation between pages. Keep the app’s chosen language stable across those steps. For important screen-reader labels, include accessibility QA as a separate gate.
Reports should contain the locale, source reference, msgid or context, error class, expected behavior and observed output. The most useful report tells someone exactly what to fix. A generic localization failed message does not.
Frequently asked questions about Django translation
What is the difference between makemessages and compilemessages? makemessages extracts and updates editable PO catalogs from marked text, while compilemessages produces compiled MO resources for runtime use.
Why are my Django translations not showing? Check source marking, active language, catalog path, entry context, PO content, MO compilation and deployment. Test one message through the actual runtime to identify the broken stage.
Should I use gettext or gettext_lazy? Use immediate translation where the active language is already appropriate and a real string is needed; use lazy translation for declarations or objects that must resolve later under the user’s locale.
Can I translate placeholders such as %(name)s? Preserve their identifiers and format types unless code is deliberately changed. You can reorder them in a natural target sentence.
Do languages all need only singular and plural? No. The target PO catalog and plural rules can support additional forms. Test real quantities under the intended locale.
What is msgctxt? It marks a translation context so the same English source text can have different target meanings in different situations.
Is successful MO compilation sufficient for launch? No. Also verify the app selects the intended language, presents the translation in the right screen, handles variables and conveys accurate meaning.
Official references and a useful five-message exercise
Django documents translation extraction, contextual messages, pluralization and lazy strings at https://docs.djangoproject.com/en/6.0/topics/i18n/translation/ . Its translation functions are referenced at https://docs.djangoproject.com/en/6.0/ref/utils/ and language settings at https://docs.djangoproject.com/en/6.0/ref/settings/ . Use the documentation matching the Django version installed in the actual application.
The How People Translate Quickly index is at https://edukatesg.com/how-people-translate-quickly-series-index/ . The related general GNU gettext QA guide is at https://edukatesg.com/2026/10/19/how-people-translate-quickly-gettext-po-pot-translation-qa/ , and the larger system is at https://edukatesg.com/2026/09/18/master-art-of-translation-complete-system-moving-meaning-between-languages/ .
Select five messages from a real Django app: a template heading, a contextual short word, a model field label marked lazily, a named-placeholder message and an ngettext plural. Extract them, translate the target PO, compile the MO, force the intended language, and check the results in the interface. If that small route works with real data, repeat it across the full catalog. This is a reliable path to faster translation without sacrificing reader understanding.
