QA & troubleshootingこの記事を日本語で読む

The CSV that looks fine in one tool and garbage in another

A translator opens your CSV in spreadsheet software, edits a few rows, sends it back — and now your import pipeline reads garbage. Or the reverse: the CSV your pipeline exported opens as garbled text the moment someone double-clicks it. Both symptoms come from the same root cause: plain CSV carries no encoding label, so every tool that opens one has to guess, and different tools guess differently on a Japanese-locale system.

Why plain CSV defaults to a legacy encoding

A CSV file is just comma-separated bytes; nothing in the format itself says what encoding those bytes are in. On a Japanese-locale operating system, spreadsheet software has historically saved and opened plain CSV using Shift_JIS or, more precisely, Microsoft's CP932 superset of it — because that was the system's native encoding for Japanese text for decades, long before UTF-8 became the universal default it is today.

That default has not fully gone away. A CSV saved from spreadsheet software on such a system, with no other signal to go on, is often still written as CP932 rather than UTF-8. Any tool that assumes UTF-8 — most modern code, most localization tooling, most web-based tools — will misread it and produce mojibake on every non-ASCII character.

Why a UTF-8 BOM changes the behavior

A BOM (byte order mark) is a specific byte sequence placed at the very start of a file to signal its encoding. For UTF-8 that sequence is the three bytes EF BB BF, which do not correspond to any visible character — they exist purely as a hint for the next reader.

Spreadsheet software on a Japanese-locale system generally recognizes that hint: a CSV that starts with EF BB BF is opened as UTF-8 correctly, while the exact same text without it falls back to the legacy CP932 assumption. This is why the advice "add a UTF-8 BOM" comes up constantly for CSV interoperability — it is not decoration, it is the one signal that flips the default.

The trade-off is that not every tool expects a BOM. A parser that treats the file strictly as data may read those three bytes as part of the first cell's content instead of stripping them, producing a stray character glued to the first column header. Whether to include a BOM has to be a decision made for the specific tools on both ends of the file, not a blanket default.

How saving from a spreadsheet silently changes the encoding

This is the trap that catches translation round trips specifically. You export a UTF-8 CSV with a BOM from your pipeline, a translator opens it, edits some cells, and saves. Depending on the save format chosen and the software's defaults, the file that comes back may no longer be UTF-8 at all — it can be silently re-saved as CP932, sometimes without the BOM it started with, and with no on-screen warning that anything changed.

Nothing in that round trip looks wrong to the translator: their software opened the file correctly and saved it in a format it considers normal for CSV on that system. The corruption only becomes visible on your end, when your importer reads the returned file with the encoding it expects and gets mojibake instead.

A safer workflow for files that must survive round trips

The most reliable fix is to stop relying on plain CSV for anything that will be edited outside your own pipeline.

  • Prefer a spreadsheet-native format for the human-editing step, if your tooling can read it directly — it carries its own explicit encoding and formatting metadata, so there is no guessing on either side.
  • If CSV is required, include a UTF-8 BOM on export specifically because you know the file will pass through spreadsheet software, and confirm your import step strips it rather than treating it as content.
  • On import, never assume the encoding — detect it explicitly (BOM presence, or a lightweight heuristic check) and log which encoding was used for each file, so a silent re-save shows up as a visible log line instead of garbled text three steps later.
  • Treat every hand-off between systems as an encoding boundary and verify at that boundary — a quick round-trip test with representative full-width and half-width characters catches this class of bug before it reaches production text.

Related articles