Korean coverage was 796 of 1132 keys (70%), so a large part of the UI fell
back to English. Alongside the missing entries, the existing translations
carried a few defects that were visible to users.
Fixes:
- Three messages had lost the `$` from their placeholders, leaving a literal
`{origin}`, `{org}` and `{from}->{to}` on screen, because `$msg()` only
substitutes `${...}`.
- `dialog.yourLanguageAvailable` referenced `%{Display language}`, but the key
is `Display Language`, so the raw token was shown. Corrected to match ja.
- Sixteen onboarding messages ended with the Japanese full stop `。`.
- Four messages hard-coded a particle after a variable (`${measuredSize}가`),
which is ungrammatical for values ending in a consonant. They now use the
paired form `이(가)`, `을(를)`, `(으)로`.
Terminology is now consistent with the Obsidian Korean UI and with the
definitions in docs/terms.md:
- `vault` is 보관함, `device` is 기기, `status bar` is 상태 표시줄.
- `chunk` is 청크 everywhere; the Eden settings had rendered it as 변경 기록
("change record"), which is not what a chunk is.
- `Compaction` is 압축 정리, separating it from `Data Compression` (데이터 압축),
which had shared the same word.
- HTTP `Origin` is 출처; it had been 원점, the geometric origin.
- Six English strings that had mapped to two different Korean strings each are
now unified.
The remaining 336 keys are translated, including the setup wizard outro, the
insecure-chunk and v0.25.6 recovery dialogues, and the database adapter
migration messages.
This ports vrtmrz/livesync-commonlib#79 to this repository, following the
v1.0.0 boundary change that moved application translations here. On top of
the original work, the 17 keys newly introduced in v1.0.0 (the P2P setup
wizard Invitation, OutroNewP2PUser and RebuildEverythingP2P dialogues, and
the moduleCheckRemoteSize notices) are translated as well.
Verified with _tools/checkI18nCoverage.ts (ko: 1132/1132, 0 missing),
tsc --noEmit, and a dev build. Generated resources are committed as produced
by `npm run i18n:bake`; besides the `ko` entries it also trims a stale
trailing space from five es/zh entries in combinedMessages.prod.ts that had
been left behind by the migration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EtFsro96LHztmoYmRjtfAy
Use the preferred Obsidian element helper and preseed the real-Obsidian scenario before startup. Register the focused test and strengthen revision boundary coverage.
Keep unresolved conflicts visible after Not now, reopen them only on explicit requests, and clear stale warning and dialogue state when a replicated resolution arrives. Add typed provisional English messages, revision-tree regressions, specifications, and a focused Real Obsidian E2E.
Obsidian's mobile storage layer forwards mtime/ctime to Capacitor's
Filesystem.setTimes, whose native binding casts the value to a Java Long.
A non-integer (float) timestamp makes that cast throw (ClassCastException:
Double cannot be cast to Long), which crashes the app on launch as soon as
such a document is replicated in.
Float timestamps can reach the database from any client that stores
fs.Stats.mtimeMs without flooring. Coerce mtime/ctime to integer ms at the
Obsidian vault and storage adapter write boundary, so a float already
present in the mesh can't crash the app regardless of where it came from.
The truly central choke point is dbToStorage in livesync-commonlib; a
matching guard there would cover the CLI and webapp too. This change
protects the platform that actually crashes.
Claude-Session: https://claude.ai/code/session_0123E9jVQrsgu3zb82Csuwhi
CLIWatchAdapter._toNodeFile passed chokidar's raw stats.ctimeMs/mtimeMs
straight through. On Linux those carry sub-millisecond precision (e.g.
1778511180024.462), so watch-mode file changes wrote non-integer
timestamps into the database.
The earlier floor fix (3f7bb047) covered the scan/stat adapters but missed
this watch path, which is the one the daemon actually uses at runtime.
Mobile clients then crash on such a document because Capacitor's
Filesystem.setTimes casts the value to a Java Long (ClassCastException:
Double cannot be cast to Long).
Floor ctimeMs/mtimeMs here, with a null guard so a partial stat still
falls back to Date.now(). Adds a regression test that a fractional stat is
floored.
Claude-Session: https://claude.ai/code/session_0123E9jVQrsgu3zb82Csuwhi