How the AI Job Search Workflow Chooses Cover Letter Language for Danish vs. English Job Postings

The MadsLorentzen/ai-job-search workflow automatically classifies each job posting as Danish or English and then selects the corresponding LaTeX cover-letter template before compilation.

The MadsLorentzen/ai-job-search repository automates end-to-end job applications, including localized cover letter generation. A key requirement for the Danish market is handling both local and international postings, so the pipeline explicitly resolves cover letter language for Danish versus English job postings before rendering begins. This article breaks down the detection logic, template selection, and CLI orchestration as implemented in the source code.

Language Detection for Danish and English Postings

The language detection is centralized in tools/security_guards.py. It runs a fast, two-stage classifier that prioritizes a curated list of Danish domain terms before falling back to a statistical library.

Token-Based Danish Detection

The first stage scans the raw posting text for tokens that are strong indicators of a Danish job ad. The module defines the constant set LANG_TOKENS_DK containing high-confidence markers:

  • "lønindeks"
  • "ansættelses‑dato"
  • "jobopslag"
  • "virksomhed"

If any token is present, the function immediately returns "da" without additional overhead.


# tools/security_guards.py

LANG_TOKENS_DK = {"lønindeks", "ansættelses‑dato", "jobopslag", "virksomhed"}

def detect_language(text: str) -> str:
    if any(tok.lower() in text.lower() for tok in LANG_TOKENS_DK):
        return "da"
    # fallback follows...

Fallback Detection with langdetect

If the token check is inconclusive, the function imports detect from the langdetect library and runs it on the full text. This returns the ISO-639-1 code ("da" or "en"). If the library fails or raises an exception, the workflow conservatively defaults to "en".


# tools/security_guards.py

def detect_language(text: str) -> str:
    if any(tok.lower() in text.lower() for tok in LANG_TOKENS_DK):
        return "da"
    try:
        from langdetect import detect
        return detect(text)        # returns "da" or "en"

    except Exception:
        return "en"

Cover Letter Template Selection for Danish and English Postings

Once the language is known, the workflow builds the path to the correct LaTeX template. The helper build_cover_letter_path lives alongside the detection logic in tools/security_guards.py. It maps a language code to a filename suffix:

  • English → cover_{company}_{role}.tex
  • Danish → cover_{company}_{role}_da.tex

Both templates reside under the cover_letters/ directory according to the repository layout.


# tools/security_guards.py

def build_cover_letter_path(company: str, role: str, lang: str) -> str:
    suffix = "_da" if lang == "da" else ""
    return f"cover_letters/cover_{company}_{role}{suffix}.tex"

The orchestrator then passes this path to the rendering stage.

End-to-End Workflow Orchestration

The full pipeline is driven by the CLI entry point in agents/job_application.py. It fetches the posting, calls detect_language, resolves the template with build_cover_letter_path, and renders the document.

from tools.security_guards import detect_language, build_cover_letter_path

def prepare_cover_letter(posting_text: str, company: str, role: str):
    lang = detect_language(posting_text)          # → "da" or "en"

    template_path = build_cover_letter_path(company, role, lang)
    # ... populate template and compile

    return template_path, lang

From the terminal, a user can trigger the workflow with a posting URL:

$ python -m agents.job_application \
    --posting-url https://example.com/job123 \
    --company AcmeCorp \
    --role "Data Engineer"

# Using Danish template: cover_letters/cover_AcmeCorp_Data_Engineer_da.tex

LaTeX Compilation and Danish Font Support

The Danish template uses XeLaTeX and the fontspec package to load the Danish-compatible Raleway font, ensuring proper glyph support for Danish characters. The English template uses the same build toolchain but relies on default Latin encoding. The orchestrator invokes the compiler after injecting candidate profile data from CLAUDE.md into the chosen template.

Summary

  • The detect_language function in tools/security_guards.py uses the curated LANG_TOKENS_DK set first, then falls back to langdetect.
  • A detection result of "da" appends a _da suffix to the template filename; otherwise the English base name is used.
  • Templates live in cover_letters/ and are rendered with XeLaTeX, with Danish templates configured for Danish glyph support.
  • The CLI entry point in agents/job_application.py coordinates fetching, detection, selection, and compilation in a single command.

Frequently Asked Questions

How does the workflow decide if a job posting is Danish or English?

The detect_language function scans the posting text for Danish-specific tokens defined in LANG_TOKENS_DK. If none are found, it falls back to the langdetect library to return an ISO language code, defaulting to English on any error.

What happens if the language detection fails?

If langdetect raises an exception or cannot classify the text, the exception handler returns "en". This ensures the pipeline never blocks and always produces a cover letter, defaulting to the English template.

Where are the cover letter templates stored?

Templates are stored in the cover_letters/ directory at the repository root. English templates follow the pattern cover_{company}_{role}.tex, while Danish variants are named cover_{company}_{role}_da.tex.

Can the detection logic be extended to support other languages?

Yes. According to the implementation in tools/security_guards.py, you can add a new token set for another language and extend the conditional chain in detect_language, or rely on langdetect if it already supports the target language natively.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →