Repository navigation
feat(i18n): add Turkish (tr) translation - #815
Conversation
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 31 minutes. View limit details
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Summary
Merge Risk: ⚪ Minimal · up to This adds Turkish as a UI language with matching release notes and documentation. No concrete merge-blocking risk was found in the supplied changes. Pre-merge checks |
|
devswha#683) > This changes DESIGN.md's "dialogs and their scrims snap open and closed" rule for phones (≤640px), so it is a design call as much as code. DESIGN.md, the changelog and `src/AGENTS.md` say what changes. Desktop keeps the snap. ## Change On a phone, the app looked and felt like a web page. Dialogs and menus appeared with no motion and could not be swiped away. The drawer slid on its own instead of following the finger. A tapped header button drew an amber box, a long press selected a button's label, and a pinch zoomed the whole interface. This makes it move and respond like an iPhone app, with the same screens and layout. - **Sheets** (dialogs, the ⋯ menu, the palette, Settings) rise from the bottom, follow a finger dragged down from their head or from a list at its top, and drop however they close. A short drag springs back. Past 30% of the height, or with a flick, the sheet closes through the same close as the scrim. A stroke on a sheet scrolls nothing behind it (`touch-action: none`), but the sheet's own lists still scroll. In `lib/sheets.ts`, each sheet calls `useSheetSwipe`. `watchSheetExits` (started in `main.tsx`) puts a closed scrim's last frame back as an inert `.is-leaving` copy inside a closed shadow root until it has slid out, so no query of the page finds two copies of a sheet. - **Drawer** stays under the finger (`lib/edgeSwipe.ts`) and settles by how much of it shows and how fast it was let go. The scrim stays mounted while the drawer is closed (`opacity: 0`, no pointer events) and fades. Otherwise, a swipe begun on the scrim would unmount it mid-stroke, and the stroke's `touchend` would go with it, leaving the drawer stuck partway. A stroke that still holds the drawer when the next touch begins lets it go first. - **Chat**: the header is glass (`--bar-bg` + `--glass-blur`) and the transcript scrolls under it. Only the transcript reaches under the header, so a prompt card, held messages and the composer stay below it. While nothing stands between them, the input card floats over the end of the transcript on `--card-glass`. The xterm mount is not fitted under the chat, so this resizes no pty. - **Touch** (`hover: none` and `pointer: coarse`): a press dims a button instead of drawing the amber `:active` box, and a list row fills. The app's chrome is not selectable and opens no callout, while the transcript, code, files and fields still are. Pinch and double-tap do not zoom, and landscape does not inflate text. - Reduced motion drops the rise and drop. A drag still follows the finger. Desktop is unchanged: sheets move only at ≤640px, the glass only at ≤768px, and the touch rules apply only on touch screens. | Chat: glass header, floating input card | Drawer under the finger | Settings sheet dragged down | |---|---|---| | <img src="https://raw.githubusercontent.com/jiunshinn/herdr-web-ui/67a11d82d843f3370d27bd9cae43f919386edbdd/ios-feel-chat.png" width="260"> | <img src="https://raw.githubusercontent.com/jiunshinn/herdr-web-ui/67a11d82d843f3370d27bd9cae43f919386edbdd/ios-feel-drawer.png" width="260"> | <img src="https://raw.githubusercontent.com/jiunshinn/herdr-web-ui/67a11d82d843f3370d27bd9cae43f919386edbdd/ios-feel-sheet.png" width="260"> | Screenshots are from the demo build (fixture transport) at 390×844 in headless Chrome, not a live session. The drawer and sheet shots are taken mid-stroke, with the finger still down. ## Validation - `bun run typecheck`, `bun run build`, and `HERDR_TEST_MODE=unit bun test ./src`: 854 pass. New tests: `src/lib/sheets.test.ts`, drawer tracking in `src/lib/edgeSwipe.test.ts`, and a stroke whose `touchend` never arrives. - Phone demo scripts on this branch: - Pass: settings-pages, keyboard-viewport, droplet, chat-greeting, composer-fit, held-rows, prompt-dock, key-bar-customization and sidebar-activity (`*-demo-regression.ts`), plus `mobile-viewport-regression.ts`, `file-viewer-regression.ts` and `file-viewer-mobile-regression.ts`. - Fails: `font-swap-demo-regression.ts`, with the same assertion on an unchanged `main` on my Mac ("the faces redrew the conversation at another height (455 -> 455)"). - Two scripts measured a sheet mid-rise or found a closing copy. `scripts/sheets-settled.ts` waits for sheets to settle, and `openSettingsPage` targets the open dialog. - iOS Simulator Safari (iPhone 17 Pro): the glass header, sheets and drawer render as intended. - I have been running this since today in the iPhone home-screen app from my fork (jiunshinn#7). The scrim fix came from that use (jiunshinn#11). Headless Chrome at 393×852 with CDP touch events: before the fix, a swipe from the scrim left the drawer at `translateX(-60px)`. After it, the drawer closes fully with no inline styles left. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Hako <devswha@gmail.com> Co-authored-by: devswha <devswha@users.noreply.github.com>
) ## Change Fixes devswha#809. Since 0.4.6 (devswha#747) a phone draws every pane of a split tab in herdr's grid. At 390px two panes are 195px each: chat wraps after three or four words, the composer covers the transcript, and each pane's key bar is cut off. Up to 768px the canvas now shows the selected pane alone at full size, the way a zoomed pane is shown, and the tab's menu (the `⋯` beside the open tab) lists the tab's panes first, the open one marked. Wider screens keep the split. - This is only what the browser draws: herdr's layout and zoom are not changed, so a desktop or the TUI on the same session is unaffected. No `pane.zoom` call was added. - The other panes stay mounted and hidden, so drafts and attachments are kept. They take the whole canvas too, so each keeps the grid it is shown with. - The decision is one pure function, `soloPane` (`src/lib/paneCanvas.ts`); `SOLO_PANE_QUERY` is shared by `PaneCanvas` and `TabStrip`. - No layout toggle: nothing on a phone wants 195px panes. Two existing checks asserted the behavior the issue reports ("the terminal lens retains both panes on a phone", "the phone retains the same split layout"). They now assert one visible pane, the picker, and that herdr's layout is not zoomed. Known limit: while herdr itself is zoomed, the selection follows herdr's zoomed pane (existing rule), so the picker cannot leave it. ## Validation - `bun run typecheck`: pass - `HERDR_TEST_MODE=unit bun test ./src/lib/paneCanvas.test.ts`: 7 pass (3 new cases for `soloPane`) - `bun run check run --build bun scripts/mobile-tabs-regression.ts`: pass (isolated herdr) - `bun run check run --build bun scripts/tab-menu-regression.ts`: pass (isolated herdr; 390x844 and 320x640: one visible frame, the menu switches the pane, `layout.zoomed === false`; a 1280px window narrowed to 700px shows one pane and lists the panes in its tab menu) - Demo client in Chrome at 390x844 and 1440x900 with a tab split three ways: one full-width pane and a three-pane menu on the phone, three panes and their dividers on the desktop, and the same page follows the viewport when resized. Not run to the end locally: `scripts/ui-regression.ts` stops on this Mac at "explicit Send now delivered one item" on unmodified `main` as well, so its later sections were run as the standalone scripts above.
…uest (devswha#821) ## Change `CONTRIBUTING.md` said "One change per PR" with no room for changes that cannot be split. It now also says that changes which cannot stand apart, because one needs another's code, may share a PR: each as its own commit, in dependency order, with the description saying what needs what. Independent changes still go in independent PRs, and a stack is still a maintainer's call; this only covers the case where a second PR could not typecheck or pass its tests without the first (for example devswha#820, where the telemetry change counts settings that the two commits before it add). ## Validation Prose only; no test.
…changed appearance settings (devswha#820) ## Change Three commits, in order; the third depends on the first two (it counts their settings). **1. Sidebar: workspace cards and pin stars are settings, off by default.** devswha#673 and devswha#778 turned both on for everyone. The Agents list is flat again and its rows carry no star unless **Settings → Appearance → Workspace cards** / **Pin agents** is on. Saved pins stay stored while the setting is off. **2. Plan: the flow and the header's count are settings, on by default.** **Plan flow** off opens a pane's plan as the list alone, with no switch; **Plan count in the header** off takes the plan button out of the header on that device. Defaults keep devswha#531 as it is. **3. Anonymous counts name the appearance settings changed from their defaults.** The install/update event gets `changed_settings`: names from a fixed list (theme, density, palette, sidebar rows, Agents order, workspace cards, pin agents, plan flow, plan count). Names only, never a value, never a setting the user types. - The browser tells its server the list on opening and on a change (`POST /api/telemetry { changed_settings }`; anything outside the list is a 400). The server keeps it for its next event and sends nothing by itself. - The notice says so and is shown once more (`NOTICE_VERSION = 2`). An install that saw the older notice sends nothing, its update count included, until it has seen this one. - The same switch, `HERDR_WEB_TELEMETRY=0` and `DO_NOT_TRACK` turn it off. - Receiver: a `changed_settings` column (sorted, comma-joined, `''` for none, `NULL` when not told). The Worker drops a name it does not know instead of refusing the event, so a newer app is still counted. `telemetry/README.md` has the `ALTER` and a query for "installs that changed each setting". The receiver is already deployed: the column was added to the D1 table and the Worker deployed from this branch's `telemetry/` (it accepts events from released versions unchanged). ## Validation - `bun run typecheck`: pass - `HERDR_TEST_MODE=unit bun test` on `server/telemetry.test.ts`, `telemetry/worker.test.ts`, `src/lib/settings.test.ts`, `i18n.test.ts`, `plan.test.ts`, `sidebarOrder.test.ts`, `sidebarAgents.test.ts`: 141 pass - `bun scripts/sidebar-activity-demo-regression.ts`: pass (new: the list is flat and starless by default; the plan's flow and header count stand by default on a desktop and a phone, and each goes with its setting) - End to end with the real `createServer` + `Telemetry`, the Worker's `handle` over an in-memory table from `schema.sql`, and Chrome: with theme, workspace cards and plan flow changed (and a typed terminal font), the stored row holds `agentCards,planFlow,theme`; neither the font nor any value leaves the PC; a later change is kept for the next event and sends nothing; with the switch off a new version sends nothing. - Deployed receiver: a test event stored `agentCards,theme` (an unknown name dropped); the test rows were deleted. `scripts/ui-regression.ts` was not run to the end locally (it stops on this Mac at "explicit Send now delivered one item" on unmodified `main` as well).
… back lands (#825) ## Change `scripts/settings-pages-demo-regression.ts` has timed out in CI on unrelated PRs (#819, #820, #815 twice, `feat/plan-step-detail`), always in a wait on `history.state` that said nothing when it ran out. **Cause, from CI's own trace** (first commit: the waits on the history say which step it was, what the history holds, and every `go` / `pushState` / `replaceState` / `popstate` up to then): ``` the reload: the history never held it. 138ms loaded {settings: {page: "alerts", depth: 2}} ?…&settings=alerts 298ms go(-2) 330ms popstate {} ?… 546ms pushState {settings: {page: null, depth: 1}} 549ms pushState {settings: {page: "alerts", depth: 2}} 736ms replaceState ?…&settings=alerts ``` A reload on a Settings entry steps out of it (`lib/settingsHistory.ts`), but that step lands when the browser gets to it. `App` reads the address once at load, and when it did so before the step landed, the address still named Settings: the dialog opened again and pushed its entries back. So this is a bug in the app that the test caught only on a slow runner, not a fault of the test: a reload with Settings open closed it on a fast device and kept it on a slow one. **Fix:** `lib/settingsHistory.ts` remembers at load whether the load began on an entry Settings left (`loadedOnSettingsEntry()`), and such a load does not open Settings from its address (`src/App.tsx`, the one read of the address at load). Asked of the module rather than read from the history when the app first draws: a review pointed out that the step back can also land *before* that, on an entry whose address names Settings too (the one a link to Settings opened), and the dialog would come back in that order instead. A link to Settings opened fresh begins on no such entry and opens as before. **Test:** the reload step now makes the slow order on purpose: it waits until the address names Settings, holds the load's steps back until the app has drawn itself, then lets them go, and asserts the dialog was never shown. Without the fix this fails every time locally with the same trace as CI; with it, it passes. A second case covers a link to Settings: it opens the dialog, and a reload there closes it. Not in this PR (older, another cause): a link naming both a file and Settings pushes the file preview's entry with a copy of the Settings entry, and a reload from the preview steps back onto a Settings entry, which opens Settings. ## Validation - `bun scripts/settings-pages-demo-regression.ts` without the fix: fails at "the reload", trace identical to CI's. - With the fix: 13 PASS, repeated runs. - CI on the first version of the fix: both browser lanes green on a full re-run (one earlier run failed in `terminal-command-backspace-regression.ts`, "readline fixture did not start", which this does not touch). - `bun run typecheck`: pass. `settingsHistory.test.ts`, `deepLink.test.ts`: 40 pass. Not reproduced by CPU load, CPU throttling or delaying `history.go` alone; a `replaceState`/`pushState` right after `history.go` does not cancel the traversal in Chrome (360/360 landed), so that was not it. ## Release Client-side only.
main gained a Turkish dictionary (devswha#815) after this branch was synced. The strings this change adds and renames get their Turkish entries here, so the dictionary check passes on the merged tree.
## Change Closes #640. Supersedes #652: @od-studio-webagency's commit is carried over onto current `main` with its authorship, and one commit on top changes two things. **New workspace** shows the PC's recent folders as chips under Directory, so a project whose panes were closed can be picked again without typing its path or browsing to it. - History is per PC, kept in this browser (`localStorage`). It is seeded from the folders of the panes a connected PC has open, and survives closing them and reloading. - Only a successful start moves a folder to the front; a failed or partly failed start changes no order. A folder merely seen in a pane goes behind what is kept: a pane's folder follows every `cd` of its shell (checked on an isolated herdr), so in front it would fill the three chips with wherever a shell has been. - New tab keeps its workspace's folder and shows no chips. Changed from #652: 1. **A chip fills Directory and starts nothing.** In #652 a click created the workspace at once, with no chance to change the agent or the name, or to take back a wrong click. 2. **Three chips, on one row.** #652 wrapped every stored folder into a scroll area of up to four rows. The dialog now shows the three most recent; a short name is shown whole and a long one gives way to an ellipsis. The stored history is not capped: capping it at three would reorder the list on every snapshot once a PC has four folders open. 3. **No hint under the chips.** The line explaining a click is gone; the chips and their tooltips say it. No server change. ## Validation - `bun run typecheck`: pass - `bun test src/lib/recent-directories.test.ts src/lib/i18n.test.ts scripts/ci-browser.test.ts`: 32 pass (the Turkish dictionary added in #815 has the three strings) - `bun run check run --build bun scripts/recent-folders-regression.ts` (Chrome, the test herdr session): pass at 1440, 1920 and 390 px in dark and light. It checks that four stored folders show three chips on one row without overflow, that a chip sets Directory and sends no request, then Start, pending, a failed start and a partly failed start. The script is added to `scripts/ci-browser.sh`. Not run locally: the whole unit and browser lanes. --------- Co-authored-by: studiood <253281802+od-studio-webagency@users.noreply.github.com>
|
@fitzgpt thank you again for the Turkish translation, it was thorough and careful work. A heads-up so it doesn't come as a surprise: we are removing the Turkish UI language in #1081. This is not about the translation's quality. Every UI language has to translate each new string and every release summary from then on, and the anonymous usage counts show very few installs that would see it (about 0.5% of installs are in Turkey). To keep that cost down, the app now keeps English, Korean, Japanese and Simplified Chinese, and CONTRIBUTING says new UI languages are not taken. A browser set to Turkish will show English, and a device that picked Turkish goes back to following the browser. Sorry to undo your work, and thanks for contributing. |
## Change The UI languages are English, Korean, Japanese and Simplified Chinese. Turkish (added in #815 three days ago) is removed with its README. - Telemetry: the language setting itself was never reported (the deployed counts name only seven appearance settings), so usage is read from the country: 5 of 1091 installs (0.5%) were last seen in Turkey. For comparison, KR 204, JP 40, and TW/HK/SG about 63. - Every UI language has to translate each new `t("…")` string (`i18n.test.ts` fails otherwise) and every release summary (`release-summaries.json`), so each one is a recurring cost. CONTRIBUTING now says new UI language PRs are not taken. - A browser set to Turkish now resolves to English. A device that chose `tr` in Settings loads as `system` (`sanitizeSettings` already falls back for an unknown value; a new test pins it). - `SUMMARY_LANGUAGES` drops `tr`, and the `tr` entries are removed from `release-summaries.json` (other languages unchanged, same JSON formatting). - Unchanged on purpose: `shared/telemetry.ts` keeps `tr` in its language enum (narrowing it would need a Worker redeploy); dictation's `tr-TR` speech language stays, since that is the browser's speech recognition, not UI text; the Turkish keyboard-layout notes in `shortcuts.ts` stay. ## Validation - `bun run check fast`: generated types, typecheck, build, unit tests all pass. - Demo app in Chromium: the Language select lists System, English, 简体中文, 日本語, 한국어; a `tr-TR` browser with `language: "tr"` stored renders `<html lang="en-US">` with the select on System; a `tr-TR` browser on System renders English; a `ko-KR` browser still renders Korean; no page errors.
Change
Adds Turkish (Türkçe) as a UI language, following the existing Korean/Japanese/Chinese pattern, in a single clean commit:
src/lib/i18n.tr.ts— a full dictionary of all 910 strings the code asks for, in the same grouped order as the other dictionaries. Labels and buttons are nouns, sentences are plain written Turkish; panel, çalışma alanı, ajan, sohbet, eşleştirme are the fixed terms, product names (herdr, Tailscale, Claude Code, Codex) stay as they are, placeholders keep their English names. Git worktree checkouts are rendered as "çalışma kopyası".src/lib/i18n.ts—trinLANGUAGE_SETTINGS/LANGUAGE_NAMES(Türkçe)/LOCALE_TAGS(tr-TR)/DICTIONARIESand the browser-detection regex.release-summaries.json— Turkish highlights for every release since 0.3.52, the same lists as the English ones (same line counts, each line within the 90-character limit), so the repo's own summaries checks stay green.shared/update.ts—trjoinsSUMMARY_LANGUAGES, which the test asserts equals the app's languages.README.tr.md— a full Turkish README, linked from the language line ofREADME.md.CONTRIBUTING.md,CHANGELOG.md, and the tworelease-notestest fixtures updated to include the new language; one test description renamed to "every language the app has".Validation
bun run generate:types --check— generated types are up to date.bun run typecheck— clean.bun run build— builds.bun run test:unit— 2691 tests, 0 fail; the i18n suite now covers the Turkish dictionary (every string present, no stale entries, placeholders intact, not just English repeated) andresolveLanguagepickstrfromtr-TR.Bağlamın %{percent}'i kaldı), checkout terminology, and several awkward sentences before opening this PR.