# â€™ and Ã© in Your HTML Page — Fixing Mojibake and � Characters

Canonical: https://commareports.com/fix/html-weird-characters
Published: 2026-10-14

> Apostrophes show as â€™, accents as Ã©, or everything as black-diamond question marks. Decode the garbage back to the character it was, find which side guessed the wrong encoding, and fix it with one meta tag or one header.

# â€™ and Ã© all over the page

The page renders, but `don’t` shows as `donâ€™t`, `café` as `cafÃ©`, or
every accented letter has turned into `�`. Nothing is wrong with the
text. Two programs disagreed about which encoding it was in, and the
pattern of garbage tells you which two.

## Decode the garbage

| You see        | It was                  | What happened                   |
| -------------- | ----------------------- | ------------------------------- |
| `â€™`          | `’`                     | UTF-8 file read as Windows-1252 |
| `â€œ` / `â€`   | `“` `”`                 | UTF-8 read as Windows-1252      |
| `â€“` / `â€”`  | `–` `—`                 | UTF-8 read as Windows-1252      |
| `Ã©` `Ã¼` `Ã«` | `é` `ü` `ë`             | UTF-8 read as Windows-1252      |
| `Â ` or `Â£`   | non-breaking space, `£` | UTF-8 read as Windows-1252      |
| `â‚¬`          | `€`                     | UTF-8 read as Windows-1252      |
| `ðŸ˜€`         | an emoji                | UTF-8 read as Windows-1252      |
| `ÃƒÂ©`         | `é`                     | Mis-decoded twice               |
| `�`            | anything non-ASCII      | Windows-1252 file read as UTF-8 |

Two families, opposite fixes:

- **`Ã` and `â€` sequences** — the bytes are fine UTF-8. The reader
  guessed wrong. Fix the declaration.
- **`�`** — the bytes are not UTF-8 at all. Declaring UTF-8 harder
  changes nothing. Fix the file.

The full mechanism is on the [mojibake](/glossary/mojibake) glossary page.

## How the browser picks an encoding

In order, first match wins:

1. A **byte order mark** at the start of the file
   ([BOM](/glossary/byte-order-mark)).
2. The **`charset` in the HTTP `Content-Type` header**.
3. A **`<meta charset>`** in the first 1024 bytes.
4. **A guess** — from the content and the browser's locale. On an
   English-language system that guess is often Windows-1252.

A UTF-8 file with no BOM, opened from disk or served without a charset,
and no meta tag, lands on step 4. That is where almost every `â€™` comes
from.

## Fix: declare UTF-8

Make this the first element inside `<head>`, before the `<title>` and
before any long inline `<style>` that could push it past 1024 bytes:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Q3 report</title>
  </head>
</html>
```

If a server is involved, the header must agree, because it beats the
meta tag:

```
Content-Type: text/html; charset=utf-8
```

On S3, that is the object's metadata (`--content-type
"text/html; charset=utf-8"` on upload). In nginx, `charset utf-8;` in
the server block. In Express, `res.type("html")` already adds it.

## Fix: write the file as UTF-8

If you see `�`, the file was written in a legacy encoding. The usual
culprit is code that writes text without naming one:

- **Python:** `open(path, "w")` uses the locale encoding on Windows.
  Always pass `encoding="utf-8"`.
- **Java before 18:** `FileWriter` uses the platform default. Pass
  `StandardCharsets.UTF_8`.
- **Windows PowerShell 5.1:** `>` and `Out-File` write UTF-16. Use
  `Out-File -Encoding utf8` or `Set-Content -Encoding utf8`.
- **Editors:** check the encoding in the status bar. VS Code's "Reopen
  with Encoding" shows you what the bytes really are; "Save with
  Encoding → UTF-8" converts them.

Then add the meta tag, so the next reader doesn't have to guess.

## Fix: text that is already garbled

If the garbage was saved — copied into a database, a CMS, another report
— fixing the header won't help; the `Ã©` is now real characters. One
round of UTF-8-read-as-Windows-1252 reverses cleanly:

```python
fixed = broken.encode("cp1252").decode("utf-8")
```

For mixed or multiple rounds (`ÃƒÂ©`), the `ftfy` library's
`fix_text()` detects the chain and undoes it. Text with `�` cannot be
recovered — the original byte was thrown away at decode time.

## Where it shows up in reports

- **Double-clicking a generated HTML file.** No server, no header — the
  file's own `<meta charset>` is the only signal. Generators that emit
  UTF-8 without one rely on the browser guessing right.
- **A CSV opened in Excel.** Same bug, different program:
  [CSV opens wrong in Excel](/fix/csv-opens-wrong-in-excel).
- **Copy-paste from Word or Outlook.** Smart quotes and em dashes are
  the first characters outside ASCII most text contains, which is why
  `â€™` is the face of this bug.
- **An S3 or static host** serving `text/html` with no charset — the
  file's meta tag decides, if it has one.

## Publish it once, encoded once

Comma reads uploaded HTML as UTF-8 and serves every report with
`charset=utf-8`, so a UTF-8 file renders the same for every reader on
every machine — no locale guess involved. If an old export shows `�`
after upload, it was saved in Windows-1252: re-save it as UTF-8 and
update the report at the same link.

Readers can then select the sentence that still looks wrong and comment
on **it** ([comment on HTML](/comment-on-html)), instead of sending a
screenshot of `â€™`.

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited
revision history.

**[Preview your HTML with the right encoding →](/tools/html-viewer)**

### Related

- [Mojibake](/glossary/mojibake) · [Byte order mark](/glossary/byte-order-mark) · [MIME type](/glossary/mime-type)
- [CSV opens wrong in Excel](/fix/csv-opens-wrong-in-excel)
- [The HTML file shows code instead of the page](/fix/html-file-shows-code-instead-of-page)
- [HTML escape / unescape](/tools/html-escape)
- [Troubleshooting](/fix)
