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

> Discover how the ai job search workflow selects cover letter language for Danish vs English postings. Learn about automatic classification and template selection.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: deep-dive
- Published: 2026-07-14

---

**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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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.

```python

# 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"`.

```python

# 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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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.

```python

# 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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/agents/job_application.py). It fetches the posting, calls `detect_language`, resolves the template with `build_cover_letter_path`, and renders the document.

```python
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:

```bash
$ 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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md) into the chosen template.

## Summary

- The **`detect_language`** function in [`tools/security_guards.py`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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`](https://github.com/MadsLorentzen/ai-job-search/blob/main/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.