Back to blog

Master Django: How to search for phrase

2026-05-18 9 min read
Master Django: How to search for phrase

Meta description: Need to change one UI phrase across Django code, templates, and .po files? Use exact search, regex, and PO-aware commands without missing variants.

You changed Add to Cart to Add to Basket. The code review is approved. The product team is happy. Then you load the app in French and still see the old copy in one modal, two validation errors, and a stale msgid in locale/fr/LC_MESSAGES/django.po.

That's the core search for phrase problem in Django. The phrase might live in Python, templates, JavaScript, fixtures, or gettext catalogs. Your editor's Find panel catches the obvious hits. It usually misses the messy ones.

A phrase matters because it's not just a bag of words. In phrase search, the exact word order is the unit, not the individual tokens, which is why exact matching narrows results and improves precision according to WorldCat phrase search documentation. In a Django app, that distinction shows up fast. Add to Cart is not the same maintenance problem as separate hits for add, to, and cart.

Finding a Needle in a Haystack

The first mistake is treating every text change like a plain text search.

In a real Django codebase, the same phrase can appear in all of these places:

Here's the kind of spread you're dealing with:

from django.utils.translation import gettext_lazy as _

class CartForm(forms.Form):
    submit_label = _("Add to Cart")
<button>{% translate "Add to Cart" %}</button>
#: templates/shop/product_detail.html:18
msgid "Add to Cart"
msgstr "Ajouter au panier"

You also hit awkward cases where the visible phrase is broken up by markup or placeholders:

<a href="#">
  {% translate "Add" %} <strong>{% translate "to Cart" %}</strong>
</a>

That won't show up in a literal exact match for the full phrase.

Practical rule: Start with exact phrase search. Assume it's incomplete. Then widen the search on purpose.

When you treat search for phrase as code archaeology, the workflow gets better. You search the literal phrase first, then variations, then translation structure. That order saves time and avoids false confidence.

Your Command-Line Toolkit for Code Search

Your editor is fine for a quick pass. For whole-repo work, use the shell.

A comparison infographic detailing the features, primary functions, and performance of command-line tools grep, find, and ripgrep.

Use ripgrep first

rg is my default. It's fast, respects ignore files, and handles regex cleanly.

rg -n -F "Add to Cart" .

Useful variants:

rg -n -i "add to cart" .
rg -n -t py -t html -t po "Add to Cart" .
rg -n --glob '!**/node_modules/**' --glob '!**/__pycache__/**' "Add to Cart" .

Use it when you want raw speed and good defaults.

Use git grep inside tracked code

git grep is better when you only care about files Git knows about. That cuts noise from build artifacts and generated junk.

git grep -n "Add to Cart"
git grep -n -i "add to cart"
git grep -n, '*.py' '*.html' '*.po'

Use it when you're auditing code that will ship.

Keep grep for bare servers

grep is everywhere. It's not pleasant, but it's always there.

grep -RIn --exclude-dir=__pycache__ --exclude-dir=node_modules "Add to Cart" .

If you spend time in shell environments where these basics still feel rusty, this essential guide to Terminal for Mac users is a decent refresher on navigation and command-line habits.

Here's the short version.

Tool Best use What it gets right What it gets wrong
rg Day-to-day repo search Fast, regex-friendly, sane defaults Not installed everywhere
git grep Tracked source only Repo-aware, low noise Ignores untracked files
grep Minimal environments Universal availability More flags, more cleanup

For Django translation work, I usually combine them:

  1. git grep for tracked source.
  2. rg for broader pattern hunts.
  3. grep only when I'm on a box without better tools.

If you want a command-centric workflow around translation jobs, the TranslateBot command reference is worth skimming because it keeps everything in CLI land instead of pushing you into a portal.

Search tracked files with git grep. Search patterns with rg. Reach for grep when the environment gives you no choice.

Using Regex to Find Phrase Variations

Literal matching breaks the moment copy isn't perfectly uniform.

You'll see all of these in old apps:

A better search for phrase flow uses regex the same way a database search uses controlled terms and variants. In systematic search methods, the workflow is to build a precise query from variants and syntax rather than guessing with one broad string, as described in this systematic-search methodology overview.

Start with case and punctuation

For most UI copy changes, this catches the common drift:

rg -n -i 'sign[ -]?up' .

That pattern matches:

If spacing is inconsistent:

rg -n -i 'sign\s*[- ]?\s*up' .

Search around placeholders

Django translations often wrap placeholders that you can't break.

msgid "Welcome, %(name)s"
msgstr ""

If the phrase changes but the placeholder must stay, search for the structure, not just the visible words:

rg -n '%\(name\)s' locale
rg -n 'Welcome,\s+%\(name\)s' locale

Brace-style placeholders show up in mixed stacks or copied strings:

rg -n 'items?\s+\{count\}' .

A realistic .po example:

msgid "%(count)s item in your cart"
msgid_plural "%(count)s items in your cart"
msgstr[0] ""
msgstr[1] ""

If you're updating cart to basket, don't search only for the raw phrase. Search the placeholder-bearing variant too:

rg -n '%\(count\)s .*cart' locale

Know when regex is worse

Regex is great for variation. It's bad when you need exact legal or UX wording.

Use exact matching when:

Use regex when:

A loose regex can explode your result set. That's the same failure mode you get in broad database searching. If the pattern returns noise, tighten it fast.

How to Search for a Phrase in PO Files

A .po file is not just text. It's a structured catalog.

That distinction matters because msgid "Add to Cart" and msgstr "Ajouter au panier" answer different questions. One tells you where the source phrase exists. The other tells you how that phrase was translated in a locale.

A four-step workflow diagram illustrating the process for effectively searching and editing phrases within .PO files.

Search msgid when source copy changed

If product changed the canonical English phrase, search msgid first.

rg -n '^msgid "Add to Cart"$' locale

That tells you exactly which catalogs still carry the old source string.

If you want surrounding context:

rg -n -C 2 '^msgid "Add to Cart"$' locale

That usually shows the file reference comment and current translation.

Example:

#: templates/shop/product_detail.html:18
msgid "Add to Cart"
msgstr "Ajouter au panier"

Search msgstr when translation output looks wrong

When a locale displays the wrong visible text, target msgstr:

rg -n '^msgstr ".*panier.*"$' locale/fr/LC_MESSAGES/django.po

That's useful when translators used inconsistent terms and you need to standardize one locale without touching the source language.

Find untranslated and fuzzy entries

These two searches belong in every Django i18n playbook.

Untranslated entries:

rg -n '^msgstr ""$' locale

Fuzzy entries:

rg -n '^#, fuzzy' locale

And here's a realistic fuzzy block:

#: templates/shop/cart.html:42
#, fuzzy
msgid "Add to Basket"
msgstr "Ajouter au panier"

A fuzzy flag is a warning, not a translation strategy. You still need to review it before shipping.

Search msgid for source truth. Search msgstr for locale behavior. Search #, fuzzy and empty msgstr before every release.

Watch plural forms and multiline strings

Plural entries need their own search habit:

rg -n 'msgid_plural|msgstr\[[0-9]+\]' locale

Multiline strings also trip people up because the phrase may be split across lines:

msgid ""
"Add to Cart "
"for selected plan"
msgstr ""

A plain literal search may miss that. At that point, use context searches or a PO-aware editor.

For teams working directly with gettext files in code review, the TranslateBot PO file docs are useful because they stay focused on actual django.po workflows instead of generic localization theory.

One more thing matters here. Developer-facing translation isn't just about linguistic quality. A significant engineering risk is broken placeholders or malformed markup in .po files, which can crash rendering if your pipeline doesn't guard against them. That's why I treat gettext files as structured data first and text second.

Let the Code Find the Phrases for You

Manual search is still necessary. It just shouldn't be your only defense.

Once you already run makemessages, the better move is to let your tooling surface changed strings as part of the normal dev cycle. That shifts the job from hunting text to reviewing diffs.

A four-step infographic illustrating the process of automating phrase change detection for software localization and development.

A practical loop looks like this:

python manage.py makemessages -l fr
python manage.py translate --locale fr
python manage.py compilemessages

That works because new or changed msgid entries become visible in Git. You review the delta instead of rediscovering every phrase by hand.

What automation is good at

TranslateBot is one option here. It adds a manage.py translate workflow for Django projects, works on .po files, and exposes a Python API for automation if you want to wire checks into your own scripts.

What automation still won't solve

If you want a light primer on the language side behind machine-assisted workflows, this piece on understanding NLP concepts is useful background. The engineering question stays the same though. Can your pipeline translate safely without corrupting placeholders or markup?

That's the bar. If the tool can't preserve code-shaped content, don't put it in CI.

A Pre-Deploy Phrase Search Checklist

Before you merge a text change, run the boring checks. They catch the stuff that slips through review.

A checklist titled Pre-Deployment Phrase Search Checklist listing five steps for verifying updated phrases in software projects.

  1. Run an exact tracked-file search Use git grep -n "Add to Cart" to find literal leftovers in source you ship.

  2. Run one regex pass for variants
    Use rg -n -i 'add[ -]?to[ -]cart' . when casing, spacing, or punctuation may have drifted.

  3. Check gettext catalogs by field
    Search msgid, then msgstr, then fuzzy and empty entries inside locale/.

  4. Regenerate messages before reviewing translations
    If you skip makemessages, you're searching stale catalogs.

  5. Compile and load the app in one non-default locale
    compilemessages catches file-level issues. The browser catches wording and layout issues.

A phrase change isn't done when the English page looks right. It's done when the source, catalogs, and rendered UI all agree.


If your team wants to stop copy-pasting strings into portals, TranslateBot is worth a look. It keeps Django translation work in code, translates .po files through a manage.py command, and is built around preserving placeholders and HTML so you can review clean diffs instead of fixing broken catalogs by hand.

Stop editing .po files manually

TranslateBot automates Django translations with AI. One command, all your languages, pennies per translation.