Skip to content

fix(hig): drop the em dash from five on-screen strings - #2199

Merged
datlechin merged 1 commit into
mainfrom
fix/window-title-em-dash
Aug 18, 2026
Merged

fix(hig): drop the em dash from five on-screen strings#2199
datlechin merged 1 commit into
mainfrom
fix/window-title-em-dash

Conversation

@datlechin

Copy link
Copy Markdown
Member

Follow-up to #2194, which reported this while working nearby.

CLAUDE.md bans the em dash from anything a user reads. Five on-screen strings still had one:

Where Was Now
JSON viewer window title JSON — <column> JSON: <column>
PHP viewer window title PHP — <column> PHP: <column>
Settings > Account, expired-license banner Sync paused — Pro license expired Sync paused, Pro license expired
Foreign key preview, NULL cell NULL — no referenced row NULL, no referenced row
Connection import list, warning line — <warning> , <warning>

Why the comma, and why the colon

The comma is the house style, not a guess. The repo has already migrated four identical em dashes and every one became a comma: Truncated, read only, License expired, sync paused, and both ~/.pgpass found, ... strings. License expired, sync paused matters most here, because it is the live sibling of the Account banner. A colon there would punctuate the same fact two different ways on two adjacent surfaces.

The colon stays for the two window titles, where the left side is a bare label rather than a clause.

The import list is the one place a colon would have been actively wrong. The subtitle immediately before it is \(host):\(port), rendered in an HStack(spacing: 0) under lineLimit(1), so a colon would produce localhost:5432: Some values are set outside this file: two colons, two meanings, one line.

The JSON title was also unlocalized

It was built as columnName.map { "JSON — \($0)" }, raw interpolation with no String(localized:), so the key never reached the catalog and the title was English in every locale. It now uses String(format: String(localized: "JSON: %@"), columnName), the shape CLAUDE.md requires and the one the PHP twin already used, so it is translatable for the first time.

What was deliberately left alone

  • JetBrainsCredentialStore.swift:44,48,52. Three Keychain service names embed an em dash because they mirror IntelliJ's own generateServiceName format, IntelliJ Platform <subsystem> — <key>. They are lookup keys, not prose, and rewriting them would break JetBrains credential import. They spell the character as \u{2014}, so a literal-character grep does not surface them, which is exactly how a blanket sweep would have broken this.
  • Nine "no value" placeholder glyphs (Text(verbatim: "—"), ?? "—") in the integrations panes and the metadata models. A bare em dash meaning "empty" is the macOS convention Finder and Activity Monitor use. It is a glyph, not a sentence to rewrite.
  • Log messages, comments, released CHANGELOG sections, and appcast.xml. Not user-facing, or a frozen record of what already shipped. [Unreleased] had none, and docs/ is already clean.

Translations

The catalog is compiler-extracted, and a command-line xcodebuild does not write back to it: I measured the file hash before and after a full build and it did not move. So the four renamed keys keep their old translations and the new keys are absent, which means these five strings fall back to their English source in tr, vi, zh-Hans and zh-Hant until the catalog is regenerated in Xcode and refilled.

No em dash reaches any user either way, because the fallback is the fixed English. This also matches what the repo already does: decd6952e renamed strings the same way and 627a03a34 refilled the translations a month later.

Five values are worth carrying over when that happens. The rest of the old values are verbatim English copies with nothing to preserve.

Old key Language Value to re-key, with the mark swapped
NULL — no referenced row tr NULL, referans satır yok
NULL — no referenced row vi NULL, không có dòng tham chiếu
NULL — no referenced row zh-Hans NULL,无引用行
Sync paused — Pro license expired vi Tạm dừng đồng bộ, giấy phép Pro đã hết hạn
Sync paused — Pro license expired zh-Hans 同步已暂停 - Pro 许可证已过期 (already has no em dash)

zh-Hant already reads NULL,沒有參照的列 and 同步已暫停,Pro 授權已過期, so both are correct as they stand.

I did not hand-edit Localizable.xcstrings: the repo hook warns it is shared and frequently dirty from other work, and hand-writing 2.4 MB of catalog to save five values is the riskier trade.

The guard

UserFacingEmDashGuardTests reads the app sources and fails on an em dash inside a string literal. It exempts the placeholder glyph, comments and log messages, and needs no exemption for the JetBrains names because those spell the character as an escape.

A guard test that cannot fail is worse than none, so I checked this one bites: reintroducing Text("NULL — no referenced row") fails it. A companion suite tests the classifier directly on each case it has to get right, including the escaped spelling and the multi-line log call.

Verification

  • generate: PASS
  • build (Debug): PASS
  • test UserFacingEmDashGuardTests, UserFacingEmDashClassifierTests: PASS, 6 executed, 6 passed
  • Mutation check: reintroducing one em dash fails the guard
  • swiftlint over TablePro and TableProTests: 0 violations

No UI automation: these are static strings with no flow to drive, and the source-scanning guard covers them more directly than a UI test could.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit c1789a8 into main Aug 18, 2026
8 checks passed
@datlechin
datlechin deleted the fix/window-title-em-dash branch August 18, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant